src/java.desktop/share/classes/java/awt/image/AffineTransformOp.java
changeset 47216 71c04702a3d5
parent 35667 ed476aba94de
equal deleted inserted replaced
47215:4ebc2e2fb97c 47216:71c04702a3d5
       
     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.geom.AffineTransform;
       
    29 import java.awt.geom.NoninvertibleTransformException;
       
    30 import java.awt.geom.Rectangle2D;
       
    31 import java.awt.geom.Point2D;
       
    32 import java.awt.AlphaComposite;
       
    33 import java.awt.GraphicsEnvironment;
       
    34 import java.awt.Rectangle;
       
    35 import java.awt.RenderingHints;
       
    36 import java.awt.Transparency;
       
    37 import java.lang.annotation.Native;
       
    38 import sun.awt.image.ImagingLib;
       
    39 
       
    40 /**
       
    41  * This class uses an affine transform to perform a linear mapping from
       
    42  * 2D coordinates in the source image or {@code Raster} to 2D coordinates
       
    43  * in the destination image or {@code Raster}.
       
    44  * The type of interpolation that is used is specified through a constructor,
       
    45  * either by a {@code RenderingHints} object or by one of the integer
       
    46  * interpolation types defined in this class.
       
    47  * <p>
       
    48  * If a {@code RenderingHints} object is specified in the constructor, the
       
    49  * interpolation hint and the rendering quality hint are used to set
       
    50  * the interpolation type for this operation.  The color rendering hint
       
    51  * and the dithering hint can be used when color conversion is required.
       
    52  * <p>
       
    53  * Note that the following constraints have to be met:
       
    54  * <ul>
       
    55  * <li>The source and destination must be different.
       
    56  * <li>For {@code Raster} objects, the number of bands in the source must
       
    57  * be equal to the number of bands in the destination.
       
    58  * </ul>
       
    59  * @see AffineTransform
       
    60  * @see BufferedImageFilter
       
    61  * @see java.awt.RenderingHints#KEY_INTERPOLATION
       
    62  * @see java.awt.RenderingHints#KEY_RENDERING
       
    63  * @see java.awt.RenderingHints#KEY_COLOR_RENDERING
       
    64  * @see java.awt.RenderingHints#KEY_DITHERING
       
    65  */
       
    66 public class AffineTransformOp implements BufferedImageOp, RasterOp {
       
    67     private AffineTransform xform;
       
    68     RenderingHints hints;
       
    69 
       
    70     /**
       
    71      * Nearest-neighbor interpolation type.
       
    72      */
       
    73     @Native public static final int TYPE_NEAREST_NEIGHBOR = 1;
       
    74 
       
    75     /**
       
    76      * Bilinear interpolation type.
       
    77      */
       
    78     @Native public static final int TYPE_BILINEAR = 2;
       
    79 
       
    80     /**
       
    81      * Bicubic interpolation type.
       
    82      */
       
    83     @Native public static final int TYPE_BICUBIC = 3;
       
    84 
       
    85     int interpolationType = TYPE_NEAREST_NEIGHBOR;
       
    86 
       
    87     /**
       
    88      * Constructs an {@code AffineTransformOp} given an affine transform.
       
    89      * The interpolation type is determined from the
       
    90      * {@code RenderingHints} object.  If the interpolation hint is
       
    91      * defined, it will be used. Otherwise, if the rendering quality hint is
       
    92      * defined, the interpolation type is determined from its value.  If no
       
    93      * hints are specified ({@code hints} is null),
       
    94      * the interpolation type is {@link #TYPE_NEAREST_NEIGHBOR
       
    95      * TYPE_NEAREST_NEIGHBOR}.
       
    96      *
       
    97      * @param xform The {@code AffineTransform} to use for the
       
    98      * operation.
       
    99      *
       
   100      * @param hints The {@code RenderingHints} object used to specify
       
   101      * the interpolation type for the operation.
       
   102      *
       
   103      * @throws ImagingOpException if the transform is non-invertible.
       
   104      * @see java.awt.RenderingHints#KEY_INTERPOLATION
       
   105      * @see java.awt.RenderingHints#KEY_RENDERING
       
   106      */
       
   107     public AffineTransformOp(AffineTransform xform, RenderingHints hints){
       
   108         validateTransform(xform);
       
   109         this.xform = (AffineTransform) xform.clone();
       
   110         this.hints = hints;
       
   111 
       
   112         if (hints != null) {
       
   113             Object value = hints.get(RenderingHints.KEY_INTERPOLATION);
       
   114             if (value == null) {
       
   115                 value = hints.get(RenderingHints.KEY_RENDERING);
       
   116                 if (value == RenderingHints.VALUE_RENDER_SPEED) {
       
   117                     interpolationType = TYPE_NEAREST_NEIGHBOR;
       
   118                 }
       
   119                 else if (value == RenderingHints.VALUE_RENDER_QUALITY) {
       
   120                     interpolationType = TYPE_BILINEAR;
       
   121                 }
       
   122             }
       
   123             else if (value == RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR) {
       
   124                 interpolationType = TYPE_NEAREST_NEIGHBOR;
       
   125             }
       
   126             else if (value == RenderingHints.VALUE_INTERPOLATION_BILINEAR) {
       
   127                 interpolationType = TYPE_BILINEAR;
       
   128             }
       
   129             else if (value == RenderingHints.VALUE_INTERPOLATION_BICUBIC) {
       
   130                 interpolationType = TYPE_BICUBIC;
       
   131             }
       
   132         }
       
   133         else {
       
   134             interpolationType = TYPE_NEAREST_NEIGHBOR;
       
   135         }
       
   136     }
       
   137 
       
   138     /**
       
   139      * Constructs an {@code AffineTransformOp} given an affine transform
       
   140      * and the interpolation type.
       
   141      *
       
   142      * @param xform The {@code AffineTransform} to use for the operation.
       
   143      * @param interpolationType One of the integer
       
   144      * interpolation type constants defined by this class:
       
   145      * {@link #TYPE_NEAREST_NEIGHBOR TYPE_NEAREST_NEIGHBOR},
       
   146      * {@link #TYPE_BILINEAR TYPE_BILINEAR},
       
   147      * {@link #TYPE_BICUBIC TYPE_BICUBIC}.
       
   148      * @throws ImagingOpException if the transform is non-invertible.
       
   149      */
       
   150     public AffineTransformOp(AffineTransform xform, int interpolationType) {
       
   151         validateTransform(xform);
       
   152         this.xform = (AffineTransform)xform.clone();
       
   153         switch(interpolationType) {
       
   154             case TYPE_NEAREST_NEIGHBOR:
       
   155             case TYPE_BILINEAR:
       
   156             case TYPE_BICUBIC:
       
   157                 break;
       
   158         default:
       
   159             throw new IllegalArgumentException("Unknown interpolation type: "+
       
   160                                                interpolationType);
       
   161         }
       
   162         this.interpolationType = interpolationType;
       
   163     }
       
   164 
       
   165     /**
       
   166      * Returns the interpolation type used by this op.
       
   167      * @return the interpolation type.
       
   168      * @see #TYPE_NEAREST_NEIGHBOR
       
   169      * @see #TYPE_BILINEAR
       
   170      * @see #TYPE_BICUBIC
       
   171      */
       
   172     public final int getInterpolationType() {
       
   173         return interpolationType;
       
   174     }
       
   175 
       
   176     /**
       
   177      * Transforms the source {@code BufferedImage} and stores the results
       
   178      * in the destination {@code BufferedImage}.
       
   179      * If the color models for the two images do not match, a color
       
   180      * conversion into the destination color model is performed.
       
   181      * If the destination image is null,
       
   182      * a {@code BufferedImage} is created with the source
       
   183      * {@code ColorModel}.
       
   184      * <p>
       
   185      * The coordinates of the rectangle returned by
       
   186      * {@code getBounds2D(BufferedImage)}
       
   187      * are not necessarily the same as the coordinates of the
       
   188      * {@code BufferedImage} returned by this method.  If the
       
   189      * upper-left corner coordinates of the rectangle are
       
   190      * negative then this part of the rectangle is not drawn.  If the
       
   191      * upper-left corner coordinates of the  rectangle are positive
       
   192      * then the filtered image is drawn at that position in the
       
   193      * destination {@code BufferedImage}.
       
   194      * <p>
       
   195      * An {@code IllegalArgumentException} is thrown if the source is
       
   196      * the same as the destination.
       
   197      *
       
   198      * @param src The {@code BufferedImage} to transform.
       
   199      * @param dst The {@code BufferedImage} in which to store the results
       
   200      * of the transformation.
       
   201      *
       
   202      * @return The filtered {@code BufferedImage}.
       
   203      * @throws IllegalArgumentException if {@code src} and
       
   204      *         {@code dst} are the same
       
   205      * @throws ImagingOpException if the image cannot be transformed
       
   206      *         because of a data-processing error that might be
       
   207      *         caused by an invalid image format, tile format, or
       
   208      *         image-processing operation, or any other unsupported
       
   209      *         operation.
       
   210      */
       
   211     public final BufferedImage filter(BufferedImage src, BufferedImage dst) {
       
   212 
       
   213         if (src == null) {
       
   214             throw new NullPointerException("src image is null");
       
   215         }
       
   216         if (src == dst) {
       
   217             throw new IllegalArgumentException("src image cannot be the "+
       
   218                                                "same as the dst image");
       
   219         }
       
   220 
       
   221         boolean needToConvert = false;
       
   222         ColorModel srcCM = src.getColorModel();
       
   223         ColorModel dstCM;
       
   224         BufferedImage origDst = dst;
       
   225 
       
   226         if (dst == null) {
       
   227             dst = createCompatibleDestImage(src, null);
       
   228             dstCM = srcCM;
       
   229             origDst = dst;
       
   230         }
       
   231         else {
       
   232             dstCM = dst.getColorModel();
       
   233             if (srcCM.getColorSpace().getType() !=
       
   234                 dstCM.getColorSpace().getType())
       
   235             {
       
   236                 int type = xform.getType();
       
   237                 boolean needTrans = ((type&
       
   238                                       (AffineTransform.TYPE_MASK_ROTATION|
       
   239                                        AffineTransform.TYPE_GENERAL_TRANSFORM))
       
   240                                      != 0);
       
   241                 if (! needTrans &&
       
   242                     type != AffineTransform.TYPE_TRANSLATION &&
       
   243                     type != AffineTransform.TYPE_IDENTITY)
       
   244                 {
       
   245                     double[] mtx = new double[4];
       
   246                     xform.getMatrix(mtx);
       
   247                     // Check out the matrix.  A non-integral scale will force ARGB
       
   248                     // since the edge conditions can't be guaranteed.
       
   249                     needTrans = (mtx[0] != (int)mtx[0] || mtx[3] != (int)mtx[3]);
       
   250                 }
       
   251 
       
   252                 if (needTrans &&
       
   253                     srcCM.getTransparency() == Transparency.OPAQUE)
       
   254                 {
       
   255                     // Need to convert first
       
   256                     ColorConvertOp ccop = new ColorConvertOp(hints);
       
   257                     BufferedImage tmpSrc = null;
       
   258                     int sw = src.getWidth();
       
   259                     int sh = src.getHeight();
       
   260                     if (dstCM.getTransparency() == Transparency.OPAQUE) {
       
   261                         tmpSrc = new BufferedImage(sw, sh,
       
   262                                                   BufferedImage.TYPE_INT_ARGB);
       
   263                     }
       
   264                     else {
       
   265                         WritableRaster r =
       
   266                             dstCM.createCompatibleWritableRaster(sw, sh);
       
   267                         tmpSrc = new BufferedImage(dstCM, r,
       
   268                                                   dstCM.isAlphaPremultiplied(),
       
   269                                                   null);
       
   270                     }
       
   271                     src = ccop.filter(src, tmpSrc);
       
   272                 }
       
   273                 else {
       
   274                     needToConvert = true;
       
   275                     dst = createCompatibleDestImage(src, null);
       
   276                 }
       
   277             }
       
   278 
       
   279         }
       
   280 
       
   281         if (interpolationType != TYPE_NEAREST_NEIGHBOR &&
       
   282             dst.getColorModel() instanceof IndexColorModel) {
       
   283             dst = new BufferedImage(dst.getWidth(), dst.getHeight(),
       
   284                                     BufferedImage.TYPE_INT_ARGB);
       
   285         }
       
   286         if (ImagingLib.filter(this, src, dst) == null) {
       
   287             throw new ImagingOpException ("Unable to transform src image");
       
   288         }
       
   289 
       
   290         if (needToConvert) {
       
   291             ColorConvertOp ccop = new ColorConvertOp(hints);
       
   292             ccop.filter(dst, origDst);
       
   293         }
       
   294         else if (origDst != dst) {
       
   295             java.awt.Graphics2D g = origDst.createGraphics();
       
   296             try {
       
   297                 g.setComposite(AlphaComposite.Src);
       
   298                 g.drawImage(dst, 0, 0, null);
       
   299             } finally {
       
   300                 g.dispose();
       
   301             }
       
   302         }
       
   303 
       
   304         return origDst;
       
   305     }
       
   306 
       
   307     /**
       
   308      * Transforms the source {@code Raster} and stores the results in
       
   309      * the destination {@code Raster}.  This operation performs the
       
   310      * transform band by band.
       
   311      * <p>
       
   312      * If the destination {@code Raster} is null, a new
       
   313      * {@code Raster} is created.
       
   314      * An {@code IllegalArgumentException} may be thrown if the source is
       
   315      * the same as the destination or if the number of bands in
       
   316      * the source is not equal to the number of bands in the
       
   317      * destination.
       
   318      * <p>
       
   319      * The coordinates of the rectangle returned by
       
   320      * {@code getBounds2D(Raster)}
       
   321      * are not necessarily the same as the coordinates of the
       
   322      * {@code WritableRaster} returned by this method.  If the
       
   323      * upper-left corner coordinates of rectangle are negative then
       
   324      * this part of the rectangle is not drawn.  If the coordinates
       
   325      * of the rectangle are positive then the filtered image is drawn at
       
   326      * that position in the destination {@code Raster}.
       
   327      *
       
   328      * @param src The {@code Raster} to transform.
       
   329      * @param dst The {@code Raster} in which to store the results of the
       
   330      * transformation.
       
   331      *
       
   332      * @return The transformed {@code Raster}.
       
   333      *
       
   334      * @throws ImagingOpException if the raster cannot be transformed
       
   335      *         because of a data-processing error that might be
       
   336      *         caused by an invalid image format, tile format, or
       
   337      *         image-processing operation, or any other unsupported
       
   338      *         operation.
       
   339      */
       
   340     public final WritableRaster filter(Raster src, WritableRaster dst) {
       
   341         if (src == null) {
       
   342             throw new NullPointerException("src image is null");
       
   343         }
       
   344         if (dst == null) {
       
   345             dst = createCompatibleDestRaster(src);
       
   346         }
       
   347         if (src == dst) {
       
   348             throw new IllegalArgumentException("src image cannot be the "+
       
   349                                                "same as the dst image");
       
   350         }
       
   351         if (src.getNumBands() != dst.getNumBands()) {
       
   352             throw new IllegalArgumentException("Number of src bands ("+
       
   353                                                src.getNumBands()+
       
   354                                                ") does not match number of "+
       
   355                                                " dst bands ("+
       
   356                                                dst.getNumBands()+")");
       
   357         }
       
   358 
       
   359         if (ImagingLib.filter(this, src, dst) == null) {
       
   360             throw new ImagingOpException ("Unable to transform src image");
       
   361         }
       
   362         return dst;
       
   363     }
       
   364 
       
   365     /**
       
   366      * Returns the bounding box of the transformed destination.  The
       
   367      * rectangle returned is the actual bounding box of the
       
   368      * transformed points.  The coordinates of the upper-left corner
       
   369      * of the returned rectangle might not be (0,&nbsp;0).
       
   370      *
       
   371      * @param src The {@code BufferedImage} to be transformed.
       
   372      *
       
   373      * @return The {@code Rectangle2D} representing the destination's
       
   374      * bounding box.
       
   375      */
       
   376     public final Rectangle2D getBounds2D (BufferedImage src) {
       
   377         return getBounds2D(src.getRaster());
       
   378     }
       
   379 
       
   380     /**
       
   381      * Returns the bounding box of the transformed destination.  The
       
   382      * rectangle returned will be the actual bounding box of the
       
   383      * transformed points.  The coordinates of the upper-left corner
       
   384      * of the returned rectangle might not be (0,&nbsp;0).
       
   385      *
       
   386      * @param src The {@code Raster} to be transformed.
       
   387      *
       
   388      * @return The {@code Rectangle2D} representing the destination's
       
   389      * bounding box.
       
   390      */
       
   391     public final Rectangle2D getBounds2D (Raster src) {
       
   392         int w = src.getWidth();
       
   393         int h = src.getHeight();
       
   394 
       
   395         // Get the bounding box of the src and transform the corners
       
   396         float[] pts = {0, 0, w, 0, w, h, 0, h};
       
   397         xform.transform(pts, 0, pts, 0, 4);
       
   398 
       
   399         // Get the min, max of the dst
       
   400         float fmaxX = pts[0];
       
   401         float fmaxY = pts[1];
       
   402         float fminX = pts[0];
       
   403         float fminY = pts[1];
       
   404         for (int i=2; i < 8; i+=2) {
       
   405             if (pts[i] > fmaxX) {
       
   406                 fmaxX = pts[i];
       
   407             }
       
   408             else if (pts[i] < fminX) {
       
   409                 fminX = pts[i];
       
   410             }
       
   411             if (pts[i+1] > fmaxY) {
       
   412                 fmaxY = pts[i+1];
       
   413             }
       
   414             else if (pts[i+1] < fminY) {
       
   415                 fminY = pts[i+1];
       
   416             }
       
   417         }
       
   418 
       
   419         return new Rectangle2D.Float(fminX, fminY, fmaxX-fminX, fmaxY-fminY);
       
   420     }
       
   421 
       
   422     /**
       
   423      * Creates a zeroed destination image with the correct size and number of
       
   424      * bands.  A {@code RasterFormatException} may be thrown if the
       
   425      * transformed width or height is equal to 0.
       
   426      * <p>
       
   427      * If {@code destCM} is null,
       
   428      * an appropriate {@code ColorModel} is used; this
       
   429      * {@code ColorModel} may have
       
   430      * an alpha channel even if the source {@code ColorModel} is opaque.
       
   431      *
       
   432      * @param src  The {@code BufferedImage} to be transformed.
       
   433      * @param destCM  {@code ColorModel} of the destination.  If null,
       
   434      * an appropriate {@code ColorModel} is used.
       
   435      *
       
   436      * @return The zeroed destination image.
       
   437      */
       
   438     public BufferedImage createCompatibleDestImage (BufferedImage src,
       
   439                                                     ColorModel destCM) {
       
   440         BufferedImage image;
       
   441         Rectangle r = getBounds2D(src).getBounds();
       
   442 
       
   443         // If r.x (or r.y) is < 0, then we want to only create an image
       
   444         // that is in the positive range.
       
   445         // If r.x (or r.y) is > 0, then we need to create an image that
       
   446         // includes the translation.
       
   447         int w = r.x + r.width;
       
   448         int h = r.y + r.height;
       
   449         if (w <= 0) {
       
   450             throw new RasterFormatException("Transformed width ("+w+
       
   451                                             ") is less than or equal to 0.");
       
   452         }
       
   453         if (h <= 0) {
       
   454             throw new RasterFormatException("Transformed height ("+h+
       
   455                                             ") is less than or equal to 0.");
       
   456         }
       
   457 
       
   458         if (destCM == null) {
       
   459             ColorModel cm = src.getColorModel();
       
   460             if (interpolationType != TYPE_NEAREST_NEIGHBOR &&
       
   461                 (cm instanceof IndexColorModel ||
       
   462                  cm.getTransparency() == Transparency.OPAQUE))
       
   463             {
       
   464                 image = new BufferedImage(w, h,
       
   465                                           BufferedImage.TYPE_INT_ARGB);
       
   466             }
       
   467             else {
       
   468                 image = new BufferedImage(cm,
       
   469                           src.getRaster().createCompatibleWritableRaster(w,h),
       
   470                           cm.isAlphaPremultiplied(), null);
       
   471             }
       
   472         }
       
   473         else {
       
   474             image = new BufferedImage(destCM,
       
   475                                     destCM.createCompatibleWritableRaster(w,h),
       
   476                                     destCM.isAlphaPremultiplied(), null);
       
   477         }
       
   478 
       
   479         return image;
       
   480     }
       
   481 
       
   482     /**
       
   483      * Creates a zeroed destination {@code Raster} with the correct size
       
   484      * and number of bands.  A {@code RasterFormatException} may be thrown
       
   485      * if the transformed width or height is equal to 0.
       
   486      *
       
   487      * @param src The {@code Raster} to be transformed.
       
   488      *
       
   489      * @return The zeroed destination {@code Raster}.
       
   490      */
       
   491     public WritableRaster createCompatibleDestRaster (Raster src) {
       
   492         Rectangle2D r = getBounds2D(src);
       
   493 
       
   494         return src.createCompatibleWritableRaster((int)r.getX(),
       
   495                                                   (int)r.getY(),
       
   496                                                   (int)r.getWidth(),
       
   497                                                   (int)r.getHeight());
       
   498     }
       
   499 
       
   500     /**
       
   501      * Returns the location of the corresponding destination point given a
       
   502      * point in the source.  If {@code dstPt} is specified, it
       
   503      * is used to hold the return value.
       
   504      *
       
   505      * @param srcPt The {@code Point2D} that represents the source
       
   506      *              point.
       
   507      * @param dstPt The {@code Point2D} in which to store the result.
       
   508      *
       
   509      * @return The {@code Point2D} in the destination that corresponds to
       
   510      * the specified point in the source.
       
   511      */
       
   512     public final Point2D getPoint2D (Point2D srcPt, Point2D dstPt) {
       
   513         return xform.transform (srcPt, dstPt);
       
   514     }
       
   515 
       
   516     /**
       
   517      * Returns the affine transform used by this transform operation.
       
   518      *
       
   519      * @return The {@code AffineTransform} associated with this op.
       
   520      */
       
   521     public final AffineTransform getTransform() {
       
   522         return (AffineTransform) xform.clone();
       
   523     }
       
   524 
       
   525     /**
       
   526      * Returns the rendering hints used by this transform operation.
       
   527      *
       
   528      * @return The {@code RenderingHints} object associated with this op.
       
   529      */
       
   530     public final RenderingHints getRenderingHints() {
       
   531         if (hints == null) {
       
   532             Object val;
       
   533             switch(interpolationType) {
       
   534             case TYPE_NEAREST_NEIGHBOR:
       
   535                 val = RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
       
   536                 break;
       
   537             case TYPE_BILINEAR:
       
   538                 val = RenderingHints.VALUE_INTERPOLATION_BILINEAR;
       
   539                 break;
       
   540             case TYPE_BICUBIC:
       
   541                 val = RenderingHints.VALUE_INTERPOLATION_BICUBIC;
       
   542                 break;
       
   543             default:
       
   544                 // Should never get here
       
   545                 throw new InternalError("Unknown interpolation type "+
       
   546                                          interpolationType);
       
   547 
       
   548             }
       
   549             hints = new RenderingHints(RenderingHints.KEY_INTERPOLATION, val);
       
   550         }
       
   551 
       
   552         return hints;
       
   553     }
       
   554 
       
   555     // We need to be able to invert the transform if we want to
       
   556     // transform the image.  If the determinant of the matrix is 0,
       
   557     // then we can't invert the transform.
       
   558     void validateTransform(AffineTransform xform) {
       
   559         if (Math.abs(xform.getDeterminant()) <= Double.MIN_VALUE) {
       
   560             throw new ImagingOpException("Unable to invert transform "+xform);
       
   561         }
       
   562     }
       
   563 }