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

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

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