42 |
import java.awt.Rectangle; |
import java.awt.Rectangle; |
43 |
import java.awt.RenderingHints; |
import java.awt.RenderingHints; |
44 |
import java.awt.geom.AffineTransform; |
import java.awt.geom.AffineTransform; |
45 |
|
import java.awt.geom.NoninvertibleTransformException; |
46 |
import java.awt.geom.Point2D; |
import java.awt.geom.Point2D; |
47 |
import java.awt.geom.Rectangle2D; |
import java.awt.geom.Rectangle2D; |
48 |
|
import java.util.Arrays; |
49 |
|
|
50 |
/** |
/** |
51 |
* This class performs affine transformation between two images or |
* This class performs affine transformation between two images or |
52 |
* rasters in 2 dimensions. |
* rasters in 2 dimensions. |
53 |
* |
* |
54 |
* @author Olga Rodimina <rodimina@redhat.com> |
* @author Olga Rodimina (rodimina@redhat.com) |
55 |
*/ |
*/ |
|
|
|
56 |
public class AffineTransformOp implements BufferedImageOp, RasterOp |
public class AffineTransformOp implements BufferedImageOp, RasterOp |
57 |
{ |
{ |
58 |
public static final int TYPE_NEAREST_NEIGHBOR = 1; |
public static final int TYPE_NEAREST_NEIGHBOR = 1; |
59 |
|
|
60 |
public static final int TYPE_BILINEAR = 2; |
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; |
private AffineTransform transform; |
68 |
private RenderingHints hints; |
private RenderingHints hints; |
69 |
|
|
70 |
/** |
/** |
71 |
* Construct AffineTransformOp with the given xform and interpolationType. |
* Construct AffineTransformOp with the given xform and interpolationType. |
72 |
* Interpolation type can be either TYPE_BILINEAR or TYPE_NEAREST_NEIGHBOR. |
* 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 |
* @param xform AffineTransform that will applied to the source image |
76 |
* @param interpolationType type of interpolation used |
* @param interpolationType type of interpolation used |
78 |
public AffineTransformOp (AffineTransform xform, int interpolationType) |
public AffineTransformOp (AffineTransform xform, int interpolationType) |
79 |
{ |
{ |
80 |
this.transform = xform; |
this.transform = xform; |
81 |
|
if (xform.getDeterminant() == 0) |
82 |
|
throw new ImagingOpException(null); |
83 |
|
|
84 |
if (interpolationType == 0) |
switch (interpolationType) |
85 |
|
{ |
86 |
|
case TYPE_BILINEAR: |
87 |
hints = new RenderingHints (RenderingHints.KEY_INTERPOLATION, |
hints = new RenderingHints (RenderingHints.KEY_INTERPOLATION, |
88 |
RenderingHints.VALUE_INTERPOLATION_BILINEAR); |
RenderingHints.VALUE_INTERPOLATION_BILINEAR); |
89 |
|
break; |
90 |
else |
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, |
hints = new RenderingHints (RenderingHints.KEY_INTERPOLATION, |
96 |
RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); |
RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); |
97 |
|
} |
98 |
} |
} |
99 |
|
|
100 |
/** |
/** |
107 |
{ |
{ |
108 |
this.transform = xform; |
this.transform = xform; |
109 |
this.hints = hints; |
this.hints = hints; |
110 |
|
if (xform.getDeterminant() == 0) |
111 |
|
throw new ImagingOpException(null); |
112 |
} |
} |
113 |
|
|
114 |
/** |
/** |
168 |
* @param dst destination image |
* @param dst destination image |
169 |
* @return transformed source image |
* @return transformed source image |
170 |
*/ |
*/ |
171 |
public BufferedImage filter (BufferedImage src, BufferedImage dst) |
public final BufferedImage filter (BufferedImage src, BufferedImage dst) |
172 |
{ |
{ |
173 |
|
|
174 |
if (dst == src) |
if (dst == src) |
199 |
* @param dst destination raster |
* @param dst destination raster |
200 |
* @return transformed raster |
* @return transformed raster |
201 |
*/ |
*/ |
202 |
public WritableRaster filter (Raster src, WritableRaster dst) |
public final WritableRaster filter (Raster src, WritableRaster dst) |
203 |
{ |
{ |
204 |
throw new UnsupportedOperationException ("not implemented yet"); |
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 |
/** |
/** |
301 |
* @param src image to be transformed |
* @param src image to be transformed |
302 |
* @return bounds of the transformed image. |
* @return bounds of the transformed image. |
303 |
*/ |
*/ |
304 |
public Rectangle2D getBounds2D (BufferedImage src) |
public final Rectangle2D getBounds2D (BufferedImage src) |
305 |
{ |
{ |
306 |
return getBounds2D (src.getRaster()); |
return getBounds2D (src.getRaster()); |
307 |
} |
} |
312 |
* @param src raster to be transformed |
* @param src raster to be transformed |
313 |
* @return bounds of the transformed raster. |
* @return bounds of the transformed raster. |
314 |
*/ |
*/ |
315 |
public Rectangle2D getBounds2D (Raster src) |
public final Rectangle2D getBounds2D (Raster src) |
316 |
{ |
{ |
317 |
// determine new size for the transformed raster. |
// determine new size for the transformed raster. |
318 |
// Need to calculate transformed coordinates of the lower right |
// Need to calculate transformed coordinates of the lower right |
331 |
* |
* |
332 |
* @return interpolation type |
* @return interpolation type |
333 |
*/ |
*/ |
334 |
public int getInterpolationType () |
public final int getInterpolationType () |
335 |
{ |
{ |
336 |
if(hints.containsValue (RenderingHints.VALUE_INTERPOLATION_BILINEAR)) |
if(hints.containsValue (RenderingHints.VALUE_INTERPOLATION_BILINEAR)) |
337 |
return TYPE_BILINEAR; |
return TYPE_BILINEAR; |
352 |
return transform.transform (srcPt, dstPt); |
return transform.transform (srcPt, dstPt); |
353 |
} |
} |
354 |
|
|
355 |
/** Returns rendering hints that are used during transformation. |
/** |
356 |
|
* Returns rendering hints that are used during transformation. |
357 |
* |
* |
358 |
* @return rendering hints |
* @return rendering hints |
359 |
*/ |
*/ |
360 |
public RenderingHints getRenderingHints () |
public final RenderingHints getRenderingHints () |
361 |
{ |
{ |
362 |
return hints; |
return hints; |
363 |
} |
} |
364 |
|
|
365 |
/** Returns transform used in transformation between source and destination |
/** |
366 |
|
* Returns transform used in transformation between source and destination |
367 |
* image. |
* image. |
368 |
* |
* |
369 |
* @return transform |
* @return transform |
370 |
*/ |
*/ |
371 |
public AffineTransform getTransform () |
public final AffineTransform getTransform () |
372 |
{ |
{ |
373 |
return transform; |
return transform; |
374 |
} |
} |