001: /*
002: * $RCSfile: MlibAffineOpImage.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:55:49 $
010: * $State: Exp $
011: */
012: package com.sun.media.jai.mlib;
013:
014: import java.awt.Point;
015: import java.awt.Rectangle;
016: import java.awt.image.DataBuffer;
017: import java.awt.image.SampleModel;
018: import java.awt.image.Raster;
019: import java.awt.image.RenderedImage;
020: import java.awt.image.WritableRaster;
021: import java.awt.image.renderable.ParameterBlock;
022: import java.awt.image.renderable.RenderedImageFactory;
023: import java.awt.geom.AffineTransform;
024: import java.awt.geom.Point2D;
025: import java.awt.geom.Rectangle2D;
026: import javax.media.jai.BorderExtender;
027: import javax.media.jai.GeometricOpImage;
028: import javax.media.jai.ImageLayout;
029: import javax.media.jai.Interpolation;
030: import javax.media.jai.InterpolationNearest;
031: import javax.media.jai.InterpolationBilinear;
032: import javax.media.jai.InterpolationBicubic;
033: import javax.media.jai.InterpolationBicubic2;
034: import javax.media.jai.KernelJAI;
035: import javax.media.jai.OpImage;
036: import javax.media.jai.PlanarImage;
037: import javax.media.jai.util.ImagingException;
038: import javax.media.jai.util.ImagingListener;
039: import java.util.Map;
040: import com.sun.medialib.mlib.*;
041: import com.sun.media.jai.util.ImageUtil;
042:
043: // import com.sun.media.jai.test.OpImageTester;
044:
045: /**
046: * An OpImage class to perform AffineTransform on a source image.
047: *
048: */
049: class MlibAffineOpImage extends GeometricOpImage {
050:
051: /**
052: * The transformation in matrix form (medialib expects this form)
053: */
054: protected double f_transform[];
055: protected double m_transform[];
056: protected double medialib_tr[];
057: protected AffineTransform transform;
058: protected AffineTransform i_transform;
059:
060: /** The Interpolation object. */
061: protected Interpolation interp;
062:
063: /** Store source & padded rectangle info */
064: private Rectangle srcimg, padimg;
065:
066: /** The BorderExtender */
067: protected BorderExtender extender;
068:
069: /** The true writable area */
070: private Rectangle theDest;
071:
072: /** Cache the ImagingListener */
073: private ImagingListener listener;
074:
075: /**
076: * Padding values for interpolation
077: */
078: public int lpad, rpad, tpad, bpad;
079:
080: private static ImageLayout layoutHelper(ImageLayout layout,
081: RenderedImage source, AffineTransform forward_tr) {
082: ImageLayout newLayout;
083: if (layout != null) {
084: newLayout = (ImageLayout) layout.clone();
085: } else {
086: newLayout = new ImageLayout();
087: }
088:
089: //
090: // Get sx0,sy0 coordinates & width & height of the source.
091: //
092: float sx0 = (float) source.getMinX();
093: float sy0 = (float) source.getMinY();
094: float sw = (float) source.getWidth();
095: float sh = (float) source.getHeight();
096:
097: //
098: // The 4 points (clockwise order) are
099: // (sx0, sy0), (sx0+sw, sy0)
100: // (sx0, sy0+sh), (sx0+sw, sy0+sh)
101: //
102: Point2D[] pts = new Point2D[4];
103: pts[0] = new Point2D.Float(sx0, sy0);
104: pts[1] = new Point2D.Float((sx0 + sw), sy0);
105: pts[2] = new Point2D.Float((sx0 + sw), (sy0 + sh));
106: pts[3] = new Point2D.Float(sx0, (sy0 + sh));
107:
108: // Forward map
109: forward_tr.transform(pts, 0, pts, 0, 4);
110:
111: float dx0 = Float.MAX_VALUE;
112: float dy0 = Float.MAX_VALUE;
113: float dx1 = -Float.MAX_VALUE;
114: float dy1 = -Float.MAX_VALUE;
115: for (int i = 0; i < 4; i++) {
116: float px = (float) pts[i].getX();
117: float py = (float) pts[i].getY();
118:
119: dx0 = Math.min(dx0, px);
120: dy0 = Math.min(dy0, py);
121: dx1 = Math.max(dx1, px);
122: dy1 = Math.max(dy1, py);
123: }
124:
125: //
126: // Get the width & height of the resulting bounding box.
127: // This is set on the layout
128: //
129: int lw = (int) (dx1 - dx0);
130: int lh = (int) (dy1 - dy0);
131:
132: //
133: // Set the starting integral coordinate
134: // with the following criterion.
135: // If it's greater than 0.5, set it to the next integral value (ceil)
136: // else set it to the integral value (floor).
137: //
138: int lx0, ly0;
139:
140: int i_dx0 = (int) Math.floor(dx0);
141: if (Math.abs(dx0 - i_dx0) <= 0.5) {
142: lx0 = i_dx0;
143: } else {
144: lx0 = (int) Math.ceil(dx0);
145: }
146:
147: int i_dy0 = (int) Math.floor(dy0);
148: if (Math.abs(dy0 - i_dy0) <= 0.5) {
149: ly0 = i_dy0;
150: } else {
151: ly0 = (int) Math.ceil(dy0);
152: }
153:
154: //
155: // Create the layout
156: //
157: newLayout.setMinX(lx0);
158: newLayout.setMinY(ly0);
159: newLayout.setWidth(lw);
160: newLayout.setHeight(lh);
161:
162: return newLayout;
163: }
164:
165: /**
166: * Creates a MlibAffineOpImage given a ParameterBlock containing the
167: * image source and the AffineTransform and the interpolation.
168: * The image dimensions are derived from the source image. The tile
169: * grid layout, SampleModel, and ColorModel may optionally be specified
170: * by an ImageLayout object.
171: *
172: * @param source a RenderedImage.
173: * @param extender a BorderExtender, or null.
174:
175: * or null. If null, a default cache will be used.
176: * @param layout an ImageLayout optionally containing the tile grid layout,
177: * SampleModel, and ColorModel, or null.
178: * @param kernel the convolution KernelJAI.
179: */
180: public MlibAffineOpImage(RenderedImage source, ImageLayout layout,
181: Map config, BorderExtender extender,
182: AffineTransform transform, Interpolation interp,
183: double[] backgroundValues) {
184: super (vectorize(source),
185: layoutHelper(layout, source, transform), config, true,
186: extender, interp, backgroundValues);
187:
188: // store the interp and extender objects
189: this .interp = interp;
190:
191: // the extender
192: this .extender = extender;
193:
194: // cache the listener
195: listener = ImageUtil
196: .getImagingListener((java.awt.RenderingHints) config);
197:
198: // Store the padding values
199: lpad = interp.getLeftPadding();
200: rpad = interp.getRightPadding();
201: tpad = interp.getTopPadding();
202: bpad = interp.getBottomPadding();
203:
204: //
205: // Store source bounds rectangle
206: // and the padded rectangle (for extension cases)
207: //
208: srcimg = new Rectangle(getSourceImage(0).getMinX(),
209: getSourceImage(0).getMinY(), getSourceImage(0)
210: .getWidth(), getSourceImage(0).getHeight());
211: padimg = new Rectangle(srcimg.x - lpad, srcimg.y - tpad,
212: srcimg.width + lpad + rpad, srcimg.height + tpad + bpad);
213:
214: if (extender == null) {
215: //
216: // Source has to be shrunk as per interpolation
217: // as a result the destination produced could
218: // be different from the layout
219: //
220:
221: //
222: // Get sx0,sy0 coordinates and width & height of the source
223: //
224: float sx0 = (float) srcimg.x;
225: float sy0 = (float) srcimg.y;
226: float sw = (float) srcimg.width;
227: float sh = (float) srcimg.height;
228:
229: //
230: // get padding amounts as per interpolation
231: //
232: float f_lpad = (float) lpad;
233: float f_rpad = (float) rpad;
234: float f_tpad = (float) tpad;
235: float f_bpad = (float) bpad;
236:
237: //
238: // As per pixel defined to be at (0.5, 0.5)
239: //
240: if ((interp instanceof InterpolationBilinear)
241: || (interp instanceof InterpolationBicubic)
242: || (interp instanceof InterpolationBicubic2)) {
243: f_lpad += 0.5;
244: f_tpad += 0.5;
245: f_rpad += 0.5;
246: f_bpad += 0.5;
247: }
248:
249: //
250: // Shrink the source by padding amount prior to forward map
251: // This is the maxmimum available source than can be mapped
252: //
253: sx0 += f_lpad;
254: sy0 += f_tpad;
255: sw -= (f_lpad + f_rpad);
256: sh -= (f_tpad + f_bpad);
257:
258: //
259: // The 4 points are (x0, y0), (x0+w, y0)
260: // (x0+w, y0+h), (x0, y0+h)
261: //
262: Point2D[] pts = new Point2D[4];
263: pts[0] = new Point2D.Float(sx0, sy0);
264: pts[1] = new Point2D.Float((sx0 + sw), sy0);
265: pts[2] = new Point2D.Float((sx0 + sw), (sy0 + sh));
266: pts[3] = new Point2D.Float(sx0, (sy0 + sh));
267:
268: // Forward map
269: transform.transform(pts, 0, pts, 0, 4);
270:
271: float dx0 = Float.MAX_VALUE;
272: float dy0 = Float.MAX_VALUE;
273: float dx1 = -Float.MAX_VALUE;
274: float dy1 = -Float.MAX_VALUE;
275: for (int i = 0; i < 4; i++) {
276: float px = (float) pts[i].getX();
277: float py = (float) pts[i].getY();
278:
279: dx0 = Math.min(dx0, px);
280: dy0 = Math.min(dy0, py);
281: dx1 = Math.max(dx1, px);
282: dy1 = Math.max(dy1, py);
283: }
284:
285: //
286: // The layout is the wholly contained integer area of the
287: // corresponding floating point bounding box.
288: // We cannot round the corners of the floating rect because it
289: // would increase the size of the rect, so we need to ceil the
290: // upper corner and floor the lower corner.
291: //
292: int lx0 = (int) Math.ceil(dx0);
293: int ly0 = (int) Math.ceil(dy0);
294: int lx1 = (int) Math.floor(dx1);
295: int ly1 = (int) Math.floor(dy1);
296:
297: theDest = new Rectangle(lx0, ly0, lx1 - lx0, ly1 - ly0);
298: } else {
299: theDest = getBounds();
300: }
301:
302: // Store the inverse and forward transform
303: try {
304: this .i_transform = transform.createInverse();
305: } catch (java.awt.geom.NoninvertibleTransformException e) {
306: String message = JaiI18N.getString("MlibAffineOpImage0");
307: listener.errorOccurred(message, new ImagingException(
308: message, e), this , false);
309: // throw new RuntimeException(JaiI18N.getString("MlibAffineOpImage0"));
310: }
311: this .transform = (AffineTransform) transform.clone();
312:
313: //
314: // Get the forward transform into an array
315: // Java returns the values into the array as
316: // {m00 m10 m01 m11 m02 m12}
317: //
318: this .f_transform = new double[6];
319: transform.getMatrix(this .f_transform);
320:
321: //
322: // Rearrange the transform to medialib specifications
323: // J2D's transform is [m00 m01 m02] [m10 m11 m12]
324: // Medialib's transform is [a b tx] [c d ty]
325: //
326: this .medialib_tr = new double[6];
327: medialib_tr[0] = f_transform[0]; // a <---> m00
328: medialib_tr[1] = f_transform[2]; // b <---> m01
329: medialib_tr[2] = f_transform[4]; // tx <---> m02
330: medialib_tr[3] = f_transform[1]; // c <---> m10
331: medialib_tr[4] = f_transform[3]; // d <---> m11
332: medialib_tr[5] = f_transform[5]; // ty <---> m12
333:
334: //
335: // Make a copy for our internal use
336: //
337: this .m_transform = new double[6];
338: m_transform[0] = f_transform[0]; // a <---> m00
339: m_transform[1] = f_transform[2]; // b <---> m01
340: m_transform[2] = f_transform[4]; // tx <---> m02
341: m_transform[3] = f_transform[1]; // c <---> m10
342: m_transform[4] = f_transform[3]; // d <---> m11
343: m_transform[5] = f_transform[5]; // ty <---> m12
344: }
345:
346: /**
347: * Computes the source point corresponding to the supplied point.
348: *
349: * @param destPt the position in destination image coordinates
350: * to map to source image coordinates.
351: *
352: * @return a <code>Point2D</code> of the same class as
353: * <code>destPt</code>.
354: *
355: * @throws IllegalArgumentException if <code>destPt</code> is
356: * <code>null</code>.
357: *
358: * @since JAI 1.1.2
359: */
360: public Point2D mapDestPoint(Point2D destPt) {
361: if (destPt == null) {
362: throw new IllegalArgumentException(JaiI18N
363: .getString("Generic0"));
364: }
365:
366: Point2D dpt = (Point2D) destPt.clone();
367: dpt.setLocation(dpt.getX() + 0.5, dpt.getY() + 0.5);
368:
369: Point2D spt = i_transform.transform(dpt, null);
370: spt.setLocation(spt.getX() - 0.5, spt.getY() - 0.5);
371:
372: return spt;
373: }
374:
375: /**
376: * Computes the destination point corresponding to the supplied point.
377: *
378: * @param sourcePt the position in source image coordinates
379: * to map to destination image coordinates.
380: *
381: * @return a <code>Point2D</code> of the same class as
382: * <code>sourcePt</code>.
383: *
384: * @throws IllegalArgumentException if <code>destPt</code> is
385: * <code>null</code>.
386: *
387: * @since JAI 1.1.2
388: */
389: public Point2D mapSourcePoint(Point2D sourcePt) {
390: if (sourcePt == null) {
391: throw new IllegalArgumentException(JaiI18N
392: .getString("Generic0"));
393: }
394:
395: Point2D spt = (Point2D) sourcePt.clone();
396: spt.setLocation(spt.getX() + 0.5, spt.getY() + 0.5);
397:
398: Point2D dpt = transform.transform(spt, null);
399: dpt.setLocation(dpt.getX() - 0.5, dpt.getY() - 0.5);
400:
401: return dpt;
402: }
403:
404: /**
405: * Forward map the source Rectangle.
406: */
407: protected Rectangle forwardMapRect(Rectangle sourceRect,
408: int sourceIndex) {
409: return transform.createTransformedShape(sourceRect).getBounds();
410: }
411:
412: /**
413: * Backward map the destination Rectangle.
414: */
415: protected Rectangle backwardMapRect(Rectangle destRect,
416: int sourceIndex) {
417: //
418: // Backward map the destination rectangle to get the
419: // corresponding source rectangle
420: //
421: float dx0 = (float) destRect.x;
422: float dy0 = (float) destRect.y;
423: float dw = (float) (destRect.width);
424: float dh = (float) (destRect.height);
425:
426: Point2D[] pts = new Point2D[4];
427: pts[0] = new Point2D.Float(dx0, dy0);
428: pts[1] = new Point2D.Float((dx0 + dw), dy0);
429: pts[2] = new Point2D.Float((dx0 + dw), (dy0 + dh));
430: pts[3] = new Point2D.Float(dx0, (dy0 + dh));
431:
432: i_transform.transform(pts, 0, pts, 0, 4);
433:
434: float f_sx0 = Float.MAX_VALUE;
435: float f_sy0 = Float.MAX_VALUE;
436: float f_sx1 = -Float.MAX_VALUE;
437: float f_sy1 = -Float.MAX_VALUE;
438: for (int i = 0; i < 4; i++) {
439: float px = (float) pts[i].getX();
440: float py = (float) pts[i].getY();
441:
442: f_sx0 = Math.min(f_sx0, px);
443: f_sy0 = Math.min(f_sy0, py);
444: f_sx1 = Math.max(f_sx1, px);
445: f_sy1 = Math.max(f_sy1, py);
446: }
447:
448: int s_x0 = 0, s_y0 = 0, s_x1 = 0, s_y1 = 0;
449:
450: // Find the bounding box of the source rectangle
451: if (interp instanceof InterpolationNearest) {
452: s_x0 = (int) Math.floor(f_sx0);
453: s_y0 = (int) Math.floor(f_sy0);
454: s_x1 = (int) Math.ceil(f_sx1);
455: s_y1 = (int) Math.ceil(f_sy1);
456: } else {
457: s_x0 = (int) Math.floor(f_sx0 - 0.5);
458: s_y0 = (int) Math.floor(f_sy0 - 0.5);
459: s_x1 = (int) Math.ceil(f_sx1);
460: s_y1 = (int) Math.ceil(f_sy1);
461: }
462:
463: //
464: // Return the new rectangle
465: //
466: return new Rectangle(s_x0, s_y0, s_x1 - s_x0, s_y1 - s_y0);
467: }
468:
469: /*
470: * Compute a given tile
471: */
472: public Raster computeTile(int tileX, int tileY) {
473: //
474: // Create a new WritableRaster to represent this tile.
475: //
476: Point org = new Point(tileXToX(tileX), tileYToY(tileY));
477: WritableRaster dest = createWritableRaster(sampleModel, org);
478:
479: //
480: // Clip output rectangle to image bounds.
481: //
482: Rectangle rect = new Rectangle(org.x, org.y, tileWidth,
483: tileHeight);
484:
485: //
486: // Clip destination tile against the writable destination
487: // area. This is either the layout or a smaller area if
488: // no extension is specified.
489: //
490: Rectangle destRect = rect.intersection(theDest);
491: Rectangle destRect1 = rect.intersection(getBounds());
492: if ((destRect.width <= 0) || (destRect.height <= 0)) {
493: if (setBackground) {
494: ImageUtil.fillBackground(dest, destRect1,
495: backgroundValues);
496: }
497: // No area to write
498: return dest;
499: }
500:
501: //
502: // determine the source rectangle needed to compute the destRect
503: //
504: Rectangle srcRect = mapDestRect(destRect, 0);
505: if (extender == null) {
506: srcRect = srcRect.intersection(srcimg);
507: } else {
508: srcRect = srcRect.intersection(padimg);
509: }
510:
511: if (srcRect.width <= 0 || srcRect.height <= 0) {
512: // destRect backward mapped outside the source
513: if (setBackground) {
514: ImageUtil.fillBackground(dest, destRect1,
515: backgroundValues);
516: }
517: return dest;
518: }
519:
520: if (!destRect1.equals(destRect)) {
521: // beware that destRect1 contains destRect
522: ImageUtil.fillBordersWithBackgroundValues(destRect1,
523: destRect, dest, backgroundValues);
524: }
525:
526: Raster[] sources = new Raster[1];
527:
528: // Get the source data
529: if (extender == null) {
530: sources[0] = getSourceImage(0).getData(srcRect);
531: } else {
532: sources[0] = getSourceImage(0).getExtendedData(srcRect,
533: extender);
534: }
535:
536: computeRect(sources, dest, destRect);
537:
538: // Recycle the source tile
539: if (getSourceImage(0).overlapsMultipleTiles(srcRect)) {
540: recycleTile(sources[0]);
541: }
542:
543: return dest;
544: }
545: }
|