Source for java.awt.image.AffineTransformOp

   1: /* AffineTransformOp.java --  This class performs affine 
   2:    transformation between two images or rasters in 2 dimensions.
   3:    Copyright (C) 2004 Free Software Foundation
   4: 
   5: This file is part of GNU Classpath.
   6: 
   7: GNU Classpath is free software; you can redistribute it and/or modify
   8: it under the terms of the GNU General Public License as published by
   9: the Free Software Foundation; either version 2, or (at your option)
  10: any later version.
  11: 
  12: GNU Classpath is distributed in the hope that it will be useful, but
  13: WITHOUT ANY WARRANTY; without even the implied warranty of
  14: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  15: General Public License for more details.
  16: 
  17: You should have received a copy of the GNU General Public License
  18: along with GNU Classpath; see the file COPYING.  If not, write to the
  19: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  20: 02110-1301 USA.
  21: 
  22: Linking this library statically or dynamically with other modules is
  23: making a combined work based on this library.  Thus, the terms and
  24: conditions of the GNU General Public License cover the whole
  25: combination.
  26: 
  27: As a special exception, the copyright holders of this library give you
  28: permission to link this library with independent modules to produce an
  29: executable, regardless of the license terms of these independent
  30: modules, and to copy and distribute the resulting executable under
  31: terms of your choice, provided that you also meet, for each linked
  32: independent module, the terms and conditions of the license of that
  33: module.  An independent module is a module which is not derived from
  34: or based on this library.  If you modify this library, you may extend
  35: this exception to your version of the library, but you are not
  36: obligated to do so.  If you do not wish to do so, delete this
  37: exception statement from your version. */
  38: 
  39: package java.awt.image;
  40: 
  41: import java.awt.Graphics2D;
  42: import java.awt.Rectangle;
  43: import java.awt.RenderingHints;
  44: import java.awt.geom.AffineTransform;
  45: import java.awt.geom.NoninvertibleTransformException;
  46: import java.awt.geom.Point2D;
  47: import java.awt.geom.Rectangle2D;
  48: import java.util.Arrays;
  49: 
  50: /**
  51:  * This class performs affine transformation between two images or 
  52:  * rasters in 2 dimensions. 
  53:  *
  54:  * @author Olga Rodimina (rodimina@redhat.com) 
  55:  */
  56: public class AffineTransformOp implements BufferedImageOp, RasterOp
  57: {
  58:     public static final int TYPE_NEAREST_NEIGHBOR = 1;
  59:     
  60:     public static final int TYPE_BILINEAR = 2;
  61:     
  62:     /**
  63:      * @since 1.5.0
  64:      */
  65:     public static final int TYPE_BICUBIC = 3;
  66: 
  67:     private AffineTransform transform;
  68:     private RenderingHints hints;
  69:     
  70:     /**
  71:      * Construct AffineTransformOp with the given xform and interpolationType.
  72:      * Interpolation type can be TYPE_BILINEAR, TYPE_BICUBIC or
  73:      * TYPE_NEAREST_NEIGHBOR.
  74:      *
  75:      * @param xform AffineTransform that will applied to the source image 
  76:      * @param interpolationType type of interpolation used
  77:      */
  78:     public AffineTransformOp (AffineTransform xform, int interpolationType)
  79:     {
  80:       this.transform = xform;
  81:       if (xform.getDeterminant() == 0)
  82:         throw new ImagingOpException(null);
  83: 
  84:       switch (interpolationType)
  85:       {
  86:       case TYPE_BILINEAR:
  87:         hints = new RenderingHints (RenderingHints.KEY_INTERPOLATION, 
  88:                                     RenderingHints.VALUE_INTERPOLATION_BILINEAR);
  89:         break;
  90:       case TYPE_BICUBIC:
  91:         hints = new RenderingHints (RenderingHints.KEY_INTERPOLATION, 
  92:                     RenderingHints.VALUE_INTERPOLATION_BICUBIC);
  93:         break;
  94:       default:
  95:         hints = new RenderingHints (RenderingHints.KEY_INTERPOLATION,
  96:                                     RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
  97:       }
  98:     }
  99: 
 100:     /**
 101:      * Construct AffineTransformOp with the given xform and rendering hints.
 102:      * 
 103:      * @param xform AffineTransform that will applied to the source image
 104:      * @param hints rendering hints that will be used during transformation
 105:      */
 106:     public AffineTransformOp (AffineTransform xform, RenderingHints hints)
 107:     {
 108:       this.transform = xform;
 109:       this.hints = hints;
 110:       if (xform.getDeterminant() == 0)
 111:         throw new ImagingOpException(null);
 112:     }
 113: 
 114:     /**
 115:      * Creates empty BufferedImage with the size equal to that of the 
 116:      * transformed image and correct number of bands. The newly created 
 117:      * image is created with the specified ColorModel. 
 118:      * If the ColorModel is equal to null, then image is created 
 119:      * with the ColorModel of the source image.
 120:      *
 121:      * @param src source image
 122:      * @param destCM color model for the destination image
 123:      * @return new compatible destination image
 124:      */
 125:     public BufferedImage createCompatibleDestImage (BufferedImage src,
 126:                                                     ColorModel destCM)
 127:     {
 128: 
 129:       // if destCm is not specified, use color model of the source image
 130: 
 131:       if (destCM == null) 
 132:         destCM = src.getColorModel ();
 133: 
 134:       return new BufferedImage (destCM, 
 135:                                 createCompatibleDestRaster (src.getRaster ()),
 136:                                 src.isAlphaPremultiplied (),
 137:                                 null);                     
 138: 
 139:     }
 140: 
 141:     /**
 142:      * Creates empty WritableRaster with the size equal to the transformed 
 143:      * source raster and correct number of bands 
 144:      *
 145:      * @param src source raster
 146:      * @throws RasterFormatException if resulting width or height of raster is 0
 147:      * @return new compatible raster
 148:      */
 149:     public WritableRaster createCompatibleDestRaster (Raster src)
 150:     {
 151:       Rectangle rect = (Rectangle) getBounds2D (src);
 152:       
 153:       // throw RasterFormatException if resulting width or height of the
 154:       // transformed raster is 0
 155: 
 156:       if (rect.getWidth () == 0 || rect.getHeight () == 0) 
 157:         throw new RasterFormatException("width or height is 0");
 158: 
 159:       return src.createCompatibleWritableRaster ((int) rect.getWidth (), 
 160:                                                 (int) rect.getHeight ());
 161:     }
 162: 
 163:     /**
 164:      * Transforms source image using transform specified at the constructor.
 165:      * The resulting transformed image is stored in the destination image. 
 166:      *
 167:      * @param src source image
 168:      * @param dst destination image
 169:      * @return transformed source image
 170:      */
 171:     public final BufferedImage filter (BufferedImage src, BufferedImage dst)
 172:     {
 173: 
 174:       if (dst == src)
 175:         throw new IllegalArgumentException ("src image cannot be the same as the dst image");
 176: 
 177:       // If the destination image is null, then BufferedImage is 
 178:       // created with ColorModel of the source image
 179: 
 180:       if (dst == null)
 181:         dst = createCompatibleDestImage(src, src.getColorModel ());
 182: 
 183:       // FIXME: Must check if color models of src and dst images are the same.
 184:       // If it is not, then source image should be converted to color model
 185:       // of the destination image
 186: 
 187:       Graphics2D gr = (Graphics2D) dst.createGraphics ();
 188:       gr.setRenderingHints (hints);    
 189:       gr.drawImage (src, transform, null);
 190:       return dst;
 191: 
 192:     }
 193: 
 194:     /**
 195:      * Transforms source raster using transform specified at the constructor.
 196:      * The resulting raster is stored in the destination raster.
 197:      *
 198:      * @param src source raster
 199:      * @param dst destination raster
 200:      * @return transformed raster
 201:      */
 202:     public final WritableRaster filter (Raster src, WritableRaster dst)
 203:     {
 204:       if (dst == src)
 205:         throw new IllegalArgumentException("src image cannot be the same as"
 206:                        + " the dst image");
 207: 
 208:       if (dst == null)
 209:         dst = createCompatibleDestRaster(src);
 210: 
 211:       if (src.getNumBands() != dst.getNumBands())
 212:         throw new IllegalArgumentException("src and dst must have same number"
 213:                        + " of bands");
 214:       
 215:       double[] dpts = new double[dst.getWidth() * 2];
 216:       double[] pts = new double[dst.getWidth() * 2];
 217:       for (int x = 0; x < dst.getWidth(); x++)
 218:       {
 219:     dpts[2 * x] = x + dst.getMinX();
 220:     dpts[2 * x + 1] = x;
 221:       }
 222:       Rectangle srcbounds = src.getBounds();
 223:       if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR))
 224:       {
 225:     for (int y = dst.getMinY(); y < dst.getMinY() + dst.getHeight(); y++)
 226:       {
 227:         try {
 228:           transform.inverseTransform(dpts, 0, pts, 0, dst.getWidth() * 2);
 229:         } catch (NoninvertibleTransformException e) {
 230:           // Can't happen since the constructor traps this
 231:           e.printStackTrace();
 232:         }
 233:         
 234:         for (int x = 0; x < dst.getWidth(); x++)
 235:           {
 236:         if (!srcbounds.contains(pts[2 * x], pts[2 * x + 1]))
 237:           continue;
 238:         dst.setDataElements(x + dst.getMinX(), y,
 239:                     src.getDataElements((int)pts[2 * x],
 240:                             (int)pts[2 * x + 1],
 241:                             null));
 242:           }
 243:       }
 244:       }
 245:       else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BILINEAR))
 246:       {
 247:         double[] tmp = new double[4 * src.getNumBands()];
 248:         for (int y = dst.getMinY(); y < dst.getMinY() + dst.getHeight(); y++)
 249:         {
 250:           try {
 251:             transform.inverseTransform(dpts, 0, pts, 0, dst.getWidth() * 2);
 252:           } catch (NoninvertibleTransformException e) {
 253:             // Can't happen since the constructor traps this
 254:             e.printStackTrace();
 255:           }
 256:         
 257:           for (int x = 0; x < dst.getWidth(); x++)
 258:           {
 259:             if (!srcbounds.contains(pts[2 * x], pts[2 * x + 1]))
 260:               continue;
 261:             int xx = (int)pts[2 * x];
 262:             int yy = (int)pts[2 * x + 1];
 263:             double dx = (pts[2 * x] - xx);
 264:             double dy = (pts[2 * x + 1] - yy);
 265:         
 266:             // TODO write this more intelligently
 267:             if (xx == src.getMinX() + src.getWidth() - 1 ||
 268:                 yy == src.getMinY() + src.getHeight() - 1)
 269:             {
 270:               // bottom or right edge
 271:               Arrays.fill(tmp, 0);
 272:               src.getPixel(xx, yy, tmp);
 273:             }
 274:             else
 275:         {
 276:               // Normal case
 277:               src.getPixels(xx, yy, 2, 2, tmp);
 278:           for (int b = 0; b < src.getNumBands(); b++)
 279:         tmp[b] = dx * dy * tmp[b]
 280:           + (1 - dx) * dy * tmp[b + src.getNumBands()]
 281:           + dx * (1 - dy) * tmp[b + 2 * src.getNumBands()]
 282:           + (1 - dx) * (1 - dy) * tmp[b + 3 * src.getNumBands()];
 283:         }
 284:             dst.setPixel(x, y, tmp);
 285:           }
 286:         }
 287:       }
 288:       else
 289:       {
 290:         // Bicubic
 291:         throw new UnsupportedOperationException("not implemented yet");
 292:       }
 293:       
 294:       return dst;  
 295:     }
 296: 
 297:     /**
 298:      * Transforms source image using transform specified at the constructor and 
 299:      * returns bounds of the transformed image.
 300:      *
 301:      * @param src image to be transformed
 302:      * @return bounds of the transformed image.
 303:      */
 304:     public final Rectangle2D getBounds2D (BufferedImage src)
 305:     {
 306:       return getBounds2D (src.getRaster());
 307:     }
 308:    
 309:     /**
 310:      * Returns bounds of the transformed raster.
 311:      *
 312:      * @param src raster to be transformed
 313:      * @return bounds of the transformed raster.
 314:      */
 315:     public final Rectangle2D getBounds2D (Raster src)
 316:     {
 317:       // determine new size for the transformed raster.
 318:       // Need to calculate transformed coordinates of the lower right
 319:       // corner of the raster. The upper left corner is always (0,0)
 320:               
 321:       double x2 = (double) src.getWidth () + src.getMinX ();
 322:       double y2 = (double) src.getHeight () + src.getMinY ();
 323:       Point2D p2 = getPoint2D (new Point2D.Double (x2,y2), null);
 324: 
 325:       Rectangle2D rect = new Rectangle (0, 0, (int) p2.getX (), (int) p2.getY ());
 326:       return rect.getBounds ();
 327:     }
 328: 
 329:     /**
 330:      * Returns interpolation type used during transformations
 331:      *
 332:      * @return interpolation type
 333:      */
 334:     public final int getInterpolationType ()
 335:     {
 336:       if(hints.containsValue (RenderingHints.VALUE_INTERPOLATION_BILINEAR))
 337:         return TYPE_BILINEAR;
 338:       else 
 339:         return TYPE_NEAREST_NEIGHBOR;
 340:     }
 341: 
 342:     /** 
 343:      * Returns location of the transformed source point. The resulting point 
 344:      * is stored in the dstPt if one is specified.
 345:      *  
 346:      * @param srcPt point to be transformed
 347:      * @param dstPt destination point
 348:      * @return the location of the transformed source point.
 349:      */
 350:     public Point2D getPoint2D (Point2D srcPt, Point2D dstPt)
 351:     {
 352:       return transform.transform (srcPt, dstPt);
 353:     }
 354: 
 355:     /**
 356:      * Returns rendering hints that are used during transformation.
 357:      *
 358:      * @return rendering hints
 359:      */
 360:     public final RenderingHints getRenderingHints ()
 361:     {
 362:       return hints;
 363:     }
 364: 
 365:     /**
 366:      * Returns transform used in transformation between source and destination
 367:      * image.
 368:      *
 369:      * @return transform
 370:      */
 371:     public final AffineTransform getTransform ()
 372:     {
 373:       return transform;
 374:     }
 375: }