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