KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > java > awt > image > AffineTransformOp


1 /*
2  * @(#)AffineTransformOp.java 1.62 03/12/19
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 package java.awt.image;
9
10 import java.awt.geom.AffineTransform JavaDoc;
11 import java.awt.geom.NoninvertibleTransformException JavaDoc;
12 import java.awt.geom.Rectangle2D JavaDoc;
13 import java.awt.geom.Point2D JavaDoc;
14 import java.awt.AlphaComposite JavaDoc;
15 import java.awt.GraphicsEnvironment JavaDoc;
16 import java.awt.Rectangle JavaDoc;
17 import java.awt.RenderingHints JavaDoc;
18 import java.awt.Transparency JavaDoc;
19 import sun.awt.image.ImagingLib;
20
21 /**
22  * This class uses an affine transform to perform a linear mapping from
23  * 2D coordinates in the source image or <CODE>Raster</CODE> to 2D coordinates
24  * in the destination image or <CODE>Raster</CODE>.
25  * The type of interpolation that is used is specified through a constructor,
26  * either by a <CODE>RenderingHints</CODE> object or by one of the integer
27  * interpolation types defined in this class.
28  * <p>
29  * If a <CODE>RenderingHints</CODE> object is specified in the constructor, the
30  * interpolation hint and the rendering quality hint are used to set
31  * the interpolation type for this operation. The color rendering hint
32  * and the dithering hint can be used when color conversion is required.
33  * <p>
34  * Note that the following constraints have to be met:
35  * <ul>
36  * <li>The source and destination must be different.
37  * <li>For <CODE>Raster</CODE> objects, the number of bands in the source must
38  * be equal to the number of bands in the destination.
39  * </ul>
40  * @see AffineTransform
41  * @see BufferedImageFilter
42  * @see java.awt.RenderingHints#KEY_INTERPOLATION
43  * @see java.awt.RenderingHints#KEY_RENDERING
44  * @see java.awt.RenderingHints#KEY_COLOR_RENDERING
45  * @see java.awt.RenderingHints#KEY_DITHERING
46  * @version 16 Apr 1998
47  */

