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