2
|
1 |
/*
|
|
2 |
* Copyright 1997-2005 Sun Microsystems, Inc. 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. Sun designates this
|
|
8 |
* particular file as subject to the "Classpath" exception as provided
|
|
9 |
* by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
|
|
22 |
* CA 95054 USA or visit www.sun.com if you need additional information or
|
|
23 |
* have any 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, 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, 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 |
}
|