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