001: /*
002: * $RCSfile: GeometricOpImage.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.2 $
009: * $Date: 2005/05/10 00:34:12 $
010: * $State: Exp $
011: */
012: package javax.media.jai;
013:
014: import java.awt.Rectangle;
015: import java.awt.RenderingHints;
016: import java.awt.geom.Point2D;
017: import java.awt.image.DataBuffer;
018: import java.awt.image.RenderedImage;
019: import java.awt.image.Raster;
020: import java.awt.image.WritableRaster;
021: import java.awt.Point;
022: import java.util.Map;
023: import java.util.Vector;
024: import javax.media.jai.util.CaselessStringKey;
025: import com.sun.media.jai.util.ImageUtil;
026:
027: /**
028: * An abstract base class for image operators that perform a geometric
029: * transformation of the source image.
030: *
031: * <p> The geometric relationship between the source and destination images
032: * will be determined by the specific behavior of the methods
033: * <code>backwardMapRect()</code> and <code>forwardMapRect()</code> the
034: * implementations of which must be provided by a subclass.
035: *
036: * <p> The location of the source pixel corresponding to a given destination
037: * pixel is determined by the aforementioned backward mapping transformation.
038: * The value of the destination pixel is then calculated by interpolating the
039: * values of a set of source pixels at locations in the vicinity of the
040: * backward mapped destination pixel location according to the requirements
041: * of a specified interpolation algorithm. In particular, a given
042: * destination pixel value may be interpolated from the neighborhood of source
043: * pixels beginning at (sx - leftPadding, sy - topPadding) and extending to
044: * (sx + rightPadding, sy + bottomPadding), inclusive, where (sx, sy) is
045: * the truncated backward mapped location of the destination pixel. The
046: * actual amount of padding required is determined by a supplied
047: * <code>Interpolation</code> object.
048: *
049: * <p> Since this operator might need a region around each source pixel in
050: * order to compute the destination pixel value, the border destination pixels
051: * might not be able to be computed without any source extension mechanism.
052: * The source extension method can be specified by supplying a
053: * <code>BorderExtender</code> object that will define the pixel values of the
054: * source outside the actual source area as a function of the actual source
055: * pixel values. If no extension is specified, the destination samples that
056: * cannot be computed will be written in the destination as the user-specified
057: * background values.
058: *
059: * @see BorderExtender
060: * @see Interpolation
061: * @see InterpolationNearest
062: * @see OpImage
063: *
064: * @since JAI 1.1
065: */
066: public abstract class GeometricOpImage extends OpImage {
067: /**
068: * The <code>Interpolation</code> object describing the subpixel
069: * interpolation method. This variable should not be null.
070: */
071: protected Interpolation interp;
072:
073: /**
074: * The <code>BorderExtender</code> describing the method by which
075: * source data are extended to provide sufficient context for
076: * calculation of the pixel values of backward mapped coordinates
077: * according to the interpolation method specified. If this
078: * variable is <code>null</code> no extension will be performed.
079: */
080: protected BorderExtender extender = null;
081:
082: /**
083: * The computable bounds of this image within which the pixels of the
084: * image may be computed and set. This is equal to the bounding box of
085: * the set of pixels the locations of which backward map to within the
086: * source image bounds contracted by the padding values required for
087: * interpolation.
088: *
089: * <p> The <code>GeometricOpImage</code> constructor sets the computable
090: * bounds to the image bounds. Subclasses should set this value to
091: * values reasonable for the operation in question.
092: */
093: protected Rectangle computableBounds;
094:
095: /**
096: * Indicates whether the background values are provided.
097: */
098: protected boolean setBackground;
099:
100: /**
101: * The user-specified background values.
102: */
103: protected double[] backgroundValues;
104:
105: /**
106: * The user-specified background values in integer.
107: */
108: protected int[] intBackgroundValues;
109:
110: /**
111: * Constructs a <code>GeometricOpImage</code>. The image layout
112: * (image bounds, tile grid layout, <code>SampleModel</code> and
113: * <code>ColorModel</code>) of the output are set in the standard
114: * way by the <code>OpImage</code> constructor.
115: *
116: * <p> Additional control over the image bounds, tile grid layout,
117: * <code>SampleModel</code>, and <code>ColorModel</code> may be
118: * obtained by specifying an <code>ImageLayout</code> parameter.
119: * This parameter will be passed to the superclass constructor
120: * unchanged.
121: *
122: * @param layout An <code>ImageLayout</code> containing the source
123: * bounds before padding, and optionally containing the
124: * tile grid layout, <code>SampleModel</code>, and
125: * <code>ColorModel</code>.
126: * @param sources The immediate sources of this image.
127: * @param configuration Configurable attributes of the image including
128: * configuration variables indexed by
129: * <code>RenderingHints.Key</code>s and image properties indexed
130: * by <code>String</code>s or <code>CaselessStringKey</code>s.
131: * This is simply forwarded to the superclass constructor.
132: * @param cobbleSources A <code>boolean</code> indicating whether
133: * <code>computeRect()</code> expects contiguous sources.
134: * @param extender A <code>BorderExtender</code>, or <code>null</code>.
135: * @param interp an <code>Interpolation</code> object to use for
136: * interpolation of the backward mapped pixel values at
137: * fractional positions. If the supplied parameter is
138: * <code>null</code> the corresponding instance variable
139: * will be initialized to an instance of
140: * <code>InterpolationNearest</code>.
141: *
142: * @throws IllegalArgumentException if <code>sources</code>
143: * is <code>null</code>.
144: * @throws IllegalArgumentException If <code>sources</code>
145: * is non-<code>null</code> and any object in
146: * <code>sources</code> is <code>null</code>.
147: * @throws IllegalArgumentException if combining the intersected
148: * source bounds with the layout parameter results in negative
149: * output width or height.
150: */
151: public GeometricOpImage(Vector sources, ImageLayout layout,
152: Map configuration, boolean cobbleSources,
153: BorderExtender extender, Interpolation interp) {
154: this (sources, layout, configuration, cobbleSources, extender,
155: interp, null);
156: }
157:
158: private static Map configHelper(Map configuration) {
159:
160: Map config;
161:
162: if (configuration == null) {
163: config = new RenderingHints(
164: JAI.KEY_REPLACE_INDEX_COLOR_MODEL, Boolean.TRUE);
165: } else {
166:
167: config = configuration;
168:
169: // If the user specified a value for this hint, we don't
170: // want to change that
171: if (!config.containsKey(JAI.KEY_REPLACE_INDEX_COLOR_MODEL)) {
172:
173: RenderingHints hints = new RenderingHints(null);
174: // This is effectively a clone of configuration
175: hints.putAll(configuration);
176: config = hints;
177: config.put(JAI.KEY_REPLACE_INDEX_COLOR_MODEL,
178: Boolean.TRUE);
179: }
180: }
181:
182: return config;
183: }
184:
185: /**
186: * Constructs a <code>GeometricOpImage</code>. The image layout
187: * (image bounds, tile grid layout, <code>SampleModel</code> and
188: * <code>ColorModel</code>) of the output are set in the standard
189: * way by the <code>OpImage</code> constructor.
190: *
191: * <p> Additional control over the image bounds, tile grid layout,
192: * <code>SampleModel</code>, and <code>ColorModel</code> may be
193: * obtained by specifying an <code>ImageLayout</code> parameter.
194: * This parameter will be passed to the superclass constructor
195: * unchanged.
196: *
197: * <p> A <code>RenderingHints</code> for
198: * <code>JAI.KEY_REPLACE_INDEX_COLOR_MODEL</code> with the value of
199: * <code>Boolean.TRUE</code> is automatically added to the given
200: * <code>configuration</code> and passed up to the superclass constructor
201: * so that geometric operations are performed on the pixel values instead
202: * of being performed on the indices into the color map for those
203: * operations whose source(s) have an <code>IndexColorModel</code>.
204: * This addition will take place only if a value for the
205: * <code>JAI.KEY_REPLACE_INDEX_COLOR_MODEL</code> has not already been
206: * provided by the user. Note that the <code>configuration</code> Map
207: * is cloned before the new hint is added to it. Regarding the value
208: * for the <code>JAI.KEY_REPLACE_INDEX_COLOR_MODEL</code>
209: * <code>RenderingHints</code>, the operator itself can be smart
210: * based on the parameters, i.e. while the default value for
211: * the <code>JAI.KEY_REPLACE_INDEX_COLOR_MODEL</code> is
212: * <code>Boolean.TRUE</code> for operations that extend this class,
213: * in some cases the operator could set the default.
214: *
215: * @param layout An <code>ImageLayout</code> containing the source
216: * bounds before padding, and optionally containing the
217: * tile grid layout, <code>SampleModel</code>, and
218: * <code>ColorModel</code>.
219: * @param sources The immediate sources of this image.
220: * @param configuration Configurable attributes of the image including
221: * configuration variables indexed by
222: * <code>RenderingHints.Key</code>s and image properties indexed
223: * by <code>String</code>s or <code>CaselessStringKey</code>s.
224: * This is simply forwarded to the superclass constructor.
225: * @param cobbleSources A <code>boolean</code> indicating whether
226: * <code>computeRect()</code> expects contiguous sources.
227: * @param extender A <code>BorderExtender</code>, or <code>null</code>.
228: * @param interp an <code>Interpolation</code> object to use for
229: * interpolation of the backward mapped pixel values at
230: * fractional positions. If the supplied parameter is
231: * <code>null</code> the corresponding instance variable
232: * will be initialized to an instance of
233: * <code>InterpolationNearest</code>.
234: * @param backgroundValues The user-specified background values. If the
235: * provided array length is smaller than the number of bands, all
236: * the bands will be filled with the first element of the array.
237: * If the provided array is null, set it to <code>new double[]{0.0}
238: * </code>.
239: *
240: * @throws IllegalArgumentException if <code>sources</code>
241: * is <code>null</code>.
242: * @throws IllegalArgumentException If <code>sources</code>
243: * is non-<code>null</code> and any object in
244: * <code>sources</code> is <code>null</code>.
245: * @throws IllegalArgumentException if combining the intersected
246: * source bounds with the layout parameter results in negative
247: * output width or height.
248: * @since JAI 1.1.2
249: */
250: public GeometricOpImage(Vector sources, ImageLayout layout,
251: Map configuration, boolean cobbleSources,
252: BorderExtender extender, Interpolation interp,
253: double[] backgroundValues) {
254: super (sources, layout, configHelper(configuration),
255: cobbleSources);
256:
257: this .extender = extender;
258: this .interp = interp != null ? interp
259: : new InterpolationNearest();
260:
261: if (backgroundValues == null)
262: backgroundValues = new double[] { 0.0 };
263:
264: this .setBackground = false;
265: for (int i = 0; i < backgroundValues.length; i++)
266: if (backgroundValues[i] != 0.0)
267: this .setBackground = true;
268:
269: this .backgroundValues = backgroundValues;
270: int numBands = getSampleModel().getNumBands();
271: if (backgroundValues.length < numBands) {
272: this .backgroundValues = new double[numBands];
273: for (int i = 0; i < numBands; i++)
274: this .backgroundValues[i] = backgroundValues[0];
275: }
276:
277: if (sampleModel.getDataType() <= DataBuffer.TYPE_INT) {
278: int length = this .backgroundValues.length;
279: intBackgroundValues = new int[length];
280: for (int i = 0; i < length; i++)
281: intBackgroundValues[i] = (int) this .backgroundValues[i];
282: }
283:
284: // Initialize computable bounds to the current image bounds.
285: computableBounds = getBounds();
286: }
287:
288: /**
289: * Retrieve the <code>Interpolation</code> object associated with
290: * this class instance. The object is returned by reference.
291: *
292: * @return The associated <code>Interpolation</code> object.
293: */
294: public Interpolation getInterpolation() {
295: return interp;
296: }
297:
298: /**
299: * Retrieve the <code>BorderExtender</code> object associated with
300: * this class instance. The object is returned by reference.
301: *
302: * @return The associated <code>BorderExtender</code> object
303: * or <code>null</code>.
304: */
305: public BorderExtender getBorderExtender() {
306: return extender;
307: }
308:
309: /**
310: * Computes the position in the specified source that best
311: * matches the supplied destination image position. If it
312: * is not possible to compute the requested position,
313: * <code>null</code> will be returned.
314: *
315: * <p>The implementation in this class returns the value of
316: * <code>pt</code> in the following code snippet:
317: *
318: * <pre>
319: * Rectangle destRect = new Rectangle((int)destPt.getX(),
320: * (int)destPt.getY(),
321: * 1, 1);
322: * Rectangle sourceRect = backwardMapRect(destRect, sourceIndex);
323: * Point2D pt = (Point2D)destPt.clone();
324: * pt.setLocation(sourceRect.x + (sourceRect.width - 1.0)/2.0,
325: * sourceRect.y + (sourceRect.height - 1.0)/2.0);
326: * </pre>
327: *
328: * Subclasses requiring different behavior should override this
329: * method.</p>
330: *
331: * @param destPt the position in destination image coordinates
332: * to map to source image coordinates.
333: * @param sourceIndex the index of the source image.
334: *
335: * @return a <code>Point2D</code> of the same class as
336: * <code>destPt</code> or <code>null</code>.
337: *
338: * @throws IllegalArgumentException if <code>destPt</code> is
339: * <code>null</code>.
340: * @throws IndexOutOfBoundsException if <code>sourceIndex</code> is
341: * negative or greater than or equal to the number of sources.
342: *
343: * @since JAI 1.1.2
344: */
345: public Point2D mapDestPoint(Point2D destPt, int sourceIndex) {
346: if (destPt == null) {
347: throw new IllegalArgumentException(JaiI18N
348: .getString("Generic0"));
349: } else if (sourceIndex < 0 || sourceIndex >= getNumSources()) {
350: throw new IndexOutOfBoundsException(JaiI18N
351: .getString("Generic1"));
352: }
353:
354: Rectangle destRect = new Rectangle((int) destPt.getX(),
355: (int) destPt.getY(), 1, 1);
356:
357: Rectangle sourceRect = backwardMapRect(destRect, sourceIndex);
358:
359: if (sourceRect == null) {
360: return null;
361: }
362:
363: Point2D pt = (Point2D) destPt.clone();
364: pt.setLocation(sourceRect.x + (sourceRect.width - 1.0) / 2.0,
365: sourceRect.y + (sourceRect.height - 1.0) / 2.0);
366:
367: return pt;
368: }
369:
370: /**
371: * Computes the position in the destination that best
372: * matches the supplied source image position. If it
373: * is not possible to compute the requested position,
374: * <code>null</code> will be returned.
375: *
376: * <p>The implementation in this class returns the value of
377: * <code>pt</code> in the following code snippet:
378: *
379: * <pre>
380: * Rectangle sourceRect = new Rectangle((int)sourcePt.getX(),
381: * (int)sourcePt.getY(),
382: * 1, 1);
383: * Rectangle destRect = forwardMapRect(sourceRect, sourceIndex);
384: * Point2D pt = (Point2D)sourcePt.clone();
385: * pt.setLocation(destRect.x + (destRect.width - 1.0)/2.0,
386: * destRect.y + (destRect.height - 1.0)/2.0);
387: * </pre>
388: *
389: * Subclasses requiring different behavior should override this
390: * method.</p>
391: *
392: * @param sourcePt the position in source image coordinates
393: * to map to destination image coordinates.
394: * @param sourceIndex the index of the source image.
395: *
396: * @return a <code>Point2D</code> of the same class as
397: * <code>sourcePt</code> or <code>null</code>.
398: *
399: * @throws IllegalArgumentException if <code>sourcePt</code> is
400: * <code>null</code>.
401: * @throws IndexOutOfBoundsException if <code>sourceIndex</code> is
402: * negative or greater than or equal to the number of sources.
403: *
404: * @since JAI 1.1.2
405: */
406: public Point2D mapSourcePoint(Point2D sourcePt, int sourceIndex) {
407: if (sourcePt == null) {
408: throw new IllegalArgumentException(JaiI18N
409: .getString("Generic0"));
410: } else if (sourceIndex < 0 || sourceIndex >= getNumSources()) {
411: throw new IndexOutOfBoundsException(JaiI18N
412: .getString("Generic1"));
413: }
414:
415: Rectangle sourceRect = new Rectangle((int) sourcePt.getX(),
416: (int) sourcePt.getY(), 1, 1);
417: Rectangle destRect = forwardMapRect(sourceRect, sourceIndex);
418:
419: if (destRect == null) {
420: return null;
421: }
422:
423: Point2D pt = (Point2D) sourcePt.clone();
424: pt.setLocation(destRect.x + (destRect.width - 1.0) / 2.0,
425: destRect.y + (destRect.height - 1.0) / 2.0);
426:
427: return pt;
428: }
429:
430: /**
431: * Returns the minimum bounding box of the region of the destination
432: * to which a particular <code>Rectangle</code> of the specified source
433: * will be mapped.
434: *
435: * <p> The integral source rectangle coordinates should be considered
436: * pixel indices. The "energy" of each pixel is defined to be
437: * concentrated in the continuous plane of pixels at an offset of
438: * (0.5, 0.5) from the index of the pixel. Forward mappings
439: * must take this (0.5, 0.5) pixel center into account. Thus
440: * given integral source pixel indices as input, the fractional
441: * destination location, as calculated by functions Xf(xSrc, ySrc),
442: * Yf(xSrc, ySrc), is given by:
443: * <pre>
444: *
445: * xDst = Xf(xSrc+0.5, ySrc+0.5) - 0.5
446: * yDst = Yf(xSrc+0.5, ySrc+0.5) - 0.5
447: *
448: * </pre>
449: *
450: * @param sourceRect the <code>Rectangle</code> in source coordinates.
451: * @param sourceIndex the index of the source image.
452: *
453: * @return a <code>Rectangle</code> indicating the destination
454: * bounding box, or <code>null</code> if the bounding box
455: * is unknown.
456: *
457: * @throws IllegalArgumentException if <code>sourceIndex</code> is
458: * negative or greater than the index of the last source.
459: * @throws IllegalArgumentException if <code>sourceRect</code> is
460: * <code>null</code>.
461: */
462: protected abstract Rectangle forwardMapRect(Rectangle sourceRect,
463: int sourceIndex);
464:
465: /**
466: * Returns the minimum bounding box of the region of the specified
467: * source to which a particular <code>Rectangle</code> of the
468: * destination will be mapped.
469: *
470: * <p> The integral destination rectangle coordinates should be considered
471: * pixel indices. The "energy" of each pixel is defined to be
472: * concentrated in the continuous plane of pixels at an offset of
473: * (0.5, 0.5) from the index of the pixel. Backward mappings
474: * must take this (0.5, 0.5) pixel center into account. Thus
475: * given integral destination pixel indices as input, the fractional
476: * source location, as calculated by functions Xb(xDst, yDst),
477: * Yb(xDst, yDst), is given by:
478: * <pre>
479: *
480: * xSrc = Xb(xDst+0.5, yDst+0.5) - 0.5
481: * ySrc = Yb(xDst+0.5, yDst+0.5) - 0.5
482: *
483: * </pre>
484: *
485: * @param destRect the <code>Rectangle</code> in destination coordinates.
486: * @param sourceIndex the index of the source image.
487: *
488: * @return a <code>Rectangle</code> indicating the source bounding box,
489: * or <code>null</code> if the bounding box is unknown.
490: *
491: * @throws IllegalArgumentException if <code>sourceIndex</code> is
492: * negative or greater than the index of the last source.
493: * @throws IllegalArgumentException if <code>destRect</code> is
494: * <code>null</code>.
495: */
496: protected abstract Rectangle backwardMapRect(Rectangle destRect,
497: int sourceIndex);
498:
499: /**
500: * Returns a conservative estimate of the destination region that
501: * can potentially be affected by the pixels of a rectangle of a
502: * given source. The supplied <code>Rectangle</code> will first be
503: * contracted according to the <code>Interpolation</code> object
504: * characteristics and then forward mapped into destination coordinate
505: * space using <code>forwardMapRect()</code>. The resulting
506: * <code>Rectangle</code> is <u>not</u> clipped to the destination
507: * image bounds.
508: *
509: * @param sourceRect the <code>Rectangle</code> in source coordinates.
510: * @param sourceIndex the index of the source image.
511: * @return a <code>Rectangle</code> indicating the potentially affected
512: * destination region. This will equal the destination bounds
513: * if <code>forwardMapRect()</code> returns null.
514: *
515: * @throws IllegalArgumentException if <code>sourceIndex</code> is
516: * negative or greater than the index of the last source.
517: * @throws IllegalArgumentException if <code>sourceRect</code> is
518: * <code>null</code>.
519: */
520: public Rectangle mapSourceRect(Rectangle sourceRect, int sourceIndex) {
521:
522: if (sourceRect == null) {
523: throw new IllegalArgumentException(JaiI18N
524: .getString("Generic0"));
525: }
526:
527: if (sourceIndex < 0 || sourceIndex >= getNumSources()) {
528: throw new IllegalArgumentException(JaiI18N
529: .getString("Generic1"));
530: }
531:
532: // Cache left and top padding.
533: int lpad = interp.getLeftPadding();
534: int tpad = interp.getTopPadding();
535:
536: // Shrink the source Rectangle according to the Interpolation.
537: Rectangle srcRect = (Rectangle) sourceRect.clone();
538: srcRect.x += lpad;
539: srcRect.y += tpad;
540: srcRect.width -= (lpad + interp.getRightPadding());
541: srcRect.height -= (tpad + interp.getBottomPadding());
542:
543: // Map the source Rectangle into destination space.
544: Rectangle destRect = forwardMapRect(srcRect, sourceIndex);
545:
546: // Return Rectangle or destination bounds.
547: return destRect == null ? getBounds() : destRect;
548: }
549:
550: /**
551: * Returns a conservative estimate of the region of a specified
552: * source that is required in order to compute the pixels of a
553: * given destination rectangle. The supplied <code>Rectangle</code>
554: * will first be backward mapped into source coordinate space using
555: * <code>backwardMapRect()</code> and then the resulting context
556: * will be modified according to the <code>Interpolation</code>
557: * object characteristics. The resulting <code>Rectangle</code>
558: * is <u>not</u> clipped to the source image bounds.
559: *
560: * @param destRect the <code>Rectangle</code> in destination coordinates.
561: * @param sourceIndex the index of the source image.
562: *
563: * @return a <code>Rectangle</code> indicating the required source region.
564: * This will equal the bounds of the respective source
565: * if <code>backwardMapRect()</code> returns null.
566: *
567: * @throws IllegalArgumentException if <code>sourceIndex</code> is
568: * negative or greater than the index of the last source.
569: * @throws IllegalArgumentException if <code>destRect</code> is
570: * <code>null</code>.
571: */
572: public Rectangle mapDestRect(Rectangle destRect, int sourceIndex) {
573:
574: if (destRect == null) {
575: throw new IllegalArgumentException(JaiI18N
576: .getString("Generic0"));
577: }
578:
579: if (sourceIndex < 0 || sourceIndex >= getNumSources()) {
580: throw new IllegalArgumentException(JaiI18N
581: .getString("Generic1"));
582: }
583:
584: // Map the destination Rectangle into the appropriate source space.
585: Rectangle sourceRect = backwardMapRect(destRect, sourceIndex);
586: if (sourceRect == null) {
587: return getSource(sourceIndex).getBounds();
588: }
589:
590: // Cache left and top padding.
591: int lpad = interp.getLeftPadding();
592: int tpad = interp.getTopPadding();
593:
594: // Return padded Rectangle.
595: return new Rectangle(sourceRect.x - lpad, sourceRect.y - tpad,
596: sourceRect.width + lpad + interp.getRightPadding(),
597: sourceRect.height + tpad + interp.getBottomPadding());
598: }
599:
600: /**
601: * Computes a tile. A new <code>WritableRaster</code> is created to
602: * represent the requested tile. Its width and height are equal to this
603: * image's tile width and tile height respectively. If the requested
604: * tile lies outside of the image's boundary, or if the backward mapped
605: * and padded tile region does not intersect all sources, the created
606: * raster is returned with all of its pixels set to 0.
607: *
608: * <p> Whether or not this method performs source cobbling is determined
609: * by the <code>cobbleSources</code> variable set at construction time.
610: * If <code>cobbleSources</code> is <code>true</code>, cobbling is
611: * performed on the source for areas that intersect multiple tiles,
612: * and <code>computeRect(Raster[], WritableRaster, Rectangle)</code>
613: * is called to perform the actual computation. Otherwise,
614: * <code>computeRect(PlanarImage[], WritableRaster, Rectangle)</code>
615: * is called to perform the actual computation.
616: *
617: * @param tileX The X index of the tile.
618: * @param tileY The Y index of the tile.
619: *
620: * @return The tile as a <code>Raster</code>.
621: */
622: public Raster computeTile(int tileX, int tileY) {
623: // The origin of the tile.
624: Point org = new Point(tileXToX(tileX), tileYToY(tileY));
625:
626: // Create a new WritableRaster to represent this tile.
627: WritableRaster dest = createWritableRaster(sampleModel, org);
628:
629: // Find the intersection between this tile and the bounds.
630: Rectangle destRect = getTileRect(tileX, tileY).intersection(
631: getBounds());
632:
633: if (destRect.isEmpty()) {
634: if (setBackground) {
635: ImageUtil.fillBackground(dest, destRect,
636: backgroundValues);
637: }
638:
639: return dest; // tile outside of destination bounds
640: }
641:
642: int numSources = getNumSources();
643: if (cobbleSources) {
644: Raster[] rasterSources = new Raster[numSources];
645:
646: // Cobble areas
647: for (int i = 0; i < numSources; i++) {
648: PlanarImage source = getSource(i);
649:
650: Rectangle srcBounds = source.getBounds();
651: Rectangle srcRect = mapDestRect(destRect, i);
652: if (srcRect == null) {
653: // Set to source bounds.
654: srcRect = srcBounds;
655: } else {
656: if (extender == null
657: && !srcBounds.contains(srcRect)) {
658: // Clip to source bounds.
659: srcRect = srcBounds.intersection(srcRect);
660: }
661: if (!srcRect.intersects(srcBounds)) {
662: // Outside of source bounds.
663: if (setBackground) {
664: ImageUtil.fillBackground(dest, destRect,
665: backgroundValues);
666: }
667: return dest;
668: }
669: }
670:
671: rasterSources[i] = extender != null ? source
672: .getExtendedData(srcRect, extender) : source
673: .getData(srcRect);
674: }
675:
676: computeRect(rasterSources, dest, destRect);
677:
678: for (int i = 0; i < numSources; i++) {
679: Raster sourceData = rasterSources[i];
680: if (sourceData != null) {
681: PlanarImage source = getSourceImage(i);
682:
683: // Recycle the source tile
684: if (source.overlapsMultipleTiles(sourceData
685: .getBounds())) {
686: recycleTile(sourceData);
687: }
688: }
689: }
690: } else {
691: PlanarImage[] imageSources = new PlanarImage[numSources];
692:
693: for (int i = 0; i < numSources; i++) {
694: imageSources[i] = getSource(i);
695: }
696: computeRect(imageSources, dest, destRect);
697: }
698:
699: return dest;
700: }
701: }
|