001: /*
002: * $RCSfile: SimpleRenderedImage.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:38 $
010: * $State: Exp $
011: */
012: package com.sun.media.jai.codecimpl;
013:
014: import java.awt.Point;
015: import java.awt.Rectangle;
016: import java.awt.image.Raster;
017: import java.awt.image.WritableRaster;
018: import java.awt.image.RenderedImage;
019: import java.awt.image.ColorModel;
020: import java.awt.image.SampleModel;
021: import java.util.Enumeration;
022: import java.util.Hashtable;
023: import java.util.Iterator;
024: import java.util.Vector;
025: import com.sun.media.jai.codecimpl.util.RasterFactory;
026:
027: /**
028: * A simple class implemented the <code>RenderedImage</code>
029: * interface. Only the <code>getTile()</code> method needs to be
030: * implemented by subclasses. The instance variables must also be
031: * filled in properly.
032: *
033: * <p> Normally in JAI <code>PlanarImage</code> is used for this
034: * purpose, but in the interest of making
035: * <code>com.sun.media.jai.codec</code> and
036: * <code>com.sun.media.jai.codecimpl</code> be as modular as possible the
037: * use of <code>PlanarImage</code> has been avoided.
038: */
039: public abstract class SimpleRenderedImage implements RenderedImage {
040:
041: /** The X coordinate of the image's upper-left pixel. */
042: protected int minX;
043:
044: /** The Y coordinate of the image's upper-left pixel. */
045: protected int minY;
046:
047: /** The image's width in pixels. */
048: protected int width;
049:
050: /** The image's height in pixels. */
051: protected int height;
052:
053: /** The width of a tile. */
054: protected int tileWidth;
055:
056: /** The height of a tile. */
057: protected int tileHeight;
058:
059: /** The X coordinate of the upper-left pixel of tile (0, 0). */
060: protected int tileGridXOffset = 0;
061:
062: /** The Y coordinate of the upper-left pixel of tile (0, 0). */
063: protected int tileGridYOffset = 0;
064:
065: /** The image's SampleModel. */
066: protected SampleModel sampleModel = null;
067:
068: /** The image's ColorModel. */
069: protected ColorModel colorModel = null;
070:
071: /** The image's sources, stored in a Vector. */
072: protected Vector sources = new Vector();
073:
074: /** A Hashtable containing the image properties. */
075: protected Hashtable properties = new Hashtable();
076:
077: public SimpleRenderedImage() {
078: }
079:
080: /** Returns the X coordinate of the leftmost column of the image. */
081: public int getMinX() {
082: return minX;
083: }
084:
085: /**
086: * Returns the X coordinate of the column immediatetely to the
087: * right of the rightmost column of the image. getMaxX() is
088: * implemented in terms of getMinX() and getWidth() and so does
089: * not need to be implemented by subclasses.
090: */
091: public final int getMaxX() {
092: return getMinX() + getWidth();
093: }
094:
095: /** Returns the X coordinate of the uppermost row of the image. */
096: public int getMinY() {
097: return minY;
098: }
099:
100: /**
101: * Returns the Y coordinate of the row immediately below the
102: * bottom row of the image. getMaxY() is implemented in terms of
103: * getMinY() and getHeight() and so does not need to be
104: * implemented by subclasses.
105: */
106: public final int getMaxY() {
107: return getMinY() + getHeight();
108: }
109:
110: /** Returns the width of the image. */
111: public int getWidth() {
112: return width;
113: }
114:
115: /** Returns the height of the image. */
116: public int getHeight() {
117: return height;
118: }
119:
120: /** Returns a Rectangle indicating the image bounds. */
121: public Rectangle getBounds() {
122: return new Rectangle(getMinX(), getMinY(), getWidth(),
123: getHeight());
124: }
125:
126: /** Returns the width of a tile. */
127: public int getTileWidth() {
128: return tileWidth;
129: }
130:
131: /** Returns the height of a tile. */
132: public int getTileHeight() {
133: return tileHeight;
134: }
135:
136: /**
137: * Returns the X coordinate of the upper-left pixel of tile (0, 0).
138: */
139: public int getTileGridXOffset() {
140: return tileGridXOffset;
141: }
142:
143: /**
144: * Returns the Y coordinate of the upper-left pixel of tile (0, 0).
145: */
146: public int getTileGridYOffset() {
147: return tileGridYOffset;
148: }
149:
150: /**
151: * Returns the horizontal index of the leftmost column of tiles.
152: * getMinTileX() is implemented in terms of getMinX()
153: * and so does not need to be implemented by subclasses.
154: */
155: public int getMinTileX() {
156: return XToTileX(getMinX());
157: }
158:
159: /**
160: * Returns the horizontal index of the rightmost column of tiles.
161: * getMaxTileX() is implemented in terms of getMaxX()
162: * and so does not need to be implemented by subclasses.
163: */
164: public int getMaxTileX() {
165: return XToTileX(getMaxX() - 1);
166: }
167:
168: /**
169: * Returns the number of tiles along the tile grid in the
170: * horizontal direction. getNumXTiles() is implemented in terms
171: * of getMinTileX() and getMaxTileX() and so does not need to be
172: * implemented by subclasses.
173: */
174: public int getNumXTiles() {
175: return getMaxTileX() - getMinTileX() + 1;
176: }
177:
178: /**
179: * Returns the vertical index of the uppermost row of tiles. getMinTileY()
180: * is implemented in terms of getMinY() and so does not need to be
181: * implemented by subclasses.
182: */
183: public int getMinTileY() {
184: return YToTileY(getMinY());
185: }
186:
187: /**
188: * Returns the vertical index of the bottom row of tiles. getMaxTileY()
189: * is implemented in terms of getMaxY() and so does not need to
190: * be implemented by subclasses.
191: */
192: public int getMaxTileY() {
193: return YToTileY(getMaxY() - 1);
194: }
195:
196: /**
197: * Returns the number of tiles along the tile grid in the vertical
198: * direction. getNumYTiles() is implemented in terms
199: * of getMinTileY() and getMaxTileY() and so does not need to be
200: * implemented by subclasses.
201: */
202: public int getNumYTiles() {
203: return getMaxTileY() - getMinTileY() + 1;
204: }
205:
206: /** Returns the SampleModel of the image. */
207: public SampleModel getSampleModel() {
208: return sampleModel;
209: }
210:
211: /** Returns the ColorModel of the image. */
212: public ColorModel getColorModel() {
213: return colorModel;
214: }
215:
216: /**
217: * Gets a property from the property set of this image. If the
218: * property name is not recognized,
219: * <code>java.awt.Image.UndefinedProperty</code> will be returned.
220: *
221: * @param name the name of the property to get, as a
222: * <code>String</code>. @return a reference to the property
223: * <code>Object</code>, or the value
224: * <code>java.awt.Image.UndefinedProperty.</code>
225: */
226: public Object getProperty(String name) {
227: name = name.toLowerCase();
228: Object value = properties.get(name);
229: return value != null ? value : java.awt.Image.UndefinedProperty;
230: }
231:
232: /**
233: * Returns a list of the properties recognized by this image. If
234: * no properties are available, <code>null</code> will be
235: * returned.
236: *
237: * @return an array of <code>String</code>s representing valid
238: * property names.
239: */
240: public String[] getPropertyNames() {
241: String[] names = null;
242:
243: if (properties.size() > 0) {
244: names = new String[properties.size()];
245: int index = 0;
246:
247: Enumeration e = properties.keys();
248: while (e.hasMoreElements()) {
249: String name = (String) e.nextElement();
250: names[index++] = name;
251: }
252: }
253:
254: return names;
255: }
256:
257: /**
258: * Returns an array of <code>String</code>s recognized as names by
259: * this property source that begin with the supplied prefix. If
260: * no property names match, <code>null</code> will be returned.
261: * The comparison is done in a case-independent manner.
262: *
263: * <p> The default implementation calls
264: * <code>getPropertyNames()</code> and searches the list of names
265: * for matches.
266: *
267: * @return an array of <code>String</code>s giving the valid
268: * property names.
269: */
270: public String[] getPropertyNames(String prefix) {
271: String propertyNames[] = getPropertyNames();
272: if (propertyNames == null) {
273: return null;
274: }
275:
276: prefix = prefix.toLowerCase();
277:
278: Vector names = new Vector();
279: for (int i = 0; i < propertyNames.length; i++) {
280: if (propertyNames[i].startsWith(prefix)) {
281: names.addElement(propertyNames[i]);
282: }
283: }
284:
285: if (names.size() == 0) {
286: return null;
287: }
288:
289: // Copy the strings from the Vector over to a String array.
290: String prefixNames[] = new String[names.size()];
291: int count = 0;
292: for (Iterator it = names.iterator(); it.hasNext();) {
293: prefixNames[count++] = (String) it.next();
294: }
295:
296: return prefixNames;
297: }
298:
299: // Utility methods.
300:
301: /**
302: * Converts a pixel's X coordinate into a horizontal tile index
303: * relative to a given tile grid layout specified by its X offset
304: * and tile width.
305: */
306: public static int XToTileX(int x, int tileGridXOffset, int tileWidth) {
307: x -= tileGridXOffset;
308: if (x < 0) {
309: x += 1 - tileWidth; // Force round to -infinity
310: }
311: return x / tileWidth;
312: }
313:
314: /**
315: * Converts a pixel's Y coordinate into a vertical tile index
316: * relative to a given tile grid layout specified by its Y offset
317: * and tile height.
318: */
319: public static int YToTileY(int y, int tileGridYOffset,
320: int tileHeight) {
321: y -= tileGridYOffset;
322: if (y < 0) {
323: y += 1 - tileHeight; // Force round to -infinity
324: }
325: return y / tileHeight;
326: }
327:
328: /**
329: * Converts a pixel's X coordinate into a horizontal tile index.
330: * This is a convenience method. No attempt is made to detect
331: * out-of-range coordinates.
332: *
333: * @param x the X coordinate of a pixel.
334: * @return the X index of the tile containing the pixel.
335: */
336: public int XToTileX(int x) {
337: return XToTileX(x, getTileGridXOffset(), getTileWidth());
338: }
339:
340: /**
341: * Converts a pixel's Y coordinate into a vertical tile index.
342: * This is a convenience method. No attempt is made to detect
343: * out-of-range coordinates.
344: *
345: * @param y the Y coordinate of a pixel.
346: * @return the Y index of the tile containing the pixel.
347: */
348: public int YToTileY(int y) {
349: return YToTileY(y, getTileGridYOffset(), getTileHeight());
350: }
351:
352: /**
353: * Converts a horizontal tile index into the X coordinate of its
354: * upper left pixel relative to a given tile grid layout specified
355: * by its X offset and tile width.
356: */
357: public static int tileXToX(int tx, int tileGridXOffset,
358: int tileWidth) {
359: return tx * tileWidth + tileGridXOffset;
360: }
361:
362: /**
363: * Converts a vertical tile index into the Y coordinate of
364: * its upper left pixel relative to a given tile grid layout
365: * specified by its Y offset and tile height.
366: */
367: public static int tileYToY(int ty, int tileGridYOffset,
368: int tileHeight) {
369: return ty * tileHeight + tileGridYOffset;
370: }
371:
372: /**
373: * Converts a horizontal tile index into the X coordinate of its
374: * upper left pixel. This is a convenience method. No attempt is made
375: * to detect out-of-range indices.
376: *
377: * @param tx the horizontal index of a tile.
378: * @return the X coordinate of the tile's upper left pixel.
379: */
380: public int tileXToX(int tx) {
381: return tx * tileWidth + tileGridXOffset;
382: }
383:
384: /**
385: * Converts a vertical tile index into the Y coordinate of its
386: * upper left pixel. This is a convenience method. No attempt is made
387: * to detect out-of-range indices.
388: *
389: * @param ty the vertical index of a tile.
390: * @return the Y coordinate of the tile's upper left pixel.
391: */
392: public int tileYToY(int ty) {
393: return ty * tileHeight + tileGridYOffset;
394: }
395:
396: public Vector getSources() {
397: return null;
398: }
399:
400: /**
401: * Returns the entire image in a single Raster. For images with
402: * multiple tiles this will require making a copy.
403: *
404: * <p> The returned Raster is semantically a copy. This means
405: * that updates to the source image will not be reflected in the
406: * returned Raster. For non-writable (immutable) source images,
407: * the returned value may be a reference to the image's internal
408: * data. The returned Raster should be considered non-writable;
409: * any attempt to alter its pixel data (such as by casting it to
410: * WritableRaster or obtaining and modifying its DataBuffer) may
411: * result in undefined behavior. The copyData method should be
412: * used if the returned Raster is to be modified.
413: *
414: * @return a Raster containing a copy of this image's data.
415: */
416: public Raster getData() {
417: Rectangle rect = new Rectangle(getMinX(), getMinY(),
418: getWidth(), getHeight());
419: return getData(rect);
420: }
421:
422: /**
423: * Returns an arbitrary rectangular region of the RenderedImage
424: * in a Raster. The rectangle of interest will be clipped against
425: * the image bounds.
426: *
427: * <p> The returned Raster is semantically a copy. This means
428: * that updates to the source image will not be reflected in the
429: * returned Raster. For non-writable (immutable) source images,
430: * the returned value may be a reference to the image's internal
431: * data. The returned Raster should be considered non-writable;
432: * any attempt to alter its pixel data (such as by casting it to
433: * WritableRaster or obtaining and modifying its DataBuffer) may
434: * result in undefined behavior. The copyData method should be
435: * used if the returned Raster is to be modified.
436: *
437: * @param bounds the region of the RenderedImage to be returned.
438: */
439: public Raster getData(Rectangle bounds) {
440: // Get the image bounds.
441: Rectangle imageBounds = getBounds();
442:
443: // Check for parameter validity.
444: if (bounds == null) {
445: bounds = imageBounds;
446: } else if (!bounds.intersects(imageBounds)) {
447: throw new IllegalArgumentException(JaiI18N
448: .getString("SimpleRenderedImage0"));
449: }
450:
451: // Determine tile limits for the prescribed bounds.
452: int startX = XToTileX(bounds.x);
453: int startY = YToTileY(bounds.y);
454: int endX = XToTileX(bounds.x + bounds.width - 1);
455: int endY = YToTileY(bounds.y + bounds.height - 1);
456:
457: // If the bounds are contained in a single tile, return a child
458: // of that tile's Raster.
459: if ((startX == endX) && (startY == endY)) {
460: Raster tile = getTile(startX, startY);
461: return tile.createChild(bounds.x, bounds.y, bounds.width,
462: bounds.height, bounds.x, bounds.y, null);
463: } else {
464: // Recalculate the tile limits if the data bounds are not a
465: // subset of the image bounds.
466: if (!imageBounds.contains(bounds)) {
467: Rectangle xsect = bounds.intersection(imageBounds);
468: startX = XToTileX(xsect.x);
469: startY = YToTileY(xsect.y);
470: endX = XToTileX(xsect.x + xsect.width - 1);
471: endY = YToTileY(xsect.y + xsect.height - 1);
472: }
473:
474: // Create a WritableRaster of the desired size
475: SampleModel sm = sampleModel.createCompatibleSampleModel(
476: bounds.width, bounds.height);
477:
478: // Translate it
479: WritableRaster dest = RasterFactory.createWritableRaster(
480: sm, bounds.getLocation());
481:
482: // Loop over the tiles in the intersection.
483: for (int j = startY; j <= endY; j++) {
484: for (int i = startX; i <= endX; i++) {
485: // Retrieve the tile.
486: Raster tile = getTile(i, j);
487:
488: // Create a child of the tile for the intersection of
489: // the tile bounds and the bounds of the requested area.
490: Rectangle tileRect = tile.getBounds();
491: Rectangle intersectRect = bounds.intersection(tile
492: .getBounds());
493: Raster liveRaster = tile.createChild(
494: intersectRect.x, intersectRect.y,
495: intersectRect.width, intersectRect.height,
496: intersectRect.x, intersectRect.y, null);
497:
498: // Copy the data from the child.
499: dest.setRect(liveRaster);
500: }
501: }
502:
503: return dest;
504: }
505: }
506:
507: /**
508: * Copies an arbitrary rectangular region of the RenderedImage
509: * into a caller-supplied WritableRaster. The region to be
510: * computed is determined by clipping the bounds of the supplied
511: * WritableRaster against the bounds of the image. The supplied
512: * WritableRaster must have a SampleModel that is compatible with
513: * that of the image.
514: *
515: * <p> If the raster argument is null, the entire image will
516: * be copied into a newly-created WritableRaster with a SampleModel
517: * that is compatible with that of the image.
518: *
519: * @param dest a WritableRaster to hold the returned portion of
520: * the image.
521: * @return a reference to the supplied WritableRaster, or to a
522: * new WritableRaster if the supplied one was null.
523: */
524: public WritableRaster copyData(WritableRaster dest) {
525: // Get the image bounds.
526: Rectangle imageBounds = getBounds();
527:
528: Rectangle bounds;
529: if (dest == null) {
530: // Create a WritableRaster for the entire image.
531: bounds = imageBounds;
532: Point p = new Point(minX, minY);
533: SampleModel sm = sampleModel.createCompatibleSampleModel(
534: width, height);
535: dest = RasterFactory.createWritableRaster(sm, p);
536: } else {
537: bounds = dest.getBounds();
538: }
539:
540: // Determine tile limits for the intersection of the prescribed
541: // bounds with the image bounds.
542: Rectangle xsect = imageBounds.contains(bounds) ? bounds
543: : bounds.intersection(imageBounds);
544: int startX = XToTileX(xsect.x);
545: int startY = YToTileY(xsect.y);
546: int endX = XToTileX(xsect.x + xsect.width - 1);
547: int endY = YToTileY(xsect.y + xsect.height - 1);
548:
549: // Loop over the tiles in the intersection.
550: for (int j = startY; j <= endY; j++) {
551: for (int i = startX; i <= endX; i++) {
552: // Retrieve the tile.
553: Raster tile = getTile(i, j);
554:
555: // Create a child of the tile for the intersection of
556: // the tile bounds and the bounds of the requested area.
557: Rectangle tileRect = tile.getBounds();
558: Rectangle intersectRect = bounds.intersection(tile
559: .getBounds());
560: Raster liveRaster = tile.createChild(intersectRect.x,
561: intersectRect.y, intersectRect.width,
562: intersectRect.height, intersectRect.x,
563: intersectRect.y, null);
564:
565: // Copy the data from the child.
566: dest.setRect(liveRaster);
567: }
568: }
569:
570: return dest;
571: }
572: }
|