001: /*
002: * $RCSfile: AffineOpImage.java,v $
003: *
004: * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
005: *
006: * Use is subject to license terms.
007: *
008: * $Revision: 1.1 $
009: * $Date: 2005/02/11 04:56:14 $
010: * $State: Exp $
011: */
012: package com.sun.media.jai.opimage;
013:
014: import java.awt.Point;
015: import java.awt.Rectangle;
016: import java.awt.geom.AffineTransform;
017: import java.awt.geom.Point2D;
018: import java.awt.geom.Rectangle2D;
019: import java.awt.image.Raster;
020: import java.awt.image.RenderedImage;
021: import java.awt.image.WritableRaster;
022: import java.awt.image.renderable.ParameterBlock;
023: import java.awt.image.DataBuffer;
024: import javax.media.jai.BorderExtender;
025: import javax.media.jai.GeometricOpImage;
026: import javax.media.jai.ImageLayout;
027: import javax.media.jai.Interpolation;
028: import javax.media.jai.InterpolationNearest;
029: import javax.media.jai.InterpolationBilinear;
030: import javax.media.jai.InterpolationBicubic;
031: import javax.media.jai.InterpolationBicubic2;
032: import javax.media.jai.PlanarImage;
033: import javax.media.jai.RasterAccessor;
034: import javax.media.jai.RasterFactory;
035: import javax.media.jai.util.ImagingException;
036: import javax.media.jai.util.ImagingListener;
037: import javax.media.jai.util.Range;
038: import java.util.Map;
039: import com.sun.media.jai.util.ImageUtil;
040:
041: /**
042: * An OpImage class to perform (possibly filtered) affine mapping between
043: * a source and destination image.
044: *
045: * The geometric relationship between source and destination pixels
046: * is defined as the following (<code>x</code> and <code>y</code> denote
047: * the source pixel coordinates; <code>x'</code> and <code>y'</code>
048: * denote the destination pixel coordinates; <code>m</code> denotes the
049: * 3x2 transform matrix):
050: * <ul>
051: * <code>
052: * x' = m[0][0] * x + m[0][1] * y + m[0][2]
053: * <br>
054: * y' = m[1][0] * x + m[1][1] * y + m[1][2]
055: * </code>
056: * </ul>
057: *
058: */
059: class AffineOpImage extends GeometricOpImage {
060:
061: /**
062: * Unsigned short Max Value
063: */
064: protected static final int USHORT_MAX = Short.MAX_VALUE
065: - Short.MIN_VALUE;
066:
067: /**
068: * The forward AffineTransform describing the image transformation.
069: */
070: protected AffineTransform f_transform;
071:
072: /**
073: * The inverse AffineTransform describing the image transformation.
074: */
075: protected AffineTransform i_transform;
076:
077: /** The Interpolation object. */
078: protected Interpolation interp;
079:
080: /** Store source & padded rectangle info */
081: private Rectangle srcimg, padimg;
082:
083: /** The BorderExtender */
084: protected BorderExtender extender;
085:
086: /** The true writable area */
087: private Rectangle theDest;
088:
089: /** Cache the ImagingListener. */
090: private ImagingListener listener;
091:
092: /**
093: * Scanline walking : variables & constants
094: */
095:
096: /** The fixed-point denominator of the fractional offsets. */
097: protected static final int geom_frac_max = 0x100000;
098:
099: double m00, m10, flr_m00, flr_m10;
100: double fracdx, fracdx1, fracdy, fracdy1;
101: int incx, incx1, incy, incy1;
102: int ifracdx, ifracdx1, ifracdy, ifracdy1;
103:
104: /**
105: * Padding values for interpolation
106: */
107: public int lpad, rpad, tpad, bpad;
108:
109: /**
110: * Computes floor(num/denom) using integer arithmetic.
111: * denom must not be equal to 0.
112: */
113: protected static int floorRatio(long num, long denom) {
114: if (denom < 0) {
115: denom = -denom;
116: num = -num;
117: }
118:
119: if (num >= 0) {
120: return (int) (num / denom);
121: } else {
122: return (int) ((num - denom + 1) / denom);
123: }
124: }
125:
126: /**
127: * Computes ceil(num/denom) using integer arithmetic.
128: * denom must not be equal to 0.
129: */
130: protected static int ceilRatio(long num, long denom) {
131: if (denom < 0) {
132: denom = -denom;
133: num = -num;
134: }
135:
136: if (num >= 0) {
137: return (int) ((num + denom - 1) / denom);
138: } else {
139: return (int) (num / denom);
140: }
141: }
142:
143: private static ImageLayout layoutHelper(ImageLayout layout,
144: RenderedImage source, AffineTransform forward_tr) {
145:
146: ImageLayout newLayout;
147: if (layout != null) {
148: newLayout = (ImageLayout) layout.clone();
149: } else {
150: newLayout = new ImageLayout();
151: }
152:
153: //
154: // Get sx0,sy0 coordinates and width & height of the source
155: //
156: float sx0 = (float) source.getMinX();
157: float sy0 = (float) source.getMinY();
158: float sw = (float) source.getWidth();
159: float sh = (float) source.getHeight();
160:
161: //
162: // The 4 points (clockwise order) are
163: // (sx0, sy0), (sx0+sw, sy0)
164: // (sx0, sy0+sh), (sx0+sw, sy0+sh)
165: //
166: Point2D[] pts = new Point2D[4];
167: pts[0] = new Point2D.Float(sx0, sy0);
168: pts[1] = new Point2D.Float((sx0 + sw), sy0);
169: pts[2] = new Point2D.Float((sx0 + sw), (sy0 + sh));
170: pts[3] = new Point2D.Float(sx0, (sy0 + sh));
171:
172: // Forward map
173: forward_tr.transform(pts, 0, pts, 0, 4);
174:
175: float dx0 = Float.MAX_VALUE;
176: float dy0 = Float.MAX_VALUE;
177: float dx1 = -Float.MAX_VALUE;
178: float dy1 = -Float.MAX_VALUE;
179: for (int i = 0; i < 4; i++) {
180: float px = (float) pts[i].getX();
181: float py = (float) pts[i].getY();
182:
183: dx0 = Math.min(dx0, px);
184: dy0 = Math.min(dy0, py);
185: dx1 = Math.max(dx1, px);
186: dy1 = Math.max(dy1, py);
187: }
188:
189: //
190: // Get the width & height of the resulting bounding box.
191: // This is set on the layout
192: //
193: int lw = (int) (dx1 - dx0);
194: int lh = (int) (dy1 - dy0);
195:
196: //
197: // Set the starting integral coordinate
198: // with the following criterion.
199: // If it's greater than 0.5, set it to the next integral value (ceil)
200: // else set it to the integral value (floor).
201: //
202: int lx0, ly0;
203:
204: int i_dx0 = (int) Math.floor(dx0);
205: if (Math.abs(dx0 - i_dx0) <= 0.5) {
206: lx0 = i_dx0;
207: } else {
208: lx0 = (int) Math.ceil(dx0);
209: }
210:
211: int i_dy0 = (int) Math.floor(dy0);
212: if (Math.abs(dy0 - i_dy0) <= 0.5) {
213: ly0 = i_dy0;
214: } else {
215: ly0 = (int) Math.ceil(dy0);
216: }
217:
218: //
219: // Create the layout
220: //
221: newLayout.setMinX(lx0);
222: newLayout.setMinY(ly0);
223: newLayout.setWidth(lw);
224: newLayout.setHeight(lh);
225:
226: return newLayout;
227: }
228:
229: /**
230: * Constructs an AffineOpImage from a RenderedImage source,
231: * AffineTransform, and Interpolation object. The image
232: * dimensions are determined by forward-mapping the source bounds.
233: * The tile grid layout, SampleModel, and ColorModel are specified
234: * by the image source, possibly overridden by values from the
235: * ImageLayout parameter.
236: *
237: * @param source a RenderedImage.
238: * @param extender a BorderExtender, or null.
239: * @param layout an ImageLayout optionally containing the tile grid layout,
240: * SampleModel, and ColorModel, or null.
241: * @param transform the desired AffineTransform.
242: * @param interp an Interpolation object.
243: */
244: public AffineOpImage(RenderedImage source, BorderExtender extender,
245: Map config, ImageLayout layout, AffineTransform transform,
246: Interpolation interp, double[] backgroundValues) {
247: super (vectorize(source),
248: layoutHelper(layout, source, transform), config, true,
249: extender, interp, backgroundValues);
250:
251: listener = ImageUtil
252: .getImagingListener((java.awt.RenderingHints) config);
253:
254: // store the interp and extender objects
255: this .interp = interp;
256:
257: // the extender
258: this .extender = extender;
259:
260: // Store the padding values
261: lpad = interp.getLeftPadding();
262: rpad = interp.getRightPadding();
263: tpad = interp.getTopPadding();
264: bpad = interp.getBottomPadding();
265:
266: //
267: // Store source bounds rectangle
268: // and the padded rectangle (for extension cases)
269: //
270: srcimg = new Rectangle(getSourceImage(0).getMinX(),
271: getSourceImage(0).getMinY(), getSourceImage(0)
272: .getWidth(), getSourceImage(0).getHeight());
273: padimg = new Rectangle(srcimg.x - lpad, srcimg.y - tpad,
274: srcimg.width + lpad + rpad, srcimg.height + tpad + bpad);
275:
276: if (extender == null) {
277: //
278: // Source has to be shrunk as per interpolation
279: // as a result the destination produced could
280: // be different from the layout
281: //
282:
283: //
284: // Get sx0,sy0 coordinates and width & height of the source
285: //
286: float sx0 = (float) srcimg.x;
287: float sy0 = (float) srcimg.y;
288: float sw = (float) srcimg.width;
289: float sh = (float) srcimg.height;
290:
291: //
292: // get padding amounts as per interpolation
293: //
294: float f_lpad = (float) lpad;
295: float f_rpad = (float) rpad;
296: float f_tpad = (float) tpad;
297: float f_bpad = (float) bpad;
298:
299: //
300: // As per pixel defined to be at (0.5, 0.5)
301: //
302: if (!(interp instanceof InterpolationNearest)) {
303: f_lpad += 0.5;
304: f_tpad += 0.5;
305: f_rpad += 0.5;
306: f_bpad += 0.5;
307: }
308:
309: //
310: // Shrink the source by padding amount prior to forward map
311: // This is the maxmimum available source than can be mapped
312: //
313: sx0 += f_lpad;
314: sy0 += f_tpad;
315: sw -= (f_lpad + f_rpad);
316: sh -= (f_tpad + f_bpad);
317:
318: //
319: // The 4 points are (x0, y0), (x0+w, y0)
320: // (x0+w, y0+h), (x0, y0+h)
321: //
322: Point2D[] pts = new Point2D[4];
323: pts[0] = new Point2D.Float(sx0, sy0);
324: pts[1] = new Point2D.Float((sx0 + sw), sy0);
325: pts[2] = new Point2D.Float((sx0 + sw), (sy0 + sh));
326: pts[3] = new Point2D.Float(sx0, (sy0 + sh));
327:
328: // Forward map
329: transform.transform(pts, 0, pts, 0, 4);
330:
331: float dx0 = Float.MAX_VALUE;
332: float dy0 = Float.MAX_VALUE;
333: float dx1 = -Float.MAX_VALUE;
334: float dy1 = -Float.MAX_VALUE;
335: for (int i = 0; i < 4; i++) {
336: float px = (float) pts[i].getX();
337: float py = (float) pts[i].getY();
338:
339: dx0 = Math.min(dx0, px);
340: dy0 = Math.min(dy0, py);
341: dx1 = Math.max(dx1, px);
342: dy1 = Math.max(dy1, py);
343: }
344:
345: //
346: // The layout is the wholly contained integer area of the
347: // corresponding floating point bounding box.
348: // We cannot round the corners of the floating rect because it
349: // would increase the size of the rect, so we need to ceil the
350: // upper corner and floor the lower corner.
351: //
352: int lx0 = (int) Math.ceil(dx0);
353: int ly0 = (int) Math.ceil(dy0);
354: int lx1 = (int) Math.floor(dx1);
355: int ly1 = (int) Math.floor(dy1);
356:
357: theDest = new Rectangle(lx0, ly0, lx1 - lx0, ly1 - ly0);
358: } else {
359: theDest = getBounds();
360: }
361:
362: // Store the inverse and forward transforms.
363: try {
364: this .i_transform = transform.createInverse();
365: } catch (Exception e) {
366: String message = JaiI18N.getString("AffineOpImage0");
367: listener.errorOccurred(message, new ImagingException(
368: message, e), this , false);
369: // throw new RuntimeException(JaiI18N.getString("AffineOpImage0"));
370: }
371: this .f_transform = (AffineTransform) transform.clone();
372:
373: //
374: // Store the incremental values used in scanline walking.
375: //
376: m00 = i_transform.getScaleX(); // get m00
377: flr_m00 = Math.floor(m00);
378: fracdx = m00 - flr_m00;
379: fracdx1 = 1.0F - fracdx;
380: incx = (int) flr_m00; // Movement
381: incx1 = incx + 1; // along x
382: ifracdx = (int) Math.round(fracdx * geom_frac_max);
383: ifracdx1 = geom_frac_max - ifracdx;
384:
385: m10 = i_transform.getShearY(); // get m10
386: flr_m10 = Math.floor(m10);
387: fracdy = m10 - flr_m10;
388: fracdy1 = 1.0F - fracdy;
389: incy = (int) flr_m10; // Movement
390: incy1 = incy + 1; // along y
391: ifracdy = (int) Math.round(fracdy * geom_frac_max);
392: ifracdy1 = geom_frac_max - ifracdy;
393: }
394:
395: /**
396: * Computes the source point corresponding to the supplied point.
397: *
398: * @param destPt the position in destination image coordinates
399: * to map to source image coordinates.
400: *
401: * @return a <code>Point2D</code> of the same class as
402: * <code>destPt</code>.
403: *
404: * @throws IllegalArgumentException if <code>destPt</code> is
405: * <code>null</code>.
406: *
407: * @since JAI 1.1.2
408: */
409: public Point2D mapDestPoint(Point2D destPt) {
410: if (destPt == null) {
411: throw new IllegalArgumentException(JaiI18N
412: .getString("Generic0"));
413: }
414:
415: Point2D dpt = (Point2D) destPt.clone();
416: dpt.setLocation(dpt.getX() + 0.5, dpt.getY() + 0.5);
417:
418: Point2D spt = i_transform.transform(dpt, null);
419: spt.setLocation(spt.getX() - 0.5, spt.getY() - 0.5);
420:
421: return spt;
422: }
423:
424: /**
425: * Computes the destination point corresponding to the supplied point.
426: *
427: * @param sourcePt the position in source image coordinates
428: * to map to destination image coordinates.
429: *
430: * @return a <code>Point2D</code> of the same class as
431: * <code>sourcePt</code>.
432: *
433: * @throws IllegalArgumentException if <code>destPt</code> is
434: * <code>null</code>.
435: *
436: * @since JAI 1.1.2
437: */
438: public Point2D mapSourcePoint(Point2D sourcePt) {
439: if (sourcePt == null) {
440: throw new IllegalArgumentException(JaiI18N
441: .getString("Generic0"));
442: }
443:
444: Point2D spt = (Point2D) sourcePt.clone();
445: spt.setLocation(spt.getX() + 0.5, spt.getY() + 0.5);
446:
447: Point2D dpt = f_transform.transform(spt, null);
448: dpt.setLocation(dpt.getX() - 0.5, dpt.getY() - 0.5);
449:
450: return dpt;
451: }
452:
453: /**
454: * Forward map the source Rectangle.
455: */
456: protected Rectangle forwardMapRect(Rectangle sourceRect,
457: int sourceIndex) {
458: return f_transform.createTransformedShape(sourceRect)
459: .getBounds();
460: }
461:
462: /**
463: * Backward map the destination Rectangle.
464: */
465: protected Rectangle backwardMapRect(Rectangle destRect,
466: int sourceIndex) {
467: //
468: // Backward map the destination to get the corresponding
469: // source Rectangle
470: //
471: float dx0 = (float) destRect.x;
472: float dy0 = (float) destRect.y;
473: float dw = (float) (destRect.width);
474: float dh = (float) (destRect.height);
475:
476: Point2D[] pts = new Point2D[4];
477: pts[0] = new Point2D.Float(dx0, dy0);
478: pts[1] = new Point2D.Float((dx0 + dw), dy0);
479: pts[2] = new Point2D.Float((dx0 + dw), (dy0 + dh));
480: pts[3] = new Point2D.Float(dx0, (dy0 + dh));
481:
482: i_transform.transform(pts, 0, pts, 0, 4);
483:
484: float f_sx0 = Float.MAX_VALUE;
485: float f_sy0 = Float.MAX_VALUE;
486: float f_sx1 = -Float.MAX_VALUE;
487: float f_sy1 = -Float.MAX_VALUE;
488: for (int i = 0; i < 4; i++) {
489: float px = (float) pts[i].getX();
490: float py = (float) pts[i].getY();
491:
492: f_sx0 = Math.min(f_sx0, px);
493: f_sy0 = Math.min(f_sy0, py);
494: f_sx1 = Math.max(f_sx1, px);
495: f_sy1 = Math.max(f_sy1, py);
496: }
497:
498: int s_x0 = 0, s_y0 = 0, s_x1 = 0, s_y1 = 0;
499:
500: // Find the bounding box of the source rectangle
501: if (interp instanceof InterpolationNearest) {
502: s_x0 = (int) Math.floor(f_sx0);
503: s_y0 = (int) Math.floor(f_sy0);
504:
505: // Fix for bug 4485920 was to add " + 0.05" to the following
506: // two lines. It should be noted that the fix was made based
507: // on empirical evidence and tested thoroughly, but it is not
508: // known whether this is the root cause.
509: s_x1 = (int) Math.ceil(f_sx1 + 0.5);
510: s_y1 = (int) Math.ceil(f_sy1 + 0.5);
511: } else {
512: s_x0 = (int) Math.floor(f_sx0 - 0.5);
513: s_y0 = (int) Math.floor(f_sy0 - 0.5);
514: s_x1 = (int) Math.ceil(f_sx1);
515: s_y1 = (int) Math.ceil(f_sy1);
516: }
517:
518: //
519: // Return the new rectangle
520: //
521: return new Rectangle(s_x0, s_y0, s_x1 - s_x0, s_y1 - s_y0);
522: }
523:
524: /**
525: * Backward map a destination coordinate (using inverse_transform)
526: * to get the corresponding source coordinate.
527: * We need not worry about interpolation here.
528: *
529: * @param destPt the destination point to backward map
530: * @return source point result of the backward map
531: */
532: public void mapDestPoint(Point2D destPoint, Point2D srcPoint) {
533: i_transform.transform(destPoint, srcPoint);
534: }
535:
536: public Raster computeTile(int tileX, int tileY) {
537: //
538: // Create a new WritableRaster to represent this tile.
539: //
540: Point org = new Point(tileXToX(tileX), tileYToY(tileY));
541: WritableRaster dest = createWritableRaster(sampleModel, org);
542:
543: //
544: // Clip output rectangle to image bounds.
545: //
546: Rectangle rect = new Rectangle(org.x, org.y, tileWidth,
547: tileHeight);
548:
549: //
550: // Clip destination tile against the writable destination
551: // area. This is either the layout or a smaller area if
552: // no extension is specified.
553: //
554: Rectangle destRect = rect.intersection(theDest);
555: Rectangle destRect1 = rect.intersection(getBounds());
556: if ((destRect.width <= 0) || (destRect.height <= 0)) {
557: // No area to write
558: if (setBackground)
559: ImageUtil.fillBackground(dest, destRect1,
560: backgroundValues);
561:
562: return dest;
563: }
564:
565: //
566: // determine the source rectangle needed to compute the destRect
567: //
568: Rectangle srcRect = mapDestRect(destRect, 0);
569: if (extender == null) {
570: srcRect = srcRect.intersection(srcimg);
571: } else {
572: srcRect = srcRect.intersection(padimg);
573: }
574:
575: if (!(srcRect.width > 0 && srcRect.height > 0)) {
576: if (setBackground)
577: ImageUtil.fillBackground(dest, destRect1,
578: backgroundValues);
579:
580: return dest;
581: }
582:
583: if (!destRect1.equals(destRect)) {
584: // beware that estRect1 contains destRect
585: ImageUtil.fillBordersWithBackgroundValues(destRect1,
586: destRect, dest, backgroundValues);
587: }
588:
589: Raster[] sources = new Raster[1];
590:
591: // Get the source data
592: if (extender == null) {
593: sources[0] = getSourceImage(0).getData(srcRect);
594: } else {
595: sources[0] = getSourceImage(0).getExtendedData(srcRect,
596: extender);
597: }
598:
599: // Compute destination tile
600: computeRect(sources, dest, destRect);
601:
602: // Recycle the source tile
603: if (getSourceImage(0).overlapsMultipleTiles(srcRect)) {
604: recycleTile(sources[0]);
605: }
606:
607: return dest;
608: }
609: }
|