blob: 04d545ba72b2b5fad9e8f701a50bf28d3fc447e7 [file] [log] [blame]
/*
* Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.imageio.plugins.common;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.RenderedImage;
import java.awt.image.ColorModel;
import java.awt.image.SampleModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Vector;
public abstract class SimpleRenderedImage implements RenderedImage {
/** The X coordinate of the image's upper-left pixel. */
protected int minX;
/** The Y coordinate of the image's upper-left pixel. */
protected int minY;
/** The image's width in pixels. */
protected int width;
/** The image's height in pixels. */
protected int height;
/** The width of a tile. */
protected int tileWidth;
/** The height of a tile. */
protected int tileHeight;
/** The X coordinate of the upper-left pixel of tile (0, 0). */
protected int tileGridXOffset = 0;
/** The Y coordinate of the upper-left pixel of tile (0, 0). */
protected int tileGridYOffset = 0;
/** The image's SampleModel. */
protected SampleModel sampleModel;
/** The image's ColorModel. */
protected ColorModel colorModel;
/** The image's sources, stored in a Vector. */
protected Vector<RenderedImage> sources = new Vector<RenderedImage>();
/** A Hashtable containing the image properties. */
protected Hashtable<String,Object> properties = new Hashtable<String,Object>();
/** Returns the X coordinate of the leftmost column of the image. */
public int getMinX() {
return minX;
}
/**
* Returns the X coordinate of the column immediatetely to the
* right of the rightmost column of the image. getMaxX() is
* implemented in terms of getMinX() and getWidth() and so does
* not need to be implemented by subclasses.
*/
public final int getMaxX() {
return getMinX() + getWidth();
}
/** Returns the X coordinate of the uppermost row of the image. */
public int getMinY() {
return minY;
}
/**
* Returns the Y coordinate of the row immediately below the
* bottom row of the image. getMaxY() is implemented in terms of
* getMinY() and getHeight() and so does not need to be
* implemented by subclasses.
*/
public final int getMaxY() {
return getMinY() + getHeight();
}
/** Returns the width of the image. */
public int getWidth() {
return width;
}
/** Returns the height of the image. */
public int getHeight() {
return height;
}
/** Returns a Rectangle indicating the image bounds. */
public Rectangle getBounds() {
return new Rectangle(getMinX(), getMinY(), getWidth(), getHeight());
}
/** Returns the width of a tile. */
public int getTileWidth() {
return tileWidth;
}
/** Returns the height of a tile. */
public int getTileHeight() {
return tileHeight;
}
/**
* Returns the X coordinate of the upper-left pixel of tile (0, 0).
*/
public int getTileGridXOffset() {
return tileGridXOffset;
}
/**
* Returns the Y coordinate of the upper-left pixel of tile (0, 0).
*/
public int getTileGridYOffset() {
return tileGridYOffset;
}
/**
* Returns the horizontal index of the leftmost column of tiles.
* getMinTileX() is implemented in terms of getMinX()
* and so does not need to be implemented by subclasses.
*/
public int getMinTileX() {
return XToTileX(getMinX());
}
/**
* Returns the horizontal index of the rightmost column of tiles.
* getMaxTileX() is implemented in terms of getMaxX()
* and so does not need to be implemented by subclasses.
*/
public int getMaxTileX() {
return XToTileX(getMaxX() - 1);
}
/**
* Returns the number of tiles along the tile grid in the
* horizontal direction. getNumXTiles() is implemented in terms
* of getMinTileX() and getMaxTileX() and so does not need to be
* implemented by subclasses.
*/
public int getNumXTiles() {
return getMaxTileX() - getMinTileX() + 1;
}
/**
* Returns the vertical index of the uppermost row of tiles. getMinTileY()
* is implemented in terms of getMinY() and so does not need to be
* implemented by subclasses.
*/
public int getMinTileY() {
return YToTileY(getMinY());
}
/**
* Returns the vertical index of the bottom row of tiles. getMaxTileY()
* is implemented in terms of getMaxY() and so does not need to
* be implemented by subclasses.
*/
public int getMaxTileY() {
return YToTileY(getMaxY() - 1);
}
/**
* Returns the number of tiles along the tile grid in the vertical
* direction. getNumYTiles() is implemented in terms
* of getMinTileY() and getMaxTileY() and so does not need to be
* implemented by subclasses.
*/
public int getNumYTiles() {
return getMaxTileY() - getMinTileY() + 1;
}
/** Returns the SampleModel of the image. */
public SampleModel getSampleModel() {
return sampleModel;
}
/** Returns the ColorModel of the image. */
public ColorModel getColorModel() {
return colorModel;
}
/**
* Gets a property from the property set of this image. If the
* property name is not recognized,
* <code>java.awt.Image.UndefinedProperty</code> will be returned.
*
* @param name the name of the property to get, as a
* <code>String</code>. @return a reference to the property
* <code>Object</code>, or the value
* <code>java.awt.Image.UndefinedProperty.</code>
*/
public Object getProperty(String name) {
name = name.toLowerCase();
Object value = properties.get(name);
return value != null ? value : java.awt.Image.UndefinedProperty;
}
/**
* Returns a list of the properties recognized by this image. If
* no properties are available, <code>null</code> will be
* returned.
*
* @return an array of <code>String</code>s representing valid
* property names.
*/
public String[] getPropertyNames() {
String[] names = null;
if(properties.size() > 0) {
names = new String[properties.size()];
int index = 0;
Enumeration<String> e = properties.keys();
while (e.hasMoreElements()) {
String name = e.nextElement();
names[index++] = name;
}
}
return names;
}
/**
* Returns an array of <code>String</code>s recognized as names by
* this property source that begin with the supplied prefix. If
* no property names match, <code>null</code> will be returned.
* The comparison is done in a case-independent manner.
*
* <p> The default implementation calls
* <code>getPropertyNames()</code> and searches the list of names
* for matches.
*
* @return an array of <code>String</code>s giving the valid
* property names.
*/
public String[] getPropertyNames(String prefix) {
String propertyNames[] = getPropertyNames();
if (propertyNames == null) {
return null;
}
prefix = prefix.toLowerCase();
Vector<String> names = new Vector<String>();
for (int i = 0; i < propertyNames.length; i++) {
if (propertyNames[i].startsWith(prefix)) {
names.addElement(propertyNames[i]);
}
}
if (names.size() == 0) {
return null;
}
// Copy the strings from the Vector over to a String array.
String prefixNames[] = new String[names.size()];
int count = 0;
for (Iterator<String> it = names.iterator(); it.hasNext(); ) {
prefixNames[count++] = it.next();
}
return prefixNames;
}
// Utility methods.
/**
* Converts a pixel's X coordinate into a horizontal tile index
* relative to a given tile grid layout specified by its X offset
* and tile width.
*/
public static int XToTileX(int x, int tileGridXOffset, int tileWidth) {
x -= tileGridXOffset;
if (x < 0) {
x += 1 - tileWidth; // Force round to -infinity
}
return x/tileWidth;
}
/**
* Converts a pixel's Y coordinate into a vertical tile index
* relative to a given tile grid layout specified by its Y offset
* and tile height.
*/
public static int YToTileY(int y, int tileGridYOffset, int tileHeight) {
y -= tileGridYOffset;
if (y < 0) {
y += 1 - tileHeight; // Force round to -infinity
}
return y/tileHeight;
}
/**
* Converts a pixel's X coordinate into a horizontal tile index.
* This is a convenience method. No attempt is made to detect
* out-of-range coordinates.
*
* @param x the X coordinate of a pixel.
* @return the X index of the tile containing the pixel.
*/
public int XToTileX(int x) {
return XToTileX(x, getTileGridXOffset(), getTileWidth());
}
/**
* Converts a pixel's Y coordinate into a vertical tile index.
* This is a convenience method. No attempt is made to detect
* out-of-range coordinates.
*
* @param y the Y coordinate of a pixel.
* @return the Y index of the tile containing the pixel.
*/
public int YToTileY(int y) {
return YToTileY(y, getTileGridYOffset(), getTileHeight());
}
/**
* Converts a horizontal tile index into the X coordinate of its
* upper left pixel relative to a given tile grid layout specified
* by its X offset and tile width.
*/
public static int tileXToX(int tx, int tileGridXOffset, int tileWidth) {
return tx*tileWidth + tileGridXOffset;
}
/**
* Converts a vertical tile index into the Y coordinate of
* its upper left pixel relative to a given tile grid layout
* specified by its Y offset and tile height.
*/
public static int tileYToY(int ty, int tileGridYOffset, int tileHeight) {
return ty*tileHeight + tileGridYOffset;
}
/**
* Converts a horizontal tile index into the X coordinate of its
* upper left pixel. This is a convenience method. No attempt is made
* to detect out-of-range indices.
*
* @param tx the horizontal index of a tile.
* @return the X coordinate of the tile's upper left pixel.
*/
public int tileXToX(int tx) {
return tx*tileWidth + tileGridXOffset;
}
/**
* Converts a vertical tile index into the Y coordinate of its
* upper left pixel. This is a convenience method. No attempt is made
* to detect out-of-range indices.
*
* @param ty the vertical index of a tile.
* @return the Y coordinate of the tile's upper left pixel.
*/
public int tileYToY(int ty) {
return ty*tileHeight + tileGridYOffset;
}
public Vector<RenderedImage> getSources() {
return null;
}
/**
* Returns the entire image in a single Raster. For images with
* multiple tiles this will require making a copy.
*
* <p> The returned Raster is semantically a copy. This means
* that updates to the source image will not be reflected in the
* returned Raster. For non-writable (immutable) source images,
* the returned value may be a reference to the image's internal
* data. The returned Raster should be considered non-writable;
* any attempt to alter its pixel data (such as by casting it to
* WritableRaster or obtaining and modifying its DataBuffer) may
* result in undefined behavior. The copyData method should be
* used if the returned Raster is to be modified.
*
* @return a Raster containing a copy of this image's data.
*/
public Raster getData() {
Rectangle rect = new Rectangle(getMinX(), getMinY(),
getWidth(), getHeight());
return getData(rect);
}
/**
* Returns an arbitrary rectangular region of the RenderedImage
* in a Raster. The rectangle of interest will be clipped against
* the image bounds.
*
* <p> The returned Raster is semantically a copy. This means
* that updates to the source image will not be reflected in the
* returned Raster. For non-writable (immutable) source images,
* the returned value may be a reference to the image's internal
* data. The returned Raster should be considered non-writable;
* any attempt to alter its pixel data (such as by casting it to
* WritableRaster or obtaining and modifying its DataBuffer) may
* result in undefined behavior. The copyData method should be
* used if the returned Raster is to be modified.
*
* @param bounds the region of the RenderedImage to be returned.
*/
public Raster getData(Rectangle bounds) {
// Get the image bounds.
Rectangle imageBounds = getBounds();
// Check for parameter validity.
if(bounds == null) {
bounds = imageBounds;
} else if(!bounds.intersects(imageBounds)) {
throw new IllegalArgumentException("The provided region doesn't intersect with the image bounds.");
}
// Determine tile limits for the prescribed bounds.
int startX = XToTileX(bounds.x);
int startY = YToTileY(bounds.y);
int endX = XToTileX(bounds.x + bounds.width - 1);
int endY = YToTileY(bounds.y + bounds.height - 1);
// If the bounds are contained in a single tile, return a child
// of that tile's Raster.
if ((startX == endX) && (startY == endY)) {
Raster tile = getTile(startX, startY);
return tile.createChild(bounds.x, bounds.y,
bounds.width, bounds.height,
bounds.x, bounds.y, null);
} else {
// Recalculate the tile limits if the data bounds are not a
// subset of the image bounds.
if(!imageBounds.contains(bounds)) {
Rectangle xsect = bounds.intersection(imageBounds);
startX = XToTileX(xsect.x);
startY = YToTileY(xsect.y);
endX = XToTileX(xsect.x + xsect.width - 1);
endY = YToTileY(xsect.y + xsect.height - 1);
}
// Create a WritableRaster of the desired size
SampleModel sm =
sampleModel.createCompatibleSampleModel(bounds.width,
bounds.height);
// Translate it
WritableRaster dest =
Raster.createWritableRaster(sm, bounds.getLocation());
// Loop over the tiles in the intersection.
for (int j = startY; j <= endY; j++) {
for (int i = startX; i <= endX; i++) {
// Retrieve the tile.
Raster tile = getTile(i, j);
// Create a child of the tile for the intersection of
// the tile bounds and the bounds of the requested area.
Rectangle tileRect = tile.getBounds();
Rectangle intersectRect =
bounds.intersection(tile.getBounds());
Raster liveRaster = tile.createChild(intersectRect.x,
intersectRect.y,
intersectRect.width,
intersectRect.height,
intersectRect.x,
intersectRect.y,
null);
// Copy the data from the child.
dest.setRect(liveRaster);
}
}
return dest;
}
}
/**
* Copies an arbitrary rectangular region of the RenderedImage
* into a caller-supplied WritableRaster. The region to be
* computed is determined by clipping the bounds of the supplied
* WritableRaster against the bounds of the image. The supplied
* WritableRaster must have a SampleModel that is compatible with
* that of the image.
*
* <p> If the raster argument is null, the entire image will
* be copied into a newly-created WritableRaster with a SampleModel
* that is compatible with that of the image.
*
* @param dest a WritableRaster to hold the returned portion of
* the image.
* @return a reference to the supplied WritableRaster, or to a
* new WritableRaster if the supplied one was null.
*/
public WritableRaster copyData(WritableRaster dest) {
// Get the image bounds.
Rectangle imageBounds = getBounds();
Rectangle bounds;
if (dest == null) {
// Create a WritableRaster for the entire image.
bounds = imageBounds;
Point p = new Point(minX, minY);
SampleModel sm =
sampleModel.createCompatibleSampleModel(width, height);
dest = Raster.createWritableRaster(sm, p);
} else {
bounds = dest.getBounds();
}
// Determine tile limits for the intersection of the prescribed
// bounds with the image bounds.
Rectangle xsect = imageBounds.contains(bounds) ?
bounds : bounds.intersection(imageBounds);
int startX = XToTileX(xsect.x);
int startY = YToTileY(xsect.y);
int endX = XToTileX(xsect.x + xsect.width - 1);
int endY = YToTileY(xsect.y + xsect.height - 1);
// Loop over the tiles in the intersection.
for (int j = startY; j <= endY; j++) {
for (int i = startX; i <= endX; i++) {
// Retrieve the tile.
Raster tile = getTile(i, j);
// Create a child of the tile for the intersection of
// the tile bounds and the bounds of the requested area.
Rectangle tileRect = tile.getBounds();
Rectangle intersectRect =
bounds.intersection(tile.getBounds());
Raster liveRaster = tile.createChild(intersectRect.x,
intersectRect.y,
intersectRect.width,
intersectRect.height,
intersectRect.x,
intersectRect.y,
null);
// Copy the data from the child.
dest.setRect(liveRaster);
}
}
return dest;
}
}