001: /*
002: * $RCSfile: AreaOpImage.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:03 $
010: * $State: Exp $
011: */
012: package javax.media.jai;
013:
014: import java.awt.Rectangle;
015: import java.awt.RenderingHints;
016: import java.awt.image.RenderedImage;
017: import java.awt.image.renderable.ParameterBlock;
018: import java.awt.image.Raster;
019: import java.awt.image.WritableRaster;
020: import java.awt.Point;
021: import java.util.Map;
022: import java.util.Vector;
023: import javax.media.jai.util.CaselessStringKey;
024:
025: /**
026: * An abstract base class for image operators that require only a
027: * fixed rectangular source region around a source pixel in order to
028: * compute each destination pixel.
029: *
030: * <p> The source and the destination images will occupy the same
031: * region of the plane. A given destination pixel (x, y) may be
032: * computed from the neighborhood of source pixels beginning at (x -
033: * leftPadding, y - topPadding) and extending to (x + rightPadding, y
034: * + bottomPadding) inclusive.
035: *
036: * <p> Since this operator needs a region around the source pixel in
037: * order to compute the destination pixel, the border destination pixels
038: * cannot be computed without any source extension. The source extension
039: * can be specified by supplying a BorderExtender that will define the
040: * pixel values of the source outside the actual source area.
041: *
042: * <p> If no extension is specified, the destination samples that
043: * cannot be computed will be written in the destination as zero. If
044: * the source image begins at pixel (minX, minY) and has width w and
045: * height h, the result of performing an area operation will be an
046: * image beginning at minX, minY, and having a width of w and a height
047: * of h, with the area being computed and written starting at (minX +
048: * leftPadding, minY + topPadding) and having width Math.max(w -
049: * leftPadding - rightPadding, 0) and height Math.max(h - topPadding
050: * - bottomPadding, 0).
051: *
052: * <p> A <code>RenderingHints</code> for
053: * <code>JAI.KEY_REPLACE_INDEX_COLOR_MODEL</code> with the value of
054: * <code>Boolean.TRUE</code> will automatically be added to the given
055: * <code>configuration</code> and passed up to the superclass constructor
056: * so that area operations are performed on the pixel values instead
057: * of being performed on the indices into the color map for those
058: * operations whose source(s) have an <code>IndexColorModel</code>.
059: * This addition will only take place if a value for the
060: * <code>JAI.KEY_REPLACE_INDEX_COLOR_MODEL</code> has not already been
061: * provided by the user. Note that the <code>configuration</code> Map
062: * is cloned before the new hint is added to it. Regarding the value
063: * for the <code>JAI.KEY_REPLACE_INDEX_COLOR_MODEL</code>
064: * <code>RenderingHints</code>, the operator itself can be smart
065: * based on the parameters, i.e. while the default value for
066: * the <code>JAI.KEY_REPLACE_INDEX_COLOR_MODEL</code> is
067: * <code>Boolean.TRUE</code> for operations that extend this class,
068: * in some cases the operator could set the default.
069: *
070: * @see BorderExtender
071: */
072: public abstract class AreaOpImage extends OpImage {
073: /**
074: * The number of source pixels needed to the left of the central pixel.
075: */
076: protected int leftPadding;
077:
078: /**
079: * The number of source pixels needed to the right of the central pixel.
080: */
081: protected int rightPadding;
082:
083: /** The number of source pixels needed above the central pixel. */
084: protected int topPadding;
085:
086: /** The number of source pixels needed below the central pixel. */
087: protected int bottomPadding;
088:
089: /** The BorderExtender, may be null. */
090: protected BorderExtender extender = null;
091:
092: private Rectangle theDest;
093:
094: /** Verify that a specified bounds overlaps that of the source image. */
095: private static ImageLayout layoutHelper(ImageLayout layout,
096: RenderedImage source) {
097: // If at least one of the bounds variables is set then
098: // check the overlap with the source image.
099: if (layout != null
100: && source != null
101: && (layout.getValidMask() & (ImageLayout.MIN_X_MASK
102: | ImageLayout.MIN_Y_MASK
103: | ImageLayout.WIDTH_MASK | ImageLayout.HEIGHT_MASK)) != 0) {
104: // Get the source bounds.
105: Rectangle sourceRect = new Rectangle(source.getMinX(),
106: source.getMinY(), source.getWidth(), source
107: .getHeight());
108:
109: // Create destination bounds defaulting un-set variables to
110: // their respective source values as is done in the superclass
111: // OpImage constructor.
112: Rectangle dstRect = new Rectangle(layout.getMinX(source),
113: layout.getMinY(source), layout.getWidth(source),
114: layout.getHeight(source));
115:
116: // Check for empty intersection.
117: if (dstRect.intersection(sourceRect).isEmpty()) {
118: throw new IllegalArgumentException(JaiI18N
119: .getString("AreaOpImage0"));
120: }
121: }
122:
123: return layout;
124: }
125:
126: private static Map configHelper(Map configuration) {
127:
128: Map config;
129:
130: if (configuration == null) {
131: config = new RenderingHints(
132: JAI.KEY_REPLACE_INDEX_COLOR_MODEL, Boolean.TRUE);
133: } else {
134:
135: config = configuration;
136:
137: // If the user specified a value for this hint, we don't
138: // want to change that
139: if (!config.containsKey(JAI.KEY_REPLACE_INDEX_COLOR_MODEL)) {
140: RenderingHints hints = new RenderingHints(null);
141: // This is effectively a clone of configuration
142: hints.putAll(configuration);
143: config = hints;
144: config.put(JAI.KEY_REPLACE_INDEX_COLOR_MODEL,
145: Boolean.TRUE);
146: }
147: }
148:
149: return config;
150: }
151:
152: /**
153: * Constructs an <code>AreaOpImage</code>. The layout variables
154: * are set in the standard way by the <code>OpImage</code> constructor.
155: *
156: * <p> Additional control over the image bounds, tile grid layout,
157: * <code>SampleModel</code>, and <code>ColorModel</code> may be
158: * obtained by specifying an <code>ImageLayout</code> parameter.
159: * If the image bounds are specified but do not overlap the source
160: * bounds then an <code>IllegalArgumentException</code> will be thrown.
161: * This parameter will be passed to the superclass constructor
162: * unchanged.
163: *
164: * <p> A <code>RenderingHints</code> for
165: * <code>JAI.KEY_REPLACE_INDEX_COLOR_MODEL</code> with the value of
166: * <code>Boolean.TRUE</code> will automatically be added to the given
167: * <code>configuration</code> and passed up to the superclass constructor
168: * so that area operations are performed on the pixel values instead
169: * of being performed on the indices into the color map for those
170: * operations whose source(s) have an <code>IndexColorModel</code>.
171: * This addition will only take place if a value for the
172: * <code>JAI.KEY_REPLACE_INDEX_COLOR_MODEL</code> has not already been
173: * provided by the user. Note that the <code>configuration</code> Map
174: * is cloned before the new hint is added to it.
175: *
176: * @param source A <code>RenderedImage</code>.
177: * @param layout An <code>ImageLayout</code> containing the source
178: * dimensions before padding, and optionally containing the
179: * tile grid layout, <code>SampleModel</code>, and
180: * <code>ColorModel</code>.
181: * @param configuration Configurable attributes of the image including
182: * configuration variables indexed by
183: * <code>RenderingHints.Key</code>s and image properties indexed
184: * by <code>String</code>s or <code>CaselessStringKey</code>s.
185: * This is simply forwarded to the superclass constructor.
186: * @param cobbleSources A <code>boolean</code> indicating whether
187: * <code>computeRect()</code> expects contiguous sources.
188: * @param extender A BorderExtender, or null.
189: * @param leftPadding The desired left padding.
190: * @param rightPadding The desired right padding.
191: * @param topPadding The desired top padding.
192: * @param bottomPadding The desired bottom padding.
193: *
194: * @throws IllegalArgumentException if <code>source</code>
195: * is <code>null</code>.
196: * @throws IllegalArgumentException if the user-specified bounds do
197: * intersect the source bounds.
198: *
199: * @since JAI 1.1
200: */
201: public AreaOpImage(RenderedImage source, ImageLayout layout,
202: Map configuration, boolean cobbleSources,
203: BorderExtender extender, int leftPadding, int rightPadding,
204: int topPadding, int bottomPadding) {
205: super (
206: vectorize(source), // vectorize() checks for null source.
207: layoutHelper(layout, source),
208: configHelper(configuration), cobbleSources);
209:
210: this .extender = extender;
211: this .leftPadding = leftPadding;
212: this .rightPadding = rightPadding;
213: this .topPadding = topPadding;
214: this .bottomPadding = bottomPadding;
215:
216: if (extender == null) {
217:
218: int d_x0 = getMinX() + leftPadding;
219: int d_y0 = getMinY() + topPadding;
220:
221: int d_w = getWidth() - leftPadding - rightPadding;
222: d_w = Math.max(d_w, 0);
223:
224: int d_h = getHeight() - topPadding - bottomPadding;
225: d_h = Math.max(d_h, 0);
226:
227: theDest = new Rectangle(d_x0, d_y0, d_w, d_h);
228: } else {
229: theDest = getBounds();
230: }
231: }
232:
233: /**
234: * Returns the number of pixels needed to the left of the central pixel.
235: *
236: * @return The left padding factor.
237: */
238: public int getLeftPadding() {
239: return leftPadding;
240: }
241:
242: /**
243: * Returns the number of pixels needed to the right of the central pixel.
244: *
245: * @return The right padding factor.
246: */
247: public int getRightPadding() {
248: return rightPadding;
249: }
250:
251: /** Returns the number of pixels needed above the central pixel.
252: *
253: * @return The top padding factor.
254: */
255: public int getTopPadding() {
256: return topPadding;
257: }
258:
259: /** Returns the number of pixels needed below the central pixel.
260: *
261: * @return The bottom padding factor.
262: */
263: public int getBottomPadding() {
264: return bottomPadding;
265: }
266:
267: /**
268: * Retrieve the <code>BorderExtender</code> object associated with
269: * this class instance. The object is returned by reference.
270: *
271: * @return The associated <code>BorderExtender</code> object
272: * or <code>null</code>.
273: *
274: * @since JAI 1.1
275: */
276: public BorderExtender getBorderExtender() {
277: return extender;
278: }
279:
280: /**
281: * Returns a conservative estimate of the destination region that
282: * can potentially be affected by the pixels of a rectangle of a
283: * given source. The resulting <code>Rectangle</code> is <u>not</u>
284: * clipped to the destination image bounds.
285: *
286: * @param sourceRect the <code>Rectangle</code> in source coordinates.
287: * @param sourceIndex the index of the source image.
288: * @return a <code>Rectangle</code> indicating the potentially affected
289: * destination region, or <code>null</code> if the region is
290: * unknown.
291: *
292: * @throws IllegalArgumentException if <code>sourceIndex</code> is
293: * negative or greater than the index of the last source.
294: * @throws IllegalArgumentException if <code>sourceRect</code> is
295: * <code>null</code>.
296: */
297: public Rectangle mapSourceRect(Rectangle sourceRect, int sourceIndex) {
298:
299: if (sourceRect == null) {
300: throw new IllegalArgumentException(JaiI18N
301: .getString("Generic0"));
302: }
303:
304: if (sourceIndex < 0 || sourceIndex >= getNumSources()) {
305: throw new IllegalArgumentException(JaiI18N
306: .getString("Generic1"));
307: }
308:
309: int lpad = getLeftPadding();
310: int rpad = getRightPadding();
311: int tpad = getTopPadding();
312: int bpad = getBottomPadding();
313:
314: return new Rectangle(sourceRect.x + lpad, sourceRect.y + tpad,
315: sourceRect.width - lpad - rpad, sourceRect.height
316: - tpad - bpad);
317: }
318:
319: /**
320: * Returns a conservative estimate of the region of a specified
321: * source that is required in order to compute the pixels of a
322: * given destination rectangle. The resulting <code>Rectangle</code>
323: * is <u>not</u> clipped to the source image bounds.
324: *
325: * @param destRect the <code>Rectangle</code> in destination coordinates.
326: * @param sourceIndex the index of the source image.
327: *
328: * @return a <code>Rectangle</code> indicating the required source region.
329: *
330: * @throws IllegalArgumentException if <code>sourceIndex</code> is
331: * negative or greater than the index of the last source.
332: * @throws IllegalArgumentException if <code>destRect</code> is
333: * <code>null</code>.
334: */
335: public Rectangle mapDestRect(Rectangle destRect, int sourceIndex) {
336: if (destRect == null) {
337: throw new IllegalArgumentException(JaiI18N
338: .getString("Generic0"));
339: }
340:
341: if (sourceIndex < 0 || sourceIndex >= getNumSources()) {
342: throw new IllegalArgumentException(JaiI18N
343: .getString("Generic1"));
344: }
345:
346: int lpad = getLeftPadding();
347: int rpad = getRightPadding();
348: int tpad = getTopPadding();
349: int bpad = getBottomPadding();
350:
351: return new Rectangle(destRect.x - lpad, destRect.y - tpad,
352: destRect.width + lpad + rpad, destRect.height + tpad
353: + bpad);
354: }
355:
356: /**
357: * Computes a tile. If source cobbling was requested at
358: * construction time, the source tile boundaries are overlayed
359: * onto the destination, cobbling is performed for areas that
360: * intersect multiple source tiles, and
361: * <code>computeRect(Raster[], WritableRaster, Rectangle)</code>
362: * is called for each of the resulting regions. Otherwise,
363: * <code>computeRect(PlanarImage[], WritableRaster,
364: * Rectangle)</code> is called once to compute the entire active
365: * area of the tile.
366: *
367: * <p> The image bounds may be larger than the bounds of the
368: * source image. In this case, samples for which there are no
369: * no corresponding sources are set to zero.
370: *
371: * @param tileX The X index of the tile.
372: * @param tileY The Y index of the tile.
373: *
374: * @return The tile as a <code>Raster</code>.
375: */
376: public Raster computeTile(int tileX, int tileY) {
377: if (!cobbleSources) {
378: return super .computeTile(tileX, tileY);
379: }
380:
381: /* Create a new WritableRaster to represent this tile. */
382: Point org = new Point(tileXToX(tileX), tileYToY(tileY));
383: WritableRaster dest = createWritableRaster(sampleModel, org);
384:
385: /* Clip output rectangle to image bounds. */
386: Rectangle rect = new Rectangle(org.x, org.y, sampleModel
387: .getWidth(), sampleModel.getHeight());
388:
389: Rectangle destRect = rect.intersection(theDest);
390: if ((destRect.width <= 0) || (destRect.height <= 0)) {
391: return dest;
392: }
393:
394: /* account for padding in srcRectangle */
395: PlanarImage s = getSource(0);
396: // Fix 4639755: Area operations throw exception for
397: // destination extending beyond source bounds
398: // The default dest image area is the same as the source
399: // image area. However, when an ImageLayout hint is set,
400: // this might be not true. So the destRect should be the
401: // intersection of the provided rectangle, the destination
402: // bounds and the source bounds.
403: destRect = destRect.intersection(s.getBounds());
404: Rectangle srcRect = new Rectangle(destRect);
405: srcRect.x -= getLeftPadding();
406: srcRect.width += getLeftPadding() + getRightPadding();
407: srcRect.y -= getTopPadding();
408: srcRect.height += getTopPadding() + getBottomPadding();
409:
410: /*
411: * The tileWidth and tileHeight of the source image
412: * may differ from this tileWidth and tileHeight.
413: */
414: IntegerSequence srcXSplits = new IntegerSequence();
415: IntegerSequence srcYSplits = new IntegerSequence();
416:
417: // there is only one source for an AreaOpImage
418: s.getSplits(srcXSplits, srcYSplits, srcRect);
419:
420: // Initialize new sequences of X splits.
421: IntegerSequence xSplits = new IntegerSequence(destRect.x,
422: destRect.x + destRect.width);
423:
424: xSplits.insert(destRect.x);
425: xSplits.insert(destRect.x + destRect.width);
426:
427: srcXSplits.startEnumeration();
428: while (srcXSplits.hasMoreElements()) {
429: int xsplit = srcXSplits.nextElement();
430: int lsplit = xsplit - getLeftPadding();
431: int rsplit = xsplit + getRightPadding();
432: xSplits.insert(lsplit);
433: xSplits.insert(rsplit);
434: }
435:
436: // Initialize new sequences of Y splits.
437: IntegerSequence ySplits = new IntegerSequence(destRect.y,
438: destRect.y + destRect.height);
439:
440: ySplits.insert(destRect.y);
441: ySplits.insert(destRect.y + destRect.height);
442:
443: srcYSplits.startEnumeration();
444: while (srcYSplits.hasMoreElements()) {
445: int ysplit = srcYSplits.nextElement();
446: int tsplit = ysplit - getBottomPadding();
447: int bsplit = ysplit + getTopPadding();
448: ySplits.insert(tsplit);
449: ySplits.insert(bsplit);
450: }
451:
452: /*
453: * Divide destRect into sub rectangles based on the source splits,
454: * and compute each sub rectangle separately.
455: */
456: int x1, x2, y1, y2;
457: Raster[] sources = new Raster[1];
458:
459: ySplits.startEnumeration();
460: for (y1 = ySplits.nextElement(); ySplits.hasMoreElements(); y1 = y2) {
461: y2 = ySplits.nextElement();
462:
463: int h = y2 - y1;
464: int py1 = y1 - getTopPadding();
465: int py2 = y2 + getBottomPadding();
466: int ph = py2 - py1;
467:
468: xSplits.startEnumeration();
469: for (x1 = xSplits.nextElement(); xSplits.hasMoreElements(); x1 = x2) {
470: x2 = xSplits.nextElement();
471:
472: int w = x2 - x1;
473: int px1 = x1 - getLeftPadding();
474: int px2 = x2 + getRightPadding();
475: int pw = px2 - px1;
476:
477: // Fetch the padded src rectangle
478: Rectangle srcSubRect = new Rectangle(px1, py1, pw, ph);
479: sources[0] = (extender != null) ? s.getExtendedData(
480: srcSubRect, extender) : s.getData(srcSubRect);
481:
482: // Make a destRectangle
483: Rectangle dstSubRect = new Rectangle(x1, y1, w, h);
484: computeRect(sources, dest, dstSubRect);
485:
486: // Recycle the source tile
487: if (s.overlapsMultipleTiles(srcSubRect)) {
488: recycleTile(sources[0]);
489: }
490: }
491: }
492: return dest;
493: }
494: }
|