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 */
045package com.github.jaiimageio.impl.common;
046
047import java.awt.Point;
048import java.awt.Rectangle;
049import java.awt.image.ColorModel;
050import java.awt.image.Raster;
051import java.awt.image.RenderedImage;
052import java.awt.image.SampleModel;
053import java.awt.image.WritableRaster;
054import java.util.Enumeration;
055import java.util.Hashtable;
056import java.util.Iterator;
057import java.util.Vector;
058
059public 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(), getHeight());
139    }
140
141    /** Returns the width of a tile. */
142    public int getTileWidth() {
143        return tileWidth;
144    }
145
146    /** Returns the height of a tile. */
147    public int getTileHeight() {
148        return tileHeight;
149    }
150
151    /**
152     * Returns the X coordinate of the upper-left pixel of tile (0, 0).
153     */
154    public int getTileGridXOffset() {
155        return tileGridXOffset;
156    }
157
158    /**
159     * Returns the Y coordinate of the upper-left pixel of tile (0, 0).
160     */
161    public int getTileGridYOffset() {
162        return tileGridYOffset;
163    }
164
165    /**
166     * Returns the horizontal index of the leftmost column of tiles.
167     * getMinTileX() is implemented in terms of getMinX()
168     * and so does not need to be implemented by subclasses.
169     */
170    public int getMinTileX() {
171        return XToTileX(getMinX());
172    }
173
174    /**
175     * Returns the horizontal index of the rightmost column of tiles.
176     * getMaxTileX() is implemented in terms of getMaxX()
177     * and so does not need to be implemented by subclasses.
178     */
179    public int getMaxTileX() {
180        return XToTileX(getMaxX() - 1);
181    }
182
183    /**
184     * Returns the number of tiles along the tile grid in the
185     * horizontal direction.  getNumXTiles() is implemented in terms
186     * of getMinTileX() and getMaxTileX() and so does not need to be
187     * implemented by subclasses.
188     */
189    public int getNumXTiles() {
190        return getMaxTileX() - getMinTileX() + 1;
191    }
192
193    /**
194     * Returns the vertical index of the uppermost row of tiles.  getMinTileY()
195     * is implemented in terms of getMinY() and so does not need to be
196     * implemented by subclasses.
197     */
198    public int getMinTileY() {
199        return YToTileY(getMinY());
200    }
201
202    /**
203     * Returns the vertical index of the bottom row of tiles.  getMaxTileY()
204     * is implemented in terms of getMaxY() and so does not need to
205     * be implemented by subclasses.
206     */
207    public int getMaxTileY() {
208        return YToTileY(getMaxY() - 1);
209    }
210
211    /**
212     * Returns the number of tiles along the tile grid in the vertical
213     * direction.  getNumYTiles() is implemented in terms
214     * of getMinTileY() and getMaxTileY() and so does not need to be
215     * implemented by subclasses.
216     */
217    public int getNumYTiles() {
218        return getMaxTileY() - getMinTileY() + 1;
219    }
220
221    /** Returns the SampleModel of the image. */
222    public SampleModel getSampleModel() {
223        return sampleModel;
224    }
225
226    /** Returns the ColorModel of the image. */
227    public ColorModel getColorModel() {
228        return colorModel;
229    }
230
231    /**
232     * Gets a property from the property set of this image.  If the
233     * property name is not recognized,
234     * <code>java.awt.Image.UndefinedProperty</code> will be returned.
235     *
236     * @param name the name of the property to get, as a
237     * <code>String</code>.  @return a reference to the property
238     * <code>Object</code>, or the value
239     * <code>java.awt.Image.UndefinedProperty.</code>
240     */
241    public Object getProperty(String name) {
242        name = name.toLowerCase();
243        Object value = properties.get(name);
244        return value != null ? value : java.awt.Image.UndefinedProperty;
245    }
246
247    /**
248     * Returns a list of the properties recognized by this image.  If
249     * no properties are available, <code>null</code> will be
250     * returned.
251     *
252     * @return an array of <code>String</code>s representing valid
253     *         property names.
254     */
255    public String[] getPropertyNames() {
256        String[] names = null;
257
258        if(properties.size() > 0) {
259            names = new String[properties.size()];
260            int index = 0;
261
262            Enumeration e = properties.keys();
263            while (e.hasMoreElements()) {
264                String name = (String)e.nextElement();
265                names[index++] = name;
266            }
267        }
268
269        return names;
270    }
271
272    /**
273     * Returns an array of <code>String</code>s recognized as names by
274     * this property source that begin with the supplied prefix.  If
275     * no property names match, <code>null</code> will be returned.
276     * The comparison is done in a case-independent manner.
277     *
278     * <p> The default implementation calls
279     * <code>getPropertyNames()</code> and searches the list of names
280     * for matches.
281     *
282     * @return an array of <code>String</code>s giving the valid
283     * property names.
284     */
285    public String[] getPropertyNames(String prefix) {
286        String propertyNames[] = getPropertyNames();
287        if (propertyNames == null) {
288            return null;
289        }
290
291        prefix = prefix.toLowerCase();
292
293        Vector names = new Vector();
294        for (int i = 0; i < propertyNames.length; i++) {
295            if (propertyNames[i].startsWith(prefix)) {
296                names.addElement(propertyNames[i]);
297            }
298        }
299
300        if (names.size() == 0) {
301            return null;
302        }
303
304        // Copy the strings from the Vector over to a String array.
305        String prefixNames[] = new String[names.size()];
306        int count = 0;
307        for (Iterator it = names.iterator(); it.hasNext(); ) {
308            prefixNames[count++] = (String)it.next();
309        }
310
311        return prefixNames;
312    }
313
314    // Utility methods.
315
316    /**
317     * Converts a pixel's X coordinate into a horizontal tile index
318     * relative to a given tile grid layout specified by its X offset
319     * and tile width.
320     */
321    public static int XToTileX(int x, int tileGridXOffset, int tileWidth) {
322        x -= tileGridXOffset;
323        if (x < 0) {
324            x += 1 - tileWidth; // Force round to -infinity
325        }
326        return x/tileWidth;
327    }
328
329    /**
330     * Converts a pixel's Y coordinate into a vertical tile index
331     * relative to a given tile grid layout specified by its Y offset
332     * and tile height.
333     */
334    public static int YToTileY(int y, int tileGridYOffset, int tileHeight) {
335        y -= tileGridYOffset;
336        if (y < 0) {
337            y += 1 - tileHeight; // Force round to -infinity
338        }
339        return y/tileHeight;
340    }
341
342    /**
343     * Converts a pixel's X coordinate into a horizontal tile index.
344     * This is a convenience method.  No attempt is made to detect
345     * out-of-range coordinates.
346     *
347     * @param x the X coordinate of a pixel.
348     * @return the X index of the tile containing the pixel.
349     */
350    public int XToTileX(int x) {
351        return XToTileX(x, getTileGridXOffset(), getTileWidth());
352    }
353
354    /**
355     * Converts a pixel's Y coordinate into a vertical tile index.
356     * This is a convenience method.  No attempt is made to detect
357     * out-of-range coordinates.
358     *
359     * @param y the Y coordinate of a pixel.
360     * @return the Y index of the tile containing the pixel.
361     */
362    public int YToTileY(int y) {
363        return YToTileY(y, getTileGridYOffset(), getTileHeight());
364    }
365
366    /**
367     * Converts a horizontal tile index into the X coordinate of its
368     * upper left pixel relative to a given tile grid layout specified
369     * by its X offset and tile width.
370     */
371    public static int tileXToX(int tx, int tileGridXOffset, int tileWidth) {
372        return tx*tileWidth + tileGridXOffset;
373    }
374
375    /**
376     * Converts a vertical tile index into the Y coordinate of
377     * its upper left pixel relative to a given tile grid layout
378     * specified by its Y offset and tile height.
379     */
380    public static int tileYToY(int ty, int tileGridYOffset, int tileHeight) {
381        return ty*tileHeight + tileGridYOffset;
382    }
383
384    /**
385     * Converts a horizontal tile index into the X 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 tx the horizontal index of a tile.
390     * @return the X coordinate of the tile's upper left pixel.
391     */
392    public int tileXToX(int tx) {
393        return tx*tileWidth + tileGridXOffset;
394    }
395
396    /**
397     * Converts a vertical tile index into the Y coordinate of its
398     * upper left pixel.  This is a convenience method.  No attempt is made
399     * to detect out-of-range indices.
400     *
401     * @param ty the vertical index of a tile.
402     * @return the Y coordinate of the tile's upper left pixel.
403     */
404    public int tileYToY(int ty) {
405        return ty*tileHeight + tileGridYOffset;
406    }
407
408    public Vector getSources() {
409        return null;
410    }
411
412    /**
413     * Returns the entire image in a single Raster.  For images with
414     * multiple tiles this will require making a copy.
415     *
416     * <p> The returned Raster is semantically a copy.  This means
417     * that updates to the source image will not be reflected in the
418     * returned Raster.  For non-writable (immutable) source images,
419     * the returned value may be a reference to the image's internal
420     * data.  The returned Raster should be considered non-writable;
421     * any attempt to alter its pixel data (such as by casting it to
422     * WritableRaster or obtaining and modifying its DataBuffer) may
423     * result in undefined behavior.  The copyData method should be
424     * used if the returned Raster is to be modified.
425     *
426     * @return a Raster containing a copy of this image's data.
427     */
428    public Raster getData() {
429        Rectangle rect = new Rectangle(getMinX(), getMinY(),
430                                       getWidth(), getHeight());
431        return getData(rect);
432    }
433
434    /**
435     * Returns an arbitrary rectangular region of the RenderedImage
436     * in a Raster.  The rectangle of interest will be clipped against
437     * the image bounds.
438     *
439     * <p> The returned Raster is semantically a copy.  This means
440     * that updates to the source image will not be reflected in the
441     * returned Raster.  For non-writable (immutable) source images,
442     * the returned value may be a reference to the image's internal
443     * data.  The returned Raster should be considered non-writable;
444     * any attempt to alter its pixel data (such as by casting it to
445     * WritableRaster or obtaining and modifying its DataBuffer) may
446     * result in undefined behavior.  The copyData method should be
447     * used if the returned Raster is to be modified.
448     *
449     * @param bounds the region of the RenderedImage to be returned.
450     */
451    public Raster getData(Rectangle bounds) {
452        // Get the image bounds.
453        Rectangle imageBounds = getBounds();
454
455        // Check for parameter validity.
456        if(bounds == null) {
457            bounds = imageBounds;
458        } else if(!bounds.intersects(imageBounds)) {
459            throw new IllegalArgumentException(I18N.getString("SimpleRenderedImage0"));
460        }
461
462        // Determine tile limits for the prescribed bounds.
463        int startX = XToTileX(bounds.x);
464        int startY = YToTileY(bounds.y);
465        int endX = XToTileX(bounds.x + bounds.width - 1);
466        int endY = YToTileY(bounds.y + bounds.height - 1);
467
468        // If the bounds are contained in a single tile, return a child
469        // of that tile's Raster.
470        if ((startX == endX) && (startY == endY)) {
471            Raster tile = getTile(startX, startY);
472            return tile.createChild(bounds.x, bounds.y,
473                                    bounds.width, bounds.height,
474                                    bounds.x, bounds.y, null);
475        } else {
476            // Recalculate the tile limits if the data bounds are not a
477            // subset of the image bounds.
478            if(!imageBounds.contains(bounds)) {
479                Rectangle xsect = bounds.intersection(imageBounds);
480                startX = XToTileX(xsect.x);
481                startY = YToTileY(xsect.y);
482                endX = XToTileX(xsect.x + xsect.width - 1);
483                endY = YToTileY(xsect.y + xsect.height - 1);
484            }
485
486            // Create a WritableRaster of the desired size
487            SampleModel sm =
488                sampleModel.createCompatibleSampleModel(bounds.width,
489                                                        bounds.height);
490
491            // Translate it
492            WritableRaster dest =
493                Raster.createWritableRaster(sm, bounds.getLocation());
494
495            // Loop over the tiles in the intersection.
496            for (int j = startY; j <= endY; j++) {
497                for (int i = startX; i <= endX; i++) {
498                    // Retrieve the tile.
499                    Raster tile = getTile(i, j);
500
501                    // Create a child of the tile for the intersection of
502                    // the tile bounds and the bounds of the requested area.
503                    Rectangle tileRect = tile.getBounds();
504                    Rectangle intersectRect =
505                        bounds.intersection(tile.getBounds());
506                    Raster liveRaster = tile.createChild(intersectRect.x,
507                                                         intersectRect.y,
508                                                         intersectRect.width,
509                                                         intersectRect.height,
510                                                         intersectRect.x,
511                                                         intersectRect.y,
512                                                         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 =
550                sampleModel.createCompatibleSampleModel(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) ?
559            bounds : 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 =
575                    bounds.intersection(tile.getBounds());
576                Raster liveRaster = tile.createChild(intersectRect.x,
577                                                     intersectRect.y,
578                                                     intersectRect.width,
579                                                     intersectRect.height,
580                                                     intersectRect.x,
581                                                     intersectRect.y,
582                                                     null);
583
584                // Copy the data from the child.
585                dest.setRect(liveRaster);
586            }
587        }
588
589        return dest;
590    }
591}
592