48 public class AffineTransformOp implements BufferedImageOp JavaDoc, RasterOp JavaDoc {
49     private AffineTransform JavaDoc xform;
50     RenderingHints JavaDoc hints;
51
52     /**
53      * Nearest-neighbor interpolation type.
54      */

55     public static final int TYPE_NEAREST_NEIGHBOR = 1;
56
57     /**
58      * Bilinear interpolation type.
59      */

60     public static final int TYPE_BILINEAR = 2;
61
62     /**
63      * Bicubic interpolation type.
64      */

65     public static final int TYPE_BICUBIC = 3;
66
67     int interpolationType = TYPE_NEAREST_NEIGHBOR;
68
69     /**
70      * Constructs an <CODE>AffineTransformOp</CODE> given an affine transform.
71      * The interpolation type is determined from the
72      * <CODE>RenderingHints</CODE> object. If the interpolation hint is
73      * defined, it will be used. Otherwise, if the rendering quality hint is
74      * defined, the interpolation type is determined from its value. If no
75      * hints are specified (<CODE>hints</CODE> is null),
76      * the interpolation type is {@link #TYPE_NEAREST_NEIGHBOR
77      * TYPE_NEAREST_NEIGHBOR}.
78      *
79      * @param xform The <CODE>AffineTransform</CODE> to use for the
80      * operation.
81      *
82      * @param hints The <CODE>RenderingHints</CODE> object used to specify
83      * the interpolation type for the operation.
84      *
85      * @throws ImagingOpException if the transform is non-invertible.
86      * @see java.awt.RenderingHints#KEY_INTERPOLATION
87      * @see java.awt.RenderingHints#KEY_RENDERING
88      */

89     public AffineTransformOp(AffineTransform JavaDoc xform, RenderingHints JavaDoc hints){
90         validateTransform(xform);
91         this.xform = (AffineTransform JavaDoc) xform.clone();
92         this.hints = hints;
93
94         if (hints != null) {
95             Object JavaDoc value = hints.get(hints.KEY_INTERPOLATION);
96             if (value == null) {
97                 value = hints.get(hints.KEY_RENDERING);
98                 if (value == hints.VALUE_RENDER_SPEED) {
99                     interpolationType = TYPE_NEAREST_NEIGHBOR;
100                 }
101                 else if (value == hints.VALUE_RENDER_QUALITY) {
102                     interpolationType = TYPE_BILINEAR;
103                 }
104             }
105             else if (value == hints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR) {
106                 interpolationType = TYPE_NEAREST_NEIGHBOR;
107             }
108             else if (value == hints.VALUE_INTERPOLATION_BILINEAR) {
109                 interpolationType = TYPE_BILINEAR;
110             }
111             else if (value == hints.VALUE_INTERPOLATION_BICUBIC) {
112                 interpolationType = TYPE_BICUBIC;
113             }
114         }
115         else {
116             interpolationType = TYPE_NEAREST_NEIGHBOR;
117         }
118     }
119
120     /**
121      * Constructs an <CODE>AffineTransformOp</CODE> given an affine transform
122      * and the interpolation type.
123      *
124      * @param xform The <CODE>AffineTransform</CODE> to use for the operation.
125      * @param interpolationType One of the integer
126      * interpolation type constants defined by this class:
127      * {@link #TYPE_NEAREST_NEIGHBOR TYPE_NEAREST_NEIGHBOR},
128      * {@link #TYPE_BILINEAR TYPE_BILINEAR},
129      * {@link #TYPE_BICUBIC TYPE_BICUBIC}.
130      * @throws ImagingOpException if the transform is non-invertible.
131      */

132     public AffineTransformOp(AffineTransform JavaDoc xform, int interpolationType) {
133         validateTransform(xform);
134         this.xform = (AffineTransform JavaDoc)xform.clone();
135         switch(interpolationType) {
136             case TYPE_NEAREST_NEIGHBOR:
137             case TYPE_BILINEAR:
138             case TYPE_BICUBIC:
139                 break;
140         default:
141             throw new IllegalArgumentException JavaDoc("Unknown interpolation type: "+
142                                                interpolationType);
143         }
144         this.interpolationType = interpolationType;
145     }
146
147     /**
148      * Returns the interpolation type used by this op.
149      * @return the interpolation type.
150      * @see #TYPE_NEAREST_NEIGHBOR
151      * @see #TYPE_BILINEAR
152      * @see #TYPE_BICUBIC
153      */

154     public final int getInterpolationType() {
155         return interpolationType;
156     }
157
158     /**
159      * Transforms the source <CODE>BufferedImage</CODE> and stores the results
160      * in the destination <CODE>BufferedImage</CODE>.
161      * If the color models for the two images do not match, a color
162      * conversion into the destination color model is performed.
163      * If the destination image is null,
164      * a <CODE>BufferedImage</CODE> is created with the source
165      * <CODE>ColorModel</CODE>.
166      * <p>
167      * The coordinates of the rectangle returned by
168      * <code>getBounds2D(BufferedImage)</code>
169      * are not necessarily the same as the coordinates of the
170      * <code>BufferedImage</code> returned by this method. If the
171      * upper-left corner coordinates of the rectangle are
172      * negative then this part of the rectangle is not drawn. If the
173      * upper-left corner coordinates of the rectangle are positive
174      * then the filtered image is drawn at that position in the
175      * destination <code>BufferedImage</code>.
176      * <p>
177      * An <CODE>IllegalArgumentException</CODE> is thrown if the source is
178      * the same as the destination.
179      *
180      * @param src The <CODE>BufferedImage</CODE> to transform.
181      * @param dst The <CODE>BufferedImage</CODE> in which to store the results
182      * of the transformation.
183      *
184      * @return The filtered <CODE>BufferedImage</CODE>.
185      * @throws IllegalArgumentException if <code>src</code> and
186      * <code>dst</code> are the same
187      * @throws ImagingOpException if the image cannot be transformed
188      * because of a data-processing error that might be
189      * caused by an invalid image format, tile format, or
190      * image-processing operation, or any other unsupported
191      * operation.
192      */

193     public final BufferedImage JavaDoc filter(BufferedImage JavaDoc src, BufferedImage JavaDoc dst) {
194         
195         if (src == null) {
196             throw new NullPointerException JavaDoc("src image is null");
197         }
198         if (src == dst) {
199             throw new IllegalArgumentException JavaDoc("src image cannot be the "+
200                                                "same as the dst image");
201         }
202
203         boolean needToConvert = false;
204         ColorModel JavaDoc srcCM = src.getColorModel();
205         ColorModel JavaDoc dstCM;
206         BufferedImage JavaDoc origDst = dst;
207         
208         if (dst == null) {
209             dst = createCompatibleDestImage(src, null);
210             dstCM = srcCM;
211             origDst = dst;
212         }
213         else {
214             dstCM = dst.getColorModel();
215             if (srcCM.getColorSpace().getType() !=
216                 dstCM.getColorSpace().getType())
217             {
218                 int type = xform.getType();
219                 boolean needTrans = ((type&
220                                       (xform.TYPE_MASK_ROTATION|
221                                        xform.TYPE_GENERAL_TRANSFORM))
222                                      != 0);
223                 if (! needTrans && type != xform.TYPE_TRANSLATION && type != xform.TYPE_IDENTITY)
224                 {
225                     double[] mtx = new double[4];
226                     xform.getMatrix(mtx);
227                     // Check out the matrix. A non-integral scale will force ARGB
228
// since the edge conditions can't be guaranteed.
229
needTrans = (mtx[0] != (int)mtx[0] || mtx[3] != (int)mtx[3]);
230                 }
231                 
232                 if (needTrans &&
233                     srcCM.getTransparency() == Transparency.OPAQUE)
234                 {
235                     // Need to convert first
236
ColorConvertOp JavaDoc ccop = new ColorConvertOp JavaDoc(hints);
237                     BufferedImage JavaDoc tmpSrc = null;
238                     int sw = src.getWidth();
239                     int sh = src.getHeight();
240                     if (dstCM.getTransparency() == Transparency.OPAQUE) {
241                         tmpSrc = new BufferedImage JavaDoc(sw, sh,
242                                                   BufferedImage.TYPE_INT_ARGB);
243                     }
244                     else {
245                         WritableRaster JavaDoc r =
246                             dstCM.createCompatibleWritableRaster(sw, sh);
247                         tmpSrc = new BufferedImage JavaDoc(dstCM, r,
248                                                   dstCM.isAlphaPremultiplied(),
249                                                   null);
250                     }
251                     src = ccop.filter(src, tmpSrc);
252                 }
253                 else {
254                     needToConvert = true;
255                     dst = createCompatibleDestImage(src, null);
256                 }
257             }
258
259         }
260         
261         if (interpolationType != TYPE_NEAREST_NEIGHBOR &&
262             dst.getColorModel() instanceof IndexColorModel JavaDoc) {
263             dst = new BufferedImage JavaDoc(dst.getWidth(), dst.getHeight(),
264                                     BufferedImage.TYPE_INT_ARGB);
265         }
266         if (ImagingLib.filter(this, src, dst) == null) {
267             throw new ImagingOpException JavaDoc ("Unable to transform src image");
268         }
269         
270         if (needToConvert) {
271             ColorConvertOp JavaDoc ccop = new ColorConvertOp JavaDoc(hints);
272             ccop.filter(dst, origDst);
273         }
274         else if (origDst != dst) {
275             java.awt.Graphics2D JavaDoc g = origDst.createGraphics();
276         try {
277                 g.setComposite(AlphaComposite.Src);
278             g.drawImage(dst, 0, 0, null);
279         } finally {
280             g.dispose();
281         }
282         }
283         
284         return origDst;
285     }
286
287     /**
288      * Transforms the source <CODE>Raster</CODE> and stores the results in
289      * the destination <CODE>Raster</CODE>. This operation performs the
290      * transform band by band.
291      * <p>
292      * If the destination <CODE>Raster</CODE> is null, a new
293      * <CODE>Raster</CODE> is created.
294      * An <CODE>IllegalArgumentException</CODE> may be thrown if the source is
295      * the same as the destination or if the number of bands in
296      * the source is not equal to the number of bands in the
297      * destination.
298      * <p>
299      * The coordinates of the rectangle returned by
300      * <code>getBounds2D(Raster)</code>
301      * are not necessarily the same as the coordinates of the
302      * <code>WritableRaster</code> returned by this method. If the
303      * upper-left corner coordinates of rectangle are negative then
304      * this part of the rectangle is not drawn. If the coordinates
305      * of the rectangle are positive then the filtered image is drawn at
306      * that position in the destination <code>Raster</code>.
307      * <p>
308      * @param src The <CODE>Raster</CODE> to transform.
309      * @param dst The <CODE>Raster</CODE> in which to store the results of the
310      * transformation.
311      *
312      * @return The transformed <CODE>Raster</CODE>.
313      *
314      * @throws ImagingOpException if the raster cannot be transformed
315      * because of a data-processing error that might be
316      * caused by an invalid image format, tile format, or
317      * image-processing operation, or any other unsupported
318      * operation.
319      */

320     public final WritableRaster JavaDoc filter(Raster JavaDoc src, WritableRaster JavaDoc dst) {
321         if (src == null) {
322             throw new NullPointerException JavaDoc("src image is null");
323         }
324         if (dst == null) {
325             dst = createCompatibleDestRaster(src);
326         }
327         if (src == dst) {
328             throw new IllegalArgumentException JavaDoc("src image cannot be the "+
329                                                "same as the dst image");
330         }
331         if (src.getNumBands() != dst.getNumBands()) {
332             throw new IllegalArgumentException JavaDoc("Number of src bands ("+
333                                                src.getNumBands()+
334                                                ") does not match number of "+
335                                                " dst bands ("+
336                                                dst.getNumBands()+")");
337         }
338
339         if (ImagingLib.filter(this, src, dst) == null) {
340             throw new ImagingOpException JavaDoc ("Unable to transform src image");
341         }
342         return dst;
343     }
344
345     /**
346      * Returns the bounding box of the transformed destination. The
347      * rectangle returned is the actual bounding box of the
348      * transformed points. The coordinates of the upper-left corner
349      * of the returned rectangle might not be (0,&nbsp;0).
350      *
351      * @param src The <CODE>BufferedImage</CODE> to be transformed.
352      *
353      * @return The <CODE>Rectangle2D</CODE> representing the destination's
354      * bounding box.
355      */

356     public final Rectangle2D JavaDoc getBounds2D (BufferedImage JavaDoc src) {
357         return getBounds2D(src.getRaster());
358     }
359
360     /**
361      * Returns the bounding box of the transformed destination. The
362      * rectangle returned will be the actual bounding box of the
363      * transformed points. The coordinates of the upper-left corner
364      * of the returned rectangle might not be (0,&nbsp;0).
365      *
366      * @param src The <CODE>Raster</CODE> to be transformed.
367      *
368      * @return The <CODE>Rectangle2D</CODE> representing the destination's
369      * bounding box.
370      */

371     public final Rectangle2D JavaDoc getBounds2D (Raster JavaDoc src) {
372         int w = src.getWidth();
373         int h = src.getHeight();
374
375         // Get the bounding box of the src and transform the corners
376
float[] pts = {0, 0, w, 0, w, h, 0, h};
377         xform.transform(pts, 0, pts, 0, 4);
378
379         // Get the min, max of the dst
380
float fmaxX = pts[0];
381         float fmaxY = pts[1];
382         float fminX = pts[0];
383         float fminY = pts[1];
384         int maxX;
385         int maxY;
386         for (int i=2; i < 8; i+=2) {
387             if (pts[i] > fmaxX) {
388                 fmaxX = pts[i];
389             }
390             else if (pts[i] < fminX) {
391                 fminX = pts[i];
392             }
393             if (pts[i+1] > fmaxY) {
394                 fmaxY = pts[i+1];
395             }
396             else if (pts[i+1] < fminY) {
397                 fminY = pts[i+1];
398             }
399         }
400
401         return new Rectangle2D.Float JavaDoc(fminX, fminY, fmaxX-fminX, fmaxY-fminY);
402     }
403
404     /**
405      * Creates a zeroed destination image with the correct size and number of
406      * bands. A <CODE>RasterFormatException</CODE> may be thrown if the
407      * transformed width or height is equal to 0.
408      * <p>
409      * If <CODE>destCM</CODE> is null,
410      * an appropriate <CODE>ColorModel</CODE> is used; this
411      * <CODE>ColorModel</CODE> may have
412      * an alpha channel even if the source <CODE>ColorModel</CODE> is opaque.
413      *
414      * @param src The <CODE>BufferedImage</CODE> to be transformed.
415      * @param destCM <CODE>ColorModel</CODE> of the destination. If null,
416      * an appropriate <CODE>ColorModel</CODE> is used.
417      *
418      * @return The zeroed destination image.
419      */

420     public BufferedImage JavaDoc createCompatibleDestImage (BufferedImage JavaDoc src,
421                                                     ColorModel JavaDoc destCM) {
422         BufferedImage JavaDoc image;
423         Rectangle JavaDoc r = getBounds2D(src).getBounds();
424
425         // If r.x (or r.y) is < 0, then we want to only create an image
426
// that is in the positive range.
427
// If r.x (or r.y) is > 0, then we need to create an image that
428
// includes the translation.
429
int w = r.x + r.width;
430         int h = r.y + r.height;
431         if (w <= 0) {
432             throw new RasterFormatException JavaDoc("Transformed width ("+w+
433                                             ") is less than or equal to 0.");
434         }
435         if (h <= 0) {
436             throw new RasterFormatException JavaDoc("Transformed height ("+h+
437                                             ") is less than or equal to 0.");
438         }
439         
440         if (destCM == null) {
441             ColorModel JavaDoc cm = src.getColorModel();
442             if (interpolationType != TYPE_NEAREST_NEIGHBOR &&
443                 (cm instanceof IndexColorModel JavaDoc ||
444                  cm.getTransparency() == Transparency.OPAQUE))
445             {
446                 image = new BufferedImage JavaDoc(w, h,
447                                           BufferedImage.TYPE_INT_ARGB);
448             }
449             else {
450                 image = new BufferedImage JavaDoc(cm,
451                           src.getRaster().createCompatibleWritableRaster(w,h),
452                           cm.isAlphaPremultiplied(), null);
453             }
454         }
455         else {
456             image = new BufferedImage JavaDoc(destCM,
457                                     destCM.createCompatibleWritableRaster(w,h),
458                                     destCM.isAlphaPremultiplied(), null);
459         }
460
461         return image;
462     }
463
464     /**
465      * Creates a zeroed destination <CODE>Raster</CODE> with the correct size
466      * and number of bands. A <CODE>RasterFormatException</CODE> may be thrown
467      * if the transformed width or height is equal to 0.
468      *
469      * @param src The <CODE>Raster</CODE> to be transformed.
470      *
471      * @return The zeroed destination <CODE>Raster</CODE>.
472      */

473     public WritableRaster JavaDoc createCompatibleDestRaster (Raster JavaDoc src) {
474         Rectangle2D JavaDoc r = getBounds2D(src);
475
476         return src.createCompatibleWritableRaster((int)r.getX(),
477                                                   (int)r.getY(),
478                                                   (int)r.getWidth(),
479                                                   (int)r.getHeight());
480     }
481
482     /**
483      * Returns the location of the corresponding destination point given a
484      * point in the source. If <CODE>dstPt</CODE> is specified, it
485      * is used to hold the return value.
486      *
487      * @param srcPt The <code>Point2D</code> that represents the source
488      * point.
489      * @param dstPt The <CODE>Point2D</CODE> in which to store the result.
490      *
491      * @return The <CODE>Point2D</CODE> in the destination that corresponds to
492      * the specified point in the source.
493      */

494     public final Point2D JavaDoc getPoint2D (Point2D JavaDoc srcPt, Point2D JavaDoc dstPt) {
495         return xform.transform (srcPt, dstPt);
496     }
497
498     /**
499      * Returns the affine transform used by this transform operation.
500      *
501      * @return The <CODE>AffineTransform</CODE> associated with this op.
502      */

503     public final AffineTransform JavaDoc getTransform() {
504         return (AffineTransform JavaDoc) xform.clone();
505     }
506
507     /**
508      * Returns the rendering hints used by this transform operation.
509      *
510      * @return The <CODE>RenderingHints</CODE> object associated with this op.
511      */

512     public final RenderingHints JavaDoc getRenderingHints() {
513         if (hints == null) {
514             Object JavaDoc val;
515             switch(interpolationType) {
516             case TYPE_NEAREST_NEIGHBOR:
517                 val = RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
518                 break;
519             case TYPE_BILINEAR:
520                 val = RenderingHints.VALUE_INTERPOLATION_BILINEAR;
521                 break;
522             case TYPE_BICUBIC:
523                 val = RenderingHints.VALUE_INTERPOLATION_BICUBIC;
524                 break;
525             default:
526                 // Should never get here
527
throw new InternalError JavaDoc("Unknown interpolation type "+
528                                          interpolationType);
529
530             }
531             hints = new RenderingHints JavaDoc(RenderingHints.KEY_INTERPOLATION, val);
532         }
533
534         return hints;
535     }
536
537     // We need to be able to invert the transform if we want to
538
// transform the image. If the determinant of the matrix is 0,
539
// then we can't invert the transform.
540
void validateTransform(AffineTransform JavaDoc xform) {
541         if (Math.abs(xform.getDeterminant()) <= Double.MIN_VALUE) {
542             throw new ImagingOpException JavaDoc("Unable to invert transform "+xform);
543         }
544     }
545 }
546
Popular Tags