1 /*
   2  * Copyright (c) 1997, 2014, 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 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 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 numSrcColorComp = srcCM.getNumColorComponents();
 330         int scaleConst = length;
 331 
 332         if (srcCM instanceof IndexColorModel) {
 333             throw new
 334                 IllegalArgumentException("Rescaling cannot be "+
 335                                          "performed on an indexed image");
 336         }
 337         if (scaleConst != 1 && scaleConst != numSrcColorComp &&
 338             scaleConst != 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 (scaleConst > numSrcColorComp && srcCM.hasAlpha()) {
 350             scaleConst = numSrcColorComp+1;
 351         }
 352 
 353         int width = src.getWidth();
 354         int height = src.getHeight();
 355 
 356         BufferedImage origDst = dst;
 357         if (dst == null) {
 358             dst = createCompatibleDestImage(src, null);
 359             dstCM = srcCM;
 360         }
 361         else {
 362             if (width != dst.getWidth()) {
 363                 throw new
 364                     IllegalArgumentException("Src width ("+width+
 365                                              ") not equal to dst width ("+
 366                                              dst.getWidth()+")");
 367             }
 368             if (height != dst.getHeight()) {
 369                 throw new
 370                     IllegalArgumentException("Src height ("+height+
 371                                              ") not equal to dst height ("+
 372                                              dst.getHeight()+")");
 373             }
 374 
 375             dstCM = dst.getColorModel();
 376             if(srcCM.getColorSpace().getType() !=
 377                dstCM.getColorSpace().getType()) {
 378                 needToConvert = true;
 379                 dst = createCompatibleDestImage(src, null);
 380             }
 381 
 382         }
 383 
 384         boolean scaleAlpha = true;
 385 
 386         //
 387         // The number of sets of scaling constants may be one,
 388         // in which case the same constants are applied to all color
 389         // (but NOT alpha) components. Otherwise, the number of sets
 390         // of scaling constants may equal the number of Source color
 391         // components, in which case NO rescaling of the alpha component
 392         // (if present) is performed.
 393         //
 394         if (numSrcColorComp == scaleConst || scaleConst == 1) {
 395             scaleAlpha = false;
 396         }
 397 
 398         //
 399         // Try to use a native BI rescale operation first
 400         //
 401         if (ImagingLib.filter(this, src, dst) == null) {
 402             //
 403             // Native BI rescale failed - convert to rasters
 404             //
 405             WritableRaster srcRaster = src.getRaster();
 406             WritableRaster dstRaster = dst.getRaster();
 407 
 408             if (!scaleAlpha) {
 409                 if (srcCM.hasAlpha()) {
 410                     // Do not rescale Alpha component
 411                     int minx = srcRaster.getMinX();
 412                     int miny = srcRaster.getMinY();
 413                     int[] bands = new int[numSrcColorComp];
 414                     for (int i=0; i < numSrcColorComp; i++) {
 415                         bands[i] = i;
 416                     }
 417                     srcRaster =
 418                         srcRaster.createWritableChild(minx, miny,
 419                                                       srcRaster.getWidth(),
 420                                                       srcRaster.getHeight(),
 421                                                       minx, miny,
 422                                                       bands);
 423                 }
 424                 if (dstCM.hasAlpha()) {
 425                     int minx = dstRaster.getMinX();
 426                     int miny = dstRaster.getMinY();
 427                     int[] bands = new int[numSrcColorComp];
 428                     for (int i=0; i < numSrcColorComp; i++) {
 429                         bands[i] = i;
 430                     }
 431                     dstRaster =
 432                         dstRaster.createWritableChild(minx, miny,
 433                                                       dstRaster.getWidth(),
 434                                                       dstRaster.getHeight(),
 435                                                       minx, miny,
 436                                                       bands);
 437                 }
 438             }
 439 
 440             //
 441             // Call the raster filter method
 442             //
 443             filterRasterImpl(srcRaster, dstRaster, scaleConst);
 444 
 445             //
 446             // here copy the unscaled src alpha to destination alpha channel
 447             //
 448             if (!scaleAlpha) {
 449                 Raster srcAlphaRaster = null;
 450                 WritableRaster dstAlphaRaster = null;
 451 
 452                 if (srcCM.hasAlpha()) {
 453                     srcAlphaRaster = src.getAlphaRaster();
 454                 }
 455                 if (dstCM.hasAlpha()) {
 456                     dstAlphaRaster = dst.getAlphaRaster();
 457                     if (srcAlphaRaster != null) {
 458                         dstAlphaRaster.setRect(srcAlphaRaster);
 459                     } else {
 460                         int alpha = 0xff << 24;
 461                         for (int cy=0; cy < dst.getHeight(); cy++) {
 462                             for (int cx=0; cx < dst.getWidth(); cx++) {
 463                                 int color = dst.getRGB(cx, cy);
 464 
 465                                 dst.setRGB(cx, cy, color | alpha);
 466                             }
 467                         }
 468                     }
 469                 }
 470             }
 471         }
 472 
 473         if (needToConvert) {
 474             // ColorModels are not the same
 475             ColorConvertOp ccop = new ColorConvertOp(hints);
 476             dst = ccop.filter(dst, origDst);
 477         }
 478         return dst;
 479     }
 480 
 481     /**
 482      * Rescales the pixel data in the source Raster.
 483      * If the destination Raster is null, a new Raster will be created.
 484      * The source and destination must have the same number of bands.
 485      * Otherwise, an IllegalArgumentException is thrown.
 486      * Note that the number of scaling factors/offsets in this object must
 487      * meet the restrictions stated in the class comments above.
 488      * Otherwise, an IllegalArgumentException is thrown.
 489      * @param src the <code>Raster</code> to be filtered
 490      * @param dst the destination for the filtering operation
 491      *            or <code>null</code>
 492      * @return the filtered <code>WritableRaster</code>.
 493      * @throws IllegalArgumentException if <code>src</code> and
 494      *         <code>dst</code> do not have the same number of bands,
 495      *         or if the number of scaling factors and offsets in this
 496      *         <code>RescaleOp</code> do not meet the requirements
 497      *         stated in the class comments.
 498      */
 499     public final WritableRaster filter (Raster src, WritableRaster dst)  {
 500         return filterRasterImpl(src, dst, length);
 501     }
 502 
 503     private WritableRaster filterRasterImpl(Raster src, WritableRaster dst, int scaleConst) {
 504         int numBands = src.getNumBands();
 505         int width  = src.getWidth();
 506         int height = src.getHeight();
 507         int[] srcPix = null;
 508         int step = 0;
 509         int tidx = 0;
 510 
 511         // Create a new destination Raster, if needed
 512         if (dst == null) {
 513             dst = createCompatibleDestRaster(src);
 514         }
 515         else if (height != dst.getHeight() || width != dst.getWidth()) {
 516             throw new
 517                IllegalArgumentException("Width or height of Rasters do not "+
 518                                         "match");
 519         }
 520         else if (numBands != dst.getNumBands()) {
 521             // Make sure that the number of bands are equal
 522             throw new IllegalArgumentException("Number of bands in src "
 523                             + numBands
 524                             + " does not equal number of bands in dest "
 525                             + dst.getNumBands());
 526         }
 527 
 528         // Make sure that the arrays match
 529         // Make sure that the low/high/constant arrays match
 530         if (scaleConst != 1 && scaleConst != src.getNumBands()) {
 531             throw new IllegalArgumentException("Number of scaling constants "+
 532                                                "does not equal the number of"+
 533                                                " of bands in the src raster");
 534         }
 535 
 536         //
 537         // Try for a native raster rescale first
 538         //
 539         if (ImagingLib.filter(this, src, dst) != null) {
 540             return dst;
 541         }
 542 
 543         //
 544         // Native raster rescale failed.
 545         // Try to see if a lookup operation can be used
 546         //
 547         if (canUseLookup(src, dst)) {
 548             int srcNgray = (1 << srcNbits);
 549             int dstNgray = (1 << dstNbits);
 550 
 551             if (dstNgray == 256) {
 552                 ByteLookupTable lut = createByteLut(scaleFactors, offsets,
 553                                                     numBands, srcNgray);
 554                 LookupOp op = new LookupOp(lut, hints);
 555                 op.filter(src, dst);
 556             } else {
 557                 ShortLookupTable lut = createShortLut(scaleFactors, offsets,
 558                                                       numBands, srcNgray);
 559                 LookupOp op = new LookupOp(lut, hints);
 560                 op.filter(src, dst);
 561             }
 562         } else {
 563             //
 564             // Fall back to the slow code
 565             //
 566             if (scaleConst > 1) {
 567                 step = 1;
 568             }
 569 
 570             int sminX = src.getMinX();
 571             int sY = src.getMinY();
 572             int dminX = dst.getMinX();
 573             int dY = dst.getMinY();
 574             int sX;
 575             int dX;
 576 
 577             //
 578             //  Determine bits per band to determine maxval for clamps.
 579             //  The min is assumed to be zero.
 580             //  REMIND: This must change if we ever support signed data types.
 581             //
 582             int nbits;
 583             int dstMax[] = new int[numBands];
 584             int dstMask[] = new int[numBands];
 585             SampleModel dstSM = dst.getSampleModel();
 586             for (int z=0; z<numBands; z++) {
 587                 nbits = dstSM.getSampleSize(z);
 588                 dstMax[z] = (1 << nbits) - 1;
 589                 dstMask[z] = ~(dstMax[z]);
 590             }
 591 
 592             int val;
 593             for (int y=0; y < height; y++, sY++, dY++) {
 594                 dX = dminX;
 595                 sX = sminX;
 596                 for (int x = 0; x < width; x++, sX++, dX++) {
 597                     // Get data for all bands at this x,y position
 598                     srcPix = src.getPixel(sX, sY, srcPix);
 599                     tidx = 0;
 600                     for (int z=0; z<numBands; z++, tidx += step) {
 601                         val = (int)(srcPix[z]*scaleFactors[tidx]
 602                                           + offsets[tidx]);
 603                         // Clamp
 604                         if ((val & dstMask[z]) != 0) {
 605                             if (val < 0) {
 606                                 val = 0;
 607                             } else {
 608                                 val = dstMax[z];
 609                             }
 610                         }
 611                         srcPix[z] = val;
 612 
 613                     }
 614 
 615                     // Put it back for all bands
 616                     dst.setPixel(dX, dY, srcPix);
 617                 }
 618             }
 619         }
 620         return dst;
 621     }
 622 
 623     /**
 624      * Returns the bounding box of the rescaled destination image.  Since
 625      * this is not a geometric operation, the bounding box does not
 626      * change.
 627      */
 628     public final Rectangle2D getBounds2D (BufferedImage src) {
 629          return getBounds2D(src.getRaster());
 630     }
 631 
 632     /**
 633      * Returns the bounding box of the rescaled destination Raster.  Since
 634      * this is not a geometric operation, the bounding box does not
 635      * change.
 636      * @param src the rescaled destination <code>Raster</code>
 637      * @return the bounds of the specified <code>Raster</code>.
 638      */
 639     public final Rectangle2D getBounds2D (Raster src) {
 640         return src.getBounds();
 641     }
 642 
 643     /**
 644      * Creates a zeroed destination image with the correct size and number of
 645      * bands.
 646      * @param src       Source image for the filter operation.
 647      * @param destCM    ColorModel of the destination.  If null, the
 648      *                  ColorModel of the source will be used.
 649      * @return the zeroed-destination image.
 650      */
 651     public BufferedImage createCompatibleDestImage (BufferedImage src,
 652                                                     ColorModel destCM) {
 653         BufferedImage image;
 654         if (destCM == null) {
 655             ColorModel cm = src.getColorModel();
 656             image = new BufferedImage(cm,
 657                                       src.getRaster().createCompatibleWritableRaster(),
 658                                       cm.isAlphaPremultiplied(),
 659                                       null);
 660         }
 661         else {
 662             int w = src.getWidth();
 663             int h = src.getHeight();
 664             image = new BufferedImage (destCM,
 665                                    destCM.createCompatibleWritableRaster(w, h),
 666                                    destCM.isAlphaPremultiplied(), null);
 667         }
 668 
 669         return image;
 670     }
 671 
 672     /**
 673      * Creates a zeroed-destination <code>Raster</code> with the correct
 674      * size and number of bands, given this source.
 675      * @param src       the source <code>Raster</code>
 676      * @return the zeroed-destination <code>Raster</code>.
 677      */
 678     public WritableRaster createCompatibleDestRaster (Raster src) {
 679         return src.createCompatibleWritableRaster(src.getWidth(), src.getHeight());
 680     }
 681 
 682     /**
 683      * Returns the location of the destination point given a
 684      * point in the source.  If dstPt is non-null, it will
 685      * be used to hold the return value.  Since this is not a geometric
 686      * operation, the srcPt will equal the dstPt.
 687      * @param srcPt a point in the source image
 688      * @param dstPt the destination point or <code>null</code>
 689      * @return the location of the destination point.
 690      */
 691     public final Point2D getPoint2D (Point2D srcPt, Point2D dstPt) {
 692         if (dstPt == null) {
 693             dstPt = new Point2D.Float();
 694         }
 695         dstPt.setLocation(srcPt.getX(), srcPt.getY());
 696         return dstPt;
 697     }
 698 
 699     /**
 700      * Returns the rendering hints for this op.
 701      * @return the rendering hints of this <code>RescaleOp</code>.
 702      */
 703     public final RenderingHints getRenderingHints() {
 704         return hints;
 705     }
 706 }