Home » openjdk-7 » java » awt » image » [javadoc | source]

    1   /*
    2    * Copyright (c) 1997, 2000, Oracle and/or its affiliates. All rights reserved.
    3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    4    *
    5    * This code is free software; you can redistribute it and/or modify it
    6    * under the terms of the GNU General Public License version 2 only, as
    7    * published by the Free Software Foundation.  Oracle designates this
    8    * particular file as subject to the "Classpath" exception as provided
    9    * by Oracle in the LICENSE file that accompanied this code.
   10    *
   11    * This code is distributed in the hope that it will be useful, but WITHOUT
   12    * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   13    * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   14    * version 2 for more details (a copy is included in the LICENSE file that
   15    * accompanied this code).
   16    *
   17    * You should have received a copy of the GNU General Public License version
   18    * 2 along with this work; if not, write to the Free Software Foundation,
   19    * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   20    *
   21    * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   22    * or visit www.oracle.com if you need additional information or have any
   23    * questions.
   24    */
   25   
   26   package java.awt.image;
   27   
   28   import java.awt.color.ColorSpace;
   29   import java.awt.geom.Rectangle2D;
   30   import java.awt.Rectangle;
   31   import java.awt.geom.Point2D;
   32   import java.awt.RenderingHints;
   33   import sun.awt.image.ImagingLib;
   34   
   35   /**
   36    * This class performs a pixel-by-pixel rescaling of the data in the
   37    * source image by multiplying the sample values for each pixel by a scale
   38    * factor and then adding an offset. The scaled sample values are clipped
   39    * to the minimum/maximum representable in the destination image.
   40    * <p>
   41    * The pseudo code for the rescaling operation is as follows:
   42    * <pre>
   43    *for each pixel from Source object {
   44    *    for each band/component of the pixel {
   45    *        dstElement = (srcElement*scaleFactor) + offset
   46    *    }
   47    *}
   48    * </pre>
   49    * <p>
   50    * For Rasters, rescaling operates on bands.  The number of
   51    * sets of scaling constants may be one, in which case the same constants
   52    * are applied to all bands, or it must equal the number of Source
   53    * Raster bands.
   54    * <p>
   55    * For BufferedImages, rescaling operates on color and alpha components.
   56    * The number of sets of scaling constants may be one, in which case the
   57    * same constants are applied to all color (but not alpha) components.
   58    * Otherwise, the  number of sets of scaling constants may
   59    * equal the number of Source color components, in which case no
   60    * rescaling of the alpha component (if present) is performed.
   61    * If neither of these cases apply, the number of sets of scaling constants
   62    * must equal the number of Source color components plus alpha components,
   63    * in which case all color and alpha components are rescaled.
   64    * <p>
   65    * BufferedImage sources with premultiplied alpha data are treated in the same
   66    * manner as non-premultiplied images for purposes of rescaling.  That is,
   67    * the rescaling is done per band on the raw data of the BufferedImage source
   68    * without regard to whether the data is premultiplied.  If a color conversion
   69    * is required to the destination ColorModel, the premultiplied state of
   70    * both source and destination will be taken into account for this step.
   71    * <p>
   72    * Images with an IndexColorModel cannot be rescaled.
   73    * <p>
   74    * If a RenderingHints object is specified in the constructor, the
   75    * color rendering hint and the dithering hint may be used when color
   76    * conversion is required.
   77    * <p>
   78    * Note that in-place operation is allowed (i.e. the source and destination can
   79    * be the same object).
   80    * @see java.awt.RenderingHints#KEY_COLOR_RENDERING
   81    * @see java.awt.RenderingHints#KEY_DITHERING
   82    */
   83   public class RescaleOp implements BufferedImageOp, RasterOp {
   84       float[] scaleFactors;
   85       float[] offsets;
   86       int length = 0;
   87       RenderingHints hints;
   88   
   89       private int srcNbits;
   90       private int dstNbits;
   91   
   92   
   93       /**
   94        * Constructs a new RescaleOp with the desired scale factors
   95        * and offsets.  The length of the scaleFactor and offset arrays
   96        * must meet the restrictions stated in the class comments above.
   97        * The RenderingHints argument may be null.
   98        * @param scaleFactors the specified scale factors
   99        * @param offsets the specified offsets
  100        * @param hints the specified <code>RenderingHints</code>, or
  101        *        <code>null</code>
  102        */
  103       public RescaleOp (float[] scaleFactors, float[] offsets,
  104                         RenderingHints hints) {
  105           length = scaleFactors.length;
  106           if (length > offsets.length) length = offsets.length;
  107   
  108           this.scaleFactors = new float[length];
  109           this.offsets      = new float[length];
  110           for (int i=0; i < length; i++) {
  111               this.scaleFactors[i] = scaleFactors[i];
  112               this.offsets[i]      = offsets[i];
  113           }
  114           this.hints = hints;
  115       }
  116   
  117       /**
  118        * Constructs a new RescaleOp with the desired scale factor
  119        * and offset.  The scaleFactor and offset will be applied to
  120        * all bands in a source Raster and to all color (but not alpha)
  121        * components in a BufferedImage.
  122        * The RenderingHints argument may be null.
  123        * @param scaleFactor the specified scale factor
  124        * @param offset the specified offset
  125        * @param hints the specified <code>RenderingHints</code>, or
  126        *        <code>null</code>
  127        */
  128       public RescaleOp (float scaleFactor, float offset, RenderingHints hints) {
  129           length = 1;
  130           this.scaleFactors = new float[1];
  131           this.offsets      = new float[1];
  132           this.scaleFactors[0] = scaleFactor;
  133           this.offsets[0]       = offset;
  134           this.hints = hints;
  135       }
  136   
  137       /**
  138        * Returns the scale factors in the given array. The array is also
  139        * returned for convenience.  If scaleFactors is null, a new array
  140        * will be allocated.
  141        * @param scaleFactors the array to contain the scale factors of
  142        *        this <code>RescaleOp</code>
  143        * @return the scale factors of this <code>RescaleOp</code>.
  144        */
  145       final public float[] getScaleFactors (float scaleFactors[]) {
  146           if (scaleFactors == null) {
  147               return (float[]) this.scaleFactors.clone();
  148           }
  149           System.arraycopy (this.scaleFactors, 0, scaleFactors, 0,
  150                             Math.min(this.scaleFactors.length,
  151                                      scaleFactors.length));
  152           return scaleFactors;
  153       }
  154   
  155       /**
  156        * Returns the offsets in the given array. The array is also returned
  157        * for convenience.  If offsets is null, a new array
  158        * will be allocated.
  159        * @param offsets the array to contain the offsets of
  160        *        this <code>RescaleOp</code>
  161        * @return the offsets of this <code>RescaleOp</code>.
  162        */
  163       final public float[] getOffsets(float offsets[]) {
  164           if (offsets == null) {
  165               return (float[]) this.offsets.clone();
  166           }
  167   
  168           System.arraycopy (this.offsets, 0, offsets, 0,
  169                             Math.min(this.offsets.length, offsets.length));
  170           return offsets;
  171       }
  172   
  173       /**
  174        * Returns the number of scaling factors and offsets used in this
  175        * RescaleOp.
  176        * @return the number of scaling factors and offsets of this
  177        *         <code>RescaleOp</code>.
  178        */
  179       final public int getNumFactors() {
  180           return length;
  181       }
  182   
  183   
  184       /**
  185        * Creates a ByteLookupTable to implement the rescale.
  186        * The table may have either a SHORT or BYTE input.
  187        * @param nElems    Number of elements the table is to have.
  188        *                  This will generally be 256 for byte and
  189        *                  65536 for short.
  190        */
  191       private ByteLookupTable createByteLut(float scale[],
  192                                             float off[],
  193                                             int   nBands,
  194                                             int   nElems) {
  195   
  196           byte[][]        lutData = new byte[scale.length][nElems];
  197   
  198           for (int band=0; band<scale.length; band++) {
  199               float  bandScale   = scale[band];
  200               float  bandOff     = off[band];
  201               byte[] bandLutData = lutData[band];
  202               for (int i=0; i<nElems; i++) {
  203                   int val = (int)(i*bandScale + bandOff);
  204                   if ((val & 0xffffff00) != 0) {
  205                       if (val < 0) {
  206                           val = 0;
  207                       } else {
  208                           val = 255;
  209                       }
  210                   }
  211                   bandLutData[i] = (byte)val;
  212               }
  213   
  214           }
  215   
  216           return new ByteLookupTable(0, lutData);
  217       }
  218   
  219       /**
  220        * Creates a ShortLookupTable to implement the rescale.
  221        * The table may have either a SHORT or BYTE input.
  222        * @param nElems    Number of elements the table is to have.
  223        *                  This will generally be 256 for byte and
  224        *                  65536 for short.
  225        */
  226       private ShortLookupTable createShortLut(float scale[],
  227                                               float off[],
  228                                               int   nBands,
  229                                               int   nElems) {
  230   
  231           short[][]        lutData = new short[scale.length][nElems];
  232   
  233           for (int band=0; band<scale.length; band++) {
  234               float   bandScale   = scale[band];
  235               float   bandOff     = off[band];
  236               short[] bandLutData = lutData[band];
  237               for (int i=0; i<nElems; i++) {
  238                   int val = (int)(i*bandScale + bandOff);
  239                   if ((val & 0xffff0000) != 0) {
  240                       if (val < 0) {
  241                           val = 0;
  242                       } else {
  243                           val = 65535;
  244                       }
  245                   }
  246                   bandLutData[i] = (short)val;
  247               }
  248           }
  249   
  250           return new ShortLookupTable(0, lutData);
  251       }
  252   
  253   
  254       /**
  255        * Determines if the rescale can be performed as a lookup.
  256        * The dst must be a byte or short type.
  257        * The src must be less than 16 bits.
  258        * All source band sizes must be the same and all dst band sizes
  259        * must be the same.
  260        */
  261       private boolean canUseLookup(Raster src, Raster dst) {
  262   
  263           //
  264           // Check that the src datatype is either a BYTE or SHORT
  265           //
  266           int datatype = src.getDataBuffer().getDataType();
  267           if(datatype != DataBuffer.TYPE_BYTE &&
  268              datatype != DataBuffer.TYPE_USHORT) {
  269               return false;
  270           }
  271   
  272           //
  273           // Check dst sample sizes. All must be 8 or 16 bits.
  274           //
  275           SampleModel dstSM = dst.getSampleModel();
  276           dstNbits = dstSM.getSampleSize(0);
  277   
  278           if (!(dstNbits == 8 || dstNbits == 16)) {
  279               return false;
  280           }
  281           for (int i=1; i<src.getNumBands(); i++) {
  282               int bandSize = dstSM.getSampleSize(i);
  283               if (bandSize != dstNbits) {
  284                   return false;
  285               }
  286           }
  287   
  288           //
  289           // Check src sample sizes. All must be the same size
  290           //
  291           SampleModel srcSM = src.getSampleModel();
  292           srcNbits = srcSM.getSampleSize(0);
  293           if (srcNbits > 16) {
  294               return false;
  295           }
  296           for (int i=1; i<src.getNumBands(); i++) {
  297               int bandSize = srcSM.getSampleSize(i);
  298               if (bandSize != srcNbits) {
  299                   return false;
  300               }
  301           }
  302   
  303           return true;
  304       }
  305   
  306       /**
  307        * Rescales the source BufferedImage.
  308        * If the color model in the source image is not the same as that
  309        * in the destination image, the pixels will be converted
  310        * in the destination.  If the destination image is null,
  311        * a BufferedImage will be created with the source ColorModel.
  312        * An IllegalArgumentException may be thrown if the number of
  313        * scaling factors/offsets in this object does not meet the
  314        * restrictions stated in the class comments above, or if the
  315        * source image has an IndexColorModel.
  316        * @param src the <code>BufferedImage</code> to be filtered
  317        * @param dst the destination for the filtering operation
  318        *            or <code>null</code>
  319        * @return the filtered <code>BufferedImage</code>.
  320        * @throws IllegalArgumentException if the <code>ColorModel</code>
  321        *         of <code>src</code> is an <code>IndexColorModel</code>,
  322        *         or if the number of scaling factors and offsets in this
  323        *         <code>RescaleOp</code> do not meet the requirements
  324        *         stated in the class comments.
  325        */
  326       public final BufferedImage filter (BufferedImage src, BufferedImage dst) {
  327           ColorModel srcCM = src.getColorModel();
  328           ColorModel dstCM;
  329           int numBands = srcCM.getNumColorComponents();
  330   
  331   
  332           if (srcCM instanceof IndexColorModel) {
  333               throw new
  334                   IllegalArgumentException("Rescaling cannot be "+
  335                                            "performed on an indexed image");
  336           }
  337           if (length != 1 && length != numBands &&
  338               length != srcCM.getNumComponents())
  339           {
  340               throw new IllegalArgumentException("Number of scaling constants "+
  341                                                  "does not equal the number of"+
  342                                                  " of color or color/alpha "+
  343                                                  " components");
  344           }
  345   
  346           boolean needToConvert = false;
  347   
  348           // Include alpha
  349           if (length > numBands && srcCM.hasAlpha()) {
  350               length = numBands+1;
  351           }
  352   
  353           int width = src.getWidth();
  354           int height = src.getHeight();
  355   
  356           if (dst == null) {
  357               dst = createCompatibleDestImage(src, null);
  358               dstCM = srcCM;
  359           }
  360           else {
  361               if (width != dst.getWidth()) {
  362                   throw new
  363                       IllegalArgumentException("Src width ("+width+
  364                                                ") not equal to dst width ("+
  365                                                dst.getWidth()+")");
  366               }
  367               if (height != dst.getHeight()) {
  368                   throw new
  369                       IllegalArgumentException("Src height ("+height+
  370                                                ") not equal to dst height ("+
  371                                                dst.getHeight()+")");
  372               }
  373   
  374               dstCM = dst.getColorModel();
  375               if(srcCM.getColorSpace().getType() !=
  376                  dstCM.getColorSpace().getType()) {
  377                   needToConvert = true;
  378                   dst = createCompatibleDestImage(src, null);
  379               }
  380   
  381           }
  382   
  383           BufferedImage origDst = dst;
  384   
  385           //
  386           // Try to use a native BI rescale operation first
  387           //
  388           if (ImagingLib.filter(this, src, dst) == null) {
  389               //
  390               // Native BI rescale failed - convert to rasters
  391               //
  392               WritableRaster srcRaster = src.getRaster();
  393               WritableRaster dstRaster = dst.getRaster();
  394   
  395               if (srcCM.hasAlpha()) {
  396                   if (numBands-1 == length || length == 1) {
  397                       int minx = srcRaster.getMinX();
  398                       int miny = srcRaster.getMinY();
  399                       int[] bands = new int[numBands-1];
  400                       for (int i=0; i < numBands-1; i++) {
  401                           bands[i] = i;
  402                       }
  403                       srcRaster =
  404                           srcRaster.createWritableChild(minx, miny,
  405                                                         srcRaster.getWidth(),
  406                                                         srcRaster.getHeight(),
  407                                                         minx, miny,
  408                                                         bands);
  409                   }
  410               }
  411               if (dstCM.hasAlpha()) {
  412                   int dstNumBands = dstRaster.getNumBands();
  413                   if (dstNumBands-1 == length || length == 1) {
  414                       int minx = dstRaster.getMinX();
  415                       int miny = dstRaster.getMinY();
  416                       int[] bands = new int[numBands-1];
  417                       for (int i=0; i < numBands-1; i++) {
  418                           bands[i] = i;
  419                       }
  420                       dstRaster =
  421                           dstRaster.createWritableChild(minx, miny,
  422                                                         dstRaster.getWidth(),
  423                                                         dstRaster.getHeight(),
  424                                                         minx, miny,
  425                                                         bands);
  426                   }
  427               }
  428   
  429               //
  430               // Call the raster filter method
  431               //
  432               filter(srcRaster, dstRaster);
  433   
  434           }
  435   
  436           if (needToConvert) {
  437               // ColorModels are not the same
  438               ColorConvertOp ccop = new ColorConvertOp(hints);
  439               ccop.filter(dst, origDst);
  440           }
  441   
  442           return origDst;
  443       }
  444   
  445       /**
  446        * Rescales the pixel data in the source Raster.
  447        * If the destination Raster is null, a new Raster will be created.
  448        * The source and destination must have the same number of bands.
  449        * Otherwise, an IllegalArgumentException is thrown.
  450        * Note that the number of scaling factors/offsets in this object must
  451        * meet the restrictions stated in the class comments above.
  452        * Otherwise, an IllegalArgumentException is thrown.
  453        * @param src the <code>Raster</code> to be filtered
  454        * @param dst the destination for the filtering operation
  455        *            or <code>null</code>
  456        * @return the filtered <code>WritableRaster</code>.
  457        * @throws IllegalArgumentException if <code>src</code> and
  458        *         <code>dst</code> do not have the same number of bands,
  459        *         or if the number of scaling factors and offsets in this
  460        *         <code>RescaleOp</code> do not meet the requirements
  461        *         stated in the class comments.
  462        */
  463       public final WritableRaster filter (Raster src, WritableRaster dst)  {
  464           int numBands = src.getNumBands();
  465           int width  = src.getWidth();
  466           int height = src.getHeight();
  467           int[] srcPix = null;
  468           int step = 0;
  469           int tidx = 0;
  470   
  471           // Create a new destination Raster, if needed
  472           if (dst == null) {
  473               dst = createCompatibleDestRaster(src);
  474           }
  475           else if (height != dst.getHeight() || width != dst.getWidth()) {
  476               throw new
  477                  IllegalArgumentException("Width or height of Rasters do not "+
  478                                           "match");
  479           }
  480           else if (numBands != dst.getNumBands()) {
  481               // Make sure that the number of bands are equal
  482               throw new IllegalArgumentException("Number of bands in src "
  483                               + numBands
  484                               + " does not equal number of bands in dest "
  485                               + dst.getNumBands());
  486           }
  487           // Make sure that the arrays match
  488           // Make sure that the low/high/constant arrays match
  489           if (length != 1 && length != src.getNumBands()) {
  490               throw new IllegalArgumentException("Number of scaling constants "+
  491                                                  "does not equal the number of"+
  492                                                  " of bands in the src raster");
  493           }
  494   
  495   
  496           //
  497           // Try for a native raster rescale first
  498           //
  499           if (ImagingLib.filter(this, src, dst) != null) {
  500               return dst;
  501           }
  502   
  503           //
  504           // Native raster rescale failed.
  505           // Try to see if a lookup operation can be used
  506           //
  507           if (canUseLookup(src, dst)) {
  508               int srcNgray = (1 << srcNbits);
  509               int dstNgray = (1 << dstNbits);
  510   
  511               if (dstNgray == 256) {
  512                   ByteLookupTable lut = createByteLut(scaleFactors, offsets,
  513                                                       numBands, srcNgray);
  514                   LookupOp op = new LookupOp(lut, hints);
  515                   op.filter(src, dst);
  516               } else {
  517                   ShortLookupTable lut = createShortLut(scaleFactors, offsets,
  518                                                         numBands, srcNgray);
  519                   LookupOp op = new LookupOp(lut, hints);
  520                   op.filter(src, dst);
  521               }
  522           } else {
  523               //
  524               // Fall back to the slow code
  525               //
  526               if (length > 1) {
  527                   step = 1;
  528               }
  529   
  530               int sminX = src.getMinX();
  531               int sY = src.getMinY();
  532               int dminX = dst.getMinX();
  533               int dY = dst.getMinY();
  534               int sX;
  535               int dX;
  536   
  537               //
  538               //  Determine bits per band to determine maxval for clamps.
  539               //  The min is assumed to be zero.
  540               //  REMIND: This must change if we ever support signed data types.
  541               //
  542               int nbits;
  543               int dstMax[] = new int[numBands];
  544               int dstMask[] = new int[numBands];
  545               SampleModel dstSM = dst.getSampleModel();
  546               for (int z=0; z<numBands; z++) {
  547                   nbits = dstSM.getSampleSize(z);
  548                   dstMax[z] = (1 << nbits) - 1;
  549                   dstMask[z] = ~(dstMax[z]);
  550               }
  551   
  552               int val;
  553               for (int y=0; y < height; y++, sY++, dY++) {
  554                   dX = dminX;
  555                   sX = sminX;
  556                   for (int x = 0; x < width; x++, sX++, dX++) {
  557                       // Get data for all bands at this x,y position
  558                       srcPix = src.getPixel(sX, sY, srcPix);
  559                       tidx = 0;
  560                       for (int z=0; z<numBands; z++, tidx += step) {
  561                           val = (int)(srcPix[z]*scaleFactors[tidx]
  562                                             + offsets[tidx]);
  563                           // Clamp
  564                           if ((val & dstMask[z]) != 0) {
  565                               if (val < 0) {
  566                                   val = 0;
  567                               } else {
  568                                   val = dstMax[z];
  569                               }
  570                           }
  571                           srcPix[z] = val;
  572   
  573                       }
  574   
  575                       // Put it back for all bands
  576                       dst.setPixel(dX, dY, srcPix);
  577                   }
  578               }
  579           }
  580           return dst;
  581       }
  582   
  583       /**
  584        * Returns the bounding box of the rescaled destination image.  Since
  585        * this is not a geometric operation, the bounding box does not
  586        * change.
  587        */
  588       public final Rectangle2D getBounds2D (BufferedImage src) {
  589            return getBounds2D(src.getRaster());
  590       }
  591   
  592       /**
  593        * Returns the bounding box of the rescaled destination Raster.  Since
  594        * this is not a geometric operation, the bounding box does not
  595        * change.
  596        * @param src the rescaled destination <code>Raster</code>
  597        * @return the bounds of the specified <code>Raster</code>.
  598        */
  599       public final Rectangle2D getBounds2D (Raster src) {
  600           return src.getBounds();
  601       }
  602   
  603       /**
  604        * Creates a zeroed destination image with the correct size and number of
  605        * bands.
  606        * @param src       Source image for the filter operation.
  607        * @param destCM    ColorModel of the destination.  If null, the
  608        *                  ColorModel of the source will be used.
  609        * @return the zeroed-destination image.
  610        */
  611       public BufferedImage createCompatibleDestImage (BufferedImage src,
  612                                                       ColorModel destCM) {
  613           BufferedImage image;
  614           if (destCM == null) {
  615               ColorModel cm = src.getColorModel();
  616               image = new BufferedImage(cm,
  617                                         src.getRaster().createCompatibleWritableRaster(),
  618                                         cm.isAlphaPremultiplied(),
  619                                         null);
  620           }
  621           else {
  622               int w = src.getWidth();
  623               int h = src.getHeight();
  624               image = new BufferedImage (destCM,
  625                                      destCM.createCompatibleWritableRaster(w, h),
  626                                      destCM.isAlphaPremultiplied(), null);
  627           }
  628   
  629           return image;
  630       }
  631   
  632       /**
  633        * Creates a zeroed-destination <code>Raster</code> with the correct
  634        * size and number of bands, given this source.
  635        * @param src       the source <code>Raster</code>
  636        * @return the zeroed-destination <code>Raster</code>.
  637        */
  638       public WritableRaster createCompatibleDestRaster (Raster src) {
  639           return src.createCompatibleWritableRaster(src.getWidth(), src.getHeight());
  640       }
  641   
  642       /**
  643        * Returns the location of the destination point given a
  644        * point in the source.  If dstPt is non-null, it will
  645        * be used to hold the return value.  Since this is not a geometric
  646        * operation, the srcPt will equal the dstPt.
  647        * @param srcPt a point in the source image
  648        * @param dstPt the destination point or <code>null</code>
  649        * @return the location of the destination point.
  650        */
  651       public final Point2D getPoint2D (Point2D srcPt, Point2D dstPt) {
  652           if (dstPt == null) {
  653               dstPt = new Point2D.Float();
  654           }
  655           dstPt.setLocation(srcPt.getX(), srcPt.getY());
  656           return dstPt;
  657       }
  658   
  659       /**
  660        * Returns the rendering hints for this op.
  661        * @return the rendering hints of this <code>RescaleOp</code>.
  662        */
  663       public final RenderingHints getRenderingHints() {
  664           return hints;
  665       }
  666   }

Home » openjdk-7 » java » awt » image » [javadoc | source]