001: /*
002: * $RCSfile: WarpOpImage.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:57:24 $
010: * $State: Exp $
011: */
012: package javax.media.jai;
013:
014: import java.awt.Point;
015: import java.awt.Rectangle;
016: import java.awt.geom.Point2D;
017: import java.awt.image.Raster;
018: import java.awt.image.RenderedImage;
019: import java.awt.image.WritableRaster;
020: import java.util.Map;
021: import javax.media.jai.util.CaselessStringKey;
022: import com.sun.media.jai.util.ImageUtil;
023:
024: /**
025: * A general implementation of image warping, and a superclass for
026: * specific image warping operations.
027: *
028: * <p> The image warp is specified by a <code>Warp</code> object
029: * and an <code>Interpolation</code> object.
030: *
031: * <p> Subclasses of <code>WarpOpImage</code> may choose whether they
032: * wish to implement the cobbled or non-cobbled variant of
033: * <code>computeRect</code> by means of the <code>cobbleSources</code>
034: * constructor parameter. The class comments for <code>OpImage</code>
035: * provide more information about how to override
036: * <code>computeRect</code>.
037: *
038: * It should be noted that the superclass <code>GeometricOpImage</code>
039: * automatically adds a value of <code>Boolean.TRUE</code> for the
040: * <code>JAI.KEY_REPLACE_INDEX_COLOR_MODEL</code> to the given
041: * <code>configuration</code> and passes it up to its superclass constructor
042: * so that geometric operations are performed on the pixel values instead
043: * of being performed on the indices into the color map for those
044: * operations whose source(s) have an <code>IndexColorModel</code>.
045: * This addition will take place only if a value for the
046: * <code>JAI.KEY_REPLACE_INDEX_COLOR_MODEL</code> has not already been
047: * provided by the user. Note that the <code>configuration</code> Map
048: * is cloned before the new hint is added to it. Regarding the value
049: * for the <code>JAI.KEY_REPLACE_INDEX_COLOR_MODEL</code>
050: * <code>RenderingHints</code>, the operator itself can be smart
051: * based on the parameters, i.e. while the default value for
052: * the <code>JAI.KEY_REPLACE_INDEX_COLOR_MODEL</code> is
053: * <code>Boolean.TRUE</code> for operations that extend this class,
054: * in some cases the operator could set the default.
055: *
056: * @see GeometricOpImage
057: * @see OpImage
058: * @see Warp
059: * @see Interpolation
060: */
061: public abstract class WarpOpImage extends GeometricOpImage {
062:
063: /**
064: * The <code>Warp</code> object describing the backwards pixel
065: * map. It can not be <code>null</code>.
066: */
067: protected Warp warp;
068:
069: /**
070: * If no bounds are specified, attempt to derive the image bounds by
071: * forward mapping the source bounds.
072: */
073: private static ImageLayout getLayout(ImageLayout layout,
074: RenderedImage source, Warp warp) {
075: // If a non-null layout with defined bounds is supplied,
076: // return it directly.
077: if (layout != null
078: && layout.isValid(ImageLayout.MIN_X_MASK
079: | ImageLayout.MIN_Y_MASK
080: | ImageLayout.WIDTH_MASK
081: | ImageLayout.HEIGHT_MASK)) {
082: return layout;
083: }
084:
085: // Get the source bounds.
086: Rectangle sourceBounds = new Rectangle(source.getMinX(), source
087: .getMinY(), source.getWidth(), source.getHeight());
088:
089: // Attempt to forward map the source bounds.
090: Rectangle destBounds = warp.mapSourceRect(sourceBounds);
091:
092: // If this failed, attempt to map the vertices.
093: if (destBounds == null) {
094: Point[] srcPts = new Point[] {
095: new Point(sourceBounds.x, sourceBounds.y),
096: new Point(sourceBounds.x + sourceBounds.width,
097: sourceBounds.y),
098: new Point(sourceBounds.x, sourceBounds.y
099: + sourceBounds.height),
100: new Point(sourceBounds.x + sourceBounds.width,
101: sourceBounds.y + sourceBounds.height) };
102:
103: boolean verticesMapped = true;
104:
105: double xMin = Double.MAX_VALUE;
106: double xMax = -Double.MAX_VALUE;
107: double yMin = Double.MAX_VALUE;
108: double yMax = -Double.MAX_VALUE;
109:
110: for (int i = 0; i < 4; i++) {
111: Point2D destPt = warp.mapSourcePoint(srcPts[i]);
112: if (destPt == null) {
113: verticesMapped = false;
114: break;
115: }
116:
117: double x = destPt.getX();
118: double y = destPt.getY();
119: if (x < xMin) {
120: xMin = x;
121: }
122: if (x > xMax) {
123: xMax = x;
124: }
125: if (y < yMin) {
126: yMin = y;
127: }
128: if (y > yMax) {
129: yMax = y;
130: }
131: }
132:
133: // If all vertices mapped, compute the bounds.
134: if (verticesMapped) {
135: destBounds = new Rectangle();
136: destBounds.x = (int) Math.floor(xMin);
137: destBounds.y = (int) Math.floor(yMin);
138: destBounds.width = (int) Math.ceil(xMax - destBounds.x);
139: destBounds.height = (int) Math
140: .ceil(yMax - destBounds.y);
141: }
142: }
143:
144: // If bounds still not computed, approximate the destination bounds
145: // by the source bounds, compute an approximate forward mapping,
146: // and use it to compute the destination bounds. If the warp is
147: // a WarpAffine then skip it as mapSourceRect() already failed.
148: if (destBounds == null && !(warp instanceof WarpAffine)) {
149: Point[] destPts = new Point[] {
150: new Point(sourceBounds.x, sourceBounds.y),
151: new Point(sourceBounds.x + sourceBounds.width,
152: sourceBounds.y),
153: new Point(sourceBounds.x, sourceBounds.y
154: + sourceBounds.height),
155: new Point(sourceBounds.x + sourceBounds.width,
156: sourceBounds.y + sourceBounds.height) };
157:
158: float[] sourceCoords = new float[8];
159: float[] destCoords = new float[8];
160: int offset = 0;
161:
162: for (int i = 0; i < 4; i++) {
163: Point2D dstPt = destPts[i];
164: Point2D srcPt = warp.mapDestPoint(destPts[i]);
165: destCoords[offset] = (float) dstPt.getX();
166: destCoords[offset + 1] = (float) dstPt.getY();
167: sourceCoords[offset] = (float) srcPt.getX();
168: sourceCoords[offset + 1] = (float) srcPt.getY();
169: offset += 2;
170: }
171:
172: // Guaranteed to be a WarpAffine as the degree is 1.
173: WarpAffine wa = (WarpAffine) WarpPolynomial.createWarp(
174: sourceCoords, 0, destCoords, 0, 8, 1.0F, 1.0F,
175: 1.0F, 1.0F, 1);
176:
177: destBounds = wa.mapSourceRect(sourceBounds);
178: }
179:
180: // If bounds available, clone or create a new ImageLayout
181: // to be modified.
182: if (destBounds != null) {
183: if (layout == null) {
184: layout = new ImageLayout(destBounds.x, destBounds.y,
185: destBounds.width, destBounds.height);
186: } else {
187: layout = (ImageLayout) layout.clone();
188: layout.setMinX(destBounds.x);
189: layout.setMinY(destBounds.y);
190: layout.setWidth(destBounds.width);
191: layout.setHeight(destBounds.height);
192: }
193: }
194:
195: return layout;
196: }
197:
198: /**
199: * Constructor.
200: *
201: * <p> The image's layout is encapsulated in the <code>layout</code>
202: * argument. The user-supplied layout values supersedes the default
203: * settings. Any layout setting not specified by the user will take
204: * the corresponding value of the source image's layout.
205: *
206: * @param layout The layout of this image.
207: * @param source The source image; can not be <code>null</code>.
208: * @param configuration Configurable attributes of the image including
209: * configuration variables indexed by
210: * <code>RenderingHints.Key</code>s and image properties indexed
211: * by <code>String</code>s or <code>CaselessStringKey</code>s.
212: * This is simply forwarded to the superclass constructor.
213: * @param cobbleSources A <code>boolean</code> indicating whether
214: * <code>computeRect()</code> expects contiguous sources.
215: * To use the default implementation of warping contained in
216: * this class, set <code>cobbleSources</code> to <code>false</code>.
217: * @param extender A BorderExtender, or null.
218: * @param interp The <code>Interpolation</code> object describing the
219: * interpolation method.
220: * @param warp The <code>Warp</code> object describing the warp.
221: *
222: * @throws IllegalArgumentException if <code>source</code>
223: * is <code>null</code>.
224: * @throws IllegalArgumentException if combining the
225: * source bounds with the layout parameter results in negative
226: * output width or height.
227: * @throws IllegalArgumentException If <code>warp</code> is
228: * <code>null</code>.
229: * @since JAI 1.1
230: */
231: public WarpOpImage(RenderedImage source, ImageLayout layout,
232: Map configuration, boolean cobbleSources,
233: BorderExtender extender, Interpolation interp, Warp warp) {
234: this (source, layout, configuration, cobbleSources, extender,
235: interp, warp, null);
236: }
237:
238: /**
239: * Constructor.
240: *
241: * <p> The image's layout is encapsulated in the <code>layout</code>
242: * argument. The user-supplied layout values supersedes the default
243: * settings. Any layout setting not specified by the user will take
244: * the corresponding value of the source image's layout.
245: *
246: * @param layout The layout of this image.
247: * @param source The source image; can not be <code>null</code>.
248: * @param configuration Configurable attributes of the image including
249: * configuration variables indexed by
250: * <code>RenderingHints.Key</code>s and image properties indexed
251: * by <code>String</code>s or <code>CaselessStringKey</code>s.
252: * This is simply forwarded to the superclass constructor.
253: * @param cobbleSources A <code>boolean</code> indicating whether
254: * <code>computeRect()</code> expects contiguous sources.
255: * To use the default implementation of warping contained in
256: * this class, set <code>cobbleSources</code> to <code>false</code>.
257: * @param extender A BorderExtender, or null.
258: * @param interp The <code>Interpolation</code> object describing the
259: * interpolation method.
260: * @param warp The <code>Warp</code> object describing the warp.
261: * @param backgroundValues The user-specified background values. If the
262: * provided array length is smaller than the number of bands, all
263: * the bands will be filled with the first element of the array.
264: * If the provided array is null, it will be set to
265: * <code>new double[]{0.0}</code> in the superclass.
266: *
267: * @throws IllegalArgumentException if <code>source</code>
268: * is <code>null</code>.
269: * @throws IllegalArgumentException if combining the
270: * source bounds with the layout parameter results in negative
271: * output width or height.
272: * @throws IllegalArgumentException If <code>warp</code> is
273: * <code>null</code>.
274: *
275: * @since JAI 1.1.2
276: */
277: public WarpOpImage(RenderedImage source, ImageLayout layout,
278: Map configuration, boolean cobbleSources,
279: BorderExtender extender, Interpolation interp, Warp warp,
280: double[] backgroundValues) {
281: super (
282: vectorize(source), // vectorize() checks for null source.
283: getLayout(layout, source, warp), configuration,
284: cobbleSources, extender, interp, backgroundValues);
285:
286: if (warp == null) {
287: throw new IllegalArgumentException(JaiI18N
288: .getString("Generic0"));
289: }
290: this .warp = warp;
291:
292: if (cobbleSources && extender == null) {
293: // Do a basic forward mapping, taking into account the
294: // pixel energy is at (0.5, 0.5).
295: int l = interp == null ? 0 : interp.getLeftPadding();
296: int r = interp == null ? 0 : interp.getRightPadding();
297: int t = interp == null ? 0 : interp.getTopPadding();
298: int b = interp == null ? 0 : interp.getBottomPadding();
299:
300: int x = getMinX() + l;
301: int y = getMinY() + t;
302: int w = Math.max(getWidth() - l - r, 0);
303: int h = Math.max(getHeight() - t - b, 0);
304:
305: computableBounds = new Rectangle(x, y, w, h);
306:
307: } else {
308: // Extender is availabe, write the entire destination.
309: computableBounds = getBounds();
310: }
311: }
312:
313: /**
314: * Returns the number of samples required to the left of the center.
315: *
316: * @return The left padding factor.
317: *
318: * @deprecated as of JAI 1.1.
319: */
320: public int getLeftPadding() {
321: return interp == null ? 0 : interp.getLeftPadding();
322: }
323:
324: /**
325: * Returns the number of samples required to the right of the center.
326: *
327: * @return The right padding factor.
328: *
329: * @deprecated as of JAI 1.1.
330: */
331: public int getRightPadding() {
332: return interp == null ? 0 : interp.getRightPadding();
333: }
334:
335: /**
336: * Returns the number of samples required above the center.
337: *
338: * @return The top padding factor.
339: *
340: * @deprecated as of JAI 1.1.
341: */
342: public int getTopPadding() {
343: return interp == null ? 0 : interp.getTopPadding();
344: }
345:
346: /**
347: * Returns the number of samples required below the center.
348: *
349: * @return The bottom padding factor.
350: *
351: * @deprecated as of JAI 1.1.
352: */
353: public int getBottomPadding() {
354: return interp == null ? 0 : interp.getBottomPadding();
355: }
356:
357: /**
358: * Computes the position in the specified source that best
359: * matches the supplied destination image position.
360: *
361: * <p>The implementation in this class returns the value returned by
362: * <code>warp.mapDestPoint(destPt)</code>. Subclasses requiring
363: * different behavior should override this method.</p>
364: *
365: * @param destPt the position in destination image coordinates
366: * to map to source image coordinates.
367: * @param sourceIndex the index of the source image.
368: *
369: * @return a <code>Point2D</code> of the same class as
370: * <code>destPt</code> or <code>null</code>.
371: *
372: * @throws IllegalArgumentException if <code>destPt</code> is
373: * <code>null</code>.
374: * @throws IndexOutOfBoundsException if <code>sourceIndex</code> is
375: * non-zero.
376: *
377: * @since JAI 1.1.2
378: */
379: public Point2D mapDestPoint(Point2D destPt, int sourceIndex) {
380: if (destPt == null) {
381: throw new IllegalArgumentException(JaiI18N
382: .getString("Generic0"));
383: } else if (sourceIndex != 0) {
384: throw new IndexOutOfBoundsException(JaiI18N
385: .getString("Generic1"));
386: }
387:
388: return warp.mapDestPoint(destPt);
389: }
390:
391: /**
392: * Computes the position in the destination that best
393: * matches the supplied source image position.
394:
395: * <p>The implementation in this class returns the value returned by
396: * <code>warp.mapSourcePoint(sourcePt)</code>. Subclasses requiring
397: * different behavior should override this method.</p>
398: *
399: * @param sourcePt the position in source image coordinates
400: * to map to destination image coordinates.
401: * @param sourceIndex the index of the source image.
402: *
403: * @return a <code>Point2D</code> of the same class as
404: * <code>sourcePt</code> or <code>null</code>.
405: *
406: * @throws IllegalArgumentException if <code>sourcePt</code> is
407: * <code>null</code>.
408: * @throws IndexOutOfBoundsException if <code>sourceIndex</code> is
409: * non-zero.
410: *
411: * @since JAI 1.1.2
412: */
413: public Point2D mapSourcePoint(Point2D sourcePt, int sourceIndex) {
414: if (sourcePt == null) {
415: throw new IllegalArgumentException(JaiI18N
416: .getString("Generic0"));
417: } else if (sourceIndex != 0) {
418: throw new IndexOutOfBoundsException(JaiI18N
419: .getString("Generic1"));
420: }
421:
422: return warp.mapSourcePoint(sourcePt);
423: }
424:
425: /**
426: * Returns the minimum bounding box of the region of the destination
427: * to which a particular <code>Rectangle</code> of the specified source
428: * will be mapped.
429: *
430: * @param sourceRect the <code>Rectangle</code> in source coordinates.
431: * @param sourceIndex the index of the source image.
432: *
433: * @return a <code>Rectangle</code> indicating the destination
434: * bounding box, or <code>null</code> if the bounding box
435: * is unknown.
436: *
437: * @throws IllegalArgumentException if <code>sourceIndex</code> is
438: * negative or greater than the index of the last source.
439: * @throws IllegalArgumentException if <code>sourceRect</code> is
440: * <code>null</code>.
441: *
442: * @since JAI 1.1
443: */
444: protected Rectangle forwardMapRect(Rectangle sourceRect,
445: int sourceIndex) {
446:
447: if (sourceRect == null) {
448: throw new IllegalArgumentException(JaiI18N
449: .getString("Generic0"));
450: }
451:
452: if (sourceIndex != 0) { // this image only has one source
453: throw new IllegalArgumentException(JaiI18N
454: .getString("Generic1"));
455: }
456:
457: return warp.mapSourceRect(sourceRect);
458: }
459:
460: /**
461: * Returns the minimum bounding box of the region of the specified
462: * source to which a particular <code>Rectangle</code> of the
463: * destination will be mapped.
464: *
465: * @param destRect the <code>Rectangle</code> in destination coordinates.
466: * @param sourceIndex the index of the source image.
467: *
468: * @return a <code>Rectangle</code> indicating the source bounding box,
469: * or <code>null</code> if the bounding box is unknown.
470: *
471: * @throws IllegalArgumentException if <code>sourceIndex</code> is
472: * negative or greater than the index of the last source.
473: * @throws IllegalArgumentException if <code>destRect</code> is
474: * <code>null</code>.
475: *
476: * @since JAI 1.1
477: */
478: protected Rectangle backwardMapRect(Rectangle destRect,
479: int sourceIndex) {
480: if (destRect == null) {
481: throw new IllegalArgumentException(JaiI18N
482: .getString("Generic0"));
483: }
484:
485: if (sourceIndex != 0) { // this image only has one source
486: throw new IllegalArgumentException(JaiI18N
487: .getString("Generic1"));
488: }
489:
490: Rectangle wrect = warp.mapDestRect(destRect);
491:
492: return wrect == null ? getSource(0).getBounds() : wrect;
493: }
494:
495: /**
496: * Computes a tile. A new <code>WritableRaster</code> is created to
497: * represent the requested tile. Its width and height equals to this
498: * image's tile width and tile height respectively. This method
499: * assumes that the requested tile either intersects or is within
500: * the bounds of this image.
501: *
502: * <p> Whether or not this method performs source cobbling is determined
503: * by the <code>cobbleSources</code> variable set at construction time.
504: * If <code>cobbleSources</code> is <code>true</code>, cobbling is
505: * performed on the source for areas that intersect multiple tiles,
506: * and <code>computeRect(Raster[], WritableRaster, Rectangle)</code>
507: * is called to perform the actual computation. Otherwise,
508: * <code>computeRect(PlanarImage[], WritableRaster, Rectangle)</code>
509: * is called to perform the actual computation.
510: *
511: * @param tileX The X index of the tile.
512: * @param tileY The Y index of the tile.
513: *
514: * @return The tile as a <code>Raster</code>.
515: */
516: public Raster computeTile(int tileX, int tileY) {
517: // The origin of the tile.
518: Point org = new Point(tileXToX(tileX), tileYToY(tileY));
519:
520: // Create a new WritableRaster to represent this tile.
521: WritableRaster dest = createWritableRaster(sampleModel, org);
522:
523: // Find the intersection between this tile and the writable bounds.
524: Rectangle destRect = new Rectangle(org.x, org.y, tileWidth,
525: tileHeight).intersection(computableBounds);
526:
527: if (destRect.isEmpty()) {
528: if (setBackground) {
529: ImageUtil.fillBackground(dest, destRect,
530: backgroundValues);
531: }
532: return dest; // tile completely outside of computable bounds
533: }
534:
535: PlanarImage source = getSource(0);
536:
537: Rectangle srcRect = mapDestRect(destRect, 0);
538: if (!srcRect.intersects(source.getBounds())) {
539: if (setBackground) {
540: ImageUtil.fillBackground(dest, destRect,
541: backgroundValues);
542: }
543: return dest; // outside of source bounds
544: }
545:
546: // This image only has one source.
547: if (cobbleSources) {
548: Raster[] srcs = new Raster[1];
549: srcs[0] = extender != null ? source.getExtendedData(
550: srcRect, extender) : source.getData(srcRect);
551:
552: // Compute the destination tile.
553: computeRect(srcs, dest, destRect);
554:
555: // Recycle the source tile
556: if (source.overlapsMultipleTiles(srcRect)) {
557: recycleTile(srcs[0]);
558: }
559: } else {
560: PlanarImage[] srcs = { source };
561: computeRect(srcs, dest, destRect);
562: }
563:
564: return dest;
565: }
566: }
|