/*
* $RCSfile: PointOpImage.java,v $
*
* Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
*
* Use is subject to license terms.
*
* $Revision: 1.1 $
* $Date: 2005/02/11 04:57:15 $
* $State: Exp $
*/
package com.lightcrafts.mediax.jai;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.ColorModel;
import java.awt.image.ComponentSampleModel;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
import java.awt.image.MultiPixelPackedSampleModel;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.awt.image.SinglePixelPackedSampleModel;
import java.awt.image.WritableRaster;
import java.awt.image.WritableRenderedImage;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Vector;
import java.security.AccessController;
import java.security.PrivilegedAction;
import com.lightcrafts.media.jai.util.ImageUtil;
import com.lightcrafts.media.jai.util.JDKWorkarounds;
/**
* An abstract base class for image operators that require only the
* (x, y) pixel from each source image in order to compute the
* destination pixel (x, y).
*
* <p> <code>PointOpImage</code> is intended as a convenient
* superclass for <code>OpImage</code>s that only need to look at each
* destination pixel's corresponding source pixels. Some examples are
* lookup, contrast adjustment, pixel arithmetic, and color space
* conversion.
*
* @see OpImage
*/
public abstract class PointOpImage extends OpImage {
/* Flag indicating that dispose() has been invoked. */
private boolean isDisposed = false;
/* Flag indicating whether the various flags have been set. */
private boolean areFieldsInitialized = false;
/* Flag indicating that in-place compatibility should be checked. */
private boolean checkInPlaceOperation = false;
/* Flag indicating whether in-place operation is enabled. */
private boolean isInPlaceEnabled = false;
/// BEGIN: Variable fields used only when in-place operation is enabled.
/* The first source cast to a WritableRenderedImage. */
private WritableRenderedImage source0AsWritableRenderedImage;
/* The first source cast to an OpImage. */
private OpImage source0AsOpImage;
/* Flag indicating whether the first source is a WritableRenderedImage. */
private boolean source0IsWritableRenderedImage;
/// END: Variable fields use only when in-place operation is enabled.
/* Flag indicating whether the bounds are the same as for all sources. */
private boolean sameBounds;
/* Flag indicating whether the tile grid is the same as for all sources. */
private boolean sameTileGrid;
// END in-place fields.
/** Fills in the default layout settings. */
private static ImageLayout layoutHelper(ImageLayout layout,
Vector sources,
Map config) {
int numSources = sources.size();
if(numSources < 1) {
throw new IllegalArgumentException(JaiI18N.getString("Generic5"));
}
RenderedImage source0 = (RenderedImage)sources.get(0);
Rectangle isect =
new Rectangle(source0.getMinX(), source0.getMinY(),
source0.getWidth(), source0.getHeight());
Rectangle rect = new Rectangle();
for (int i = 1; i < numSources; i++) {
RenderedImage s = (RenderedImage)sources.get(i);
rect.setBounds(s.getMinX(), s.getMinY(),
s.getWidth(), s.getHeight());
isect = isect.intersection(rect);
}
if (isect.isEmpty()) {
throw new IllegalArgumentException(
JaiI18N.getString("PointOpImage0"));
}
if(layout == null) {
layout = new ImageLayout(isect.x, isect.y,
isect.width, isect.height);
} else {
layout = (ImageLayout)layout.clone();
if (!layout.isValid(ImageLayout.MIN_X_MASK)) {
layout.setMinX(isect.x);
}
if (!layout.isValid(ImageLayout.MIN_Y_MASK)) {
layout.setMinY(isect.y);
}
if (!layout.isValid(ImageLayout.WIDTH_MASK)) {
layout.setWidth(isect.width);
}
if (!layout.isValid(ImageLayout.HEIGHT_MASK)) {
layout.setHeight(isect.height);
}
Rectangle r = new Rectangle(layout.getMinX(null),
layout.getMinY(null),
layout.getWidth(null),
layout.getHeight(null));
if (r.isEmpty()) {
throw new IllegalArgumentException(
JaiI18N.getString("PointOpImage1"));
}
if (!isect.contains(r)) {
throw new IllegalArgumentException(
JaiI18N.getString("PointOpImage2"));
}
}
// If no SampleModel is given, create a new SampleModel (and perhaps
// ColorModel) corresponding to the minimum band count and maximum
// data depth.
if (numSources > 1 && !layout.isValid(ImageLayout.SAMPLE_MODEL_MASK)) {
// Determine the min number of bands and max range of data
SampleModel sm = source0.getSampleModel();
ColorModel cm = source0.getColorModel();
int dtype0 = getAppropriateDataType(sm);
int bands0 = getBandCount(sm, cm);
int dtype = dtype0;
int bands = bands0;
for (int i = 1; i < numSources; i++) {
RenderedImage source = (RenderedImage)sources.get(i);
sm = source.getSampleModel();
cm = source.getColorModel();
int sourceBands = getBandCount(sm, cm);
dtype = mergeTypes(dtype, getPixelType(sm));
bands = Math.min(bands, sourceBands);
}
// Force data type to byte for multi-band bilevel data.
if(dtype == PixelAccessor.TYPE_BIT && bands > 1) {
dtype = DataBuffer.TYPE_BYTE;
}
// Set a new SampleModel if and only if that of source 0 is
// not compatible.
SampleModel sm0 = source0.getSampleModel();
if(dtype != sm0.getDataType() || bands != sm0.getNumBands()) {
int tw = layout.getTileWidth(source0);
int th = layout.getTileHeight(source0);
SampleModel sampleModel;
if(dtype == PixelAccessor.TYPE_BIT) {
sampleModel =
new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE,
tw, th, 1);
} else {
sampleModel =
RasterFactory.createPixelInterleavedSampleModel(dtype,
tw, th,
bands);
}
layout.setSampleModel(sampleModel);
// Set a new ColorModel only if this one is incompatible.
if(cm != null &&
!JDKWorkarounds.areCompatibleDataModels(sampleModel, cm)) {
cm = ImageUtil.getCompatibleColorModel(sampleModel, config);
layout.setColorModel(cm);
}
}
}
return layout;
}
/**
* Returns the pixel type.
*/
private static int getPixelType(SampleModel sampleModel) {
return ImageUtil.isBinary(sampleModel) ?
PixelAccessor.TYPE_BIT : sampleModel.getDataType();
}
/**
* Returns the number of bands.
*/
private static int getBandCount(SampleModel sampleModel,
ColorModel colorModel) {
if(ImageUtil.isBinary(sampleModel)) {
return 1;
} else if (colorModel instanceof IndexColorModel) {
return colorModel.getNumComponents();
} else {
return sampleModel.getNumBands();
}
}
/**
* Determine the appropriate data type for the SampleModel.
*/
private static int getAppropriateDataType(SampleModel sampleModel) {
int dataType = sampleModel.getDataType();
int retVal = dataType;
if (ImageUtil.isBinary(sampleModel)) {
retVal = PixelAccessor.TYPE_BIT;
} else if (dataType == DataBuffer.TYPE_USHORT ||
dataType == DataBuffer.TYPE_INT) {
boolean canUseBytes = true;
boolean canUseShorts = true;
int[] ss = sampleModel.getSampleSize();
for (int i = 0; i < ss.length; i++) {
if (ss[i] > 16) {
canUseBytes = false;
canUseShorts = false;
break;
}
if (ss[i] > 8) {
canUseBytes = false;
}
}
if (canUseBytes) {
retVal = DataBuffer.TYPE_BYTE;
} else if (canUseShorts) {
retVal = DataBuffer.TYPE_USHORT;
}
}
return retVal;
}
/**
* Returns a type (one of the enumerated constants from
* DataBuffer) that has sufficent range to contain values from
* either of two given types. This corresponds to an upwards move
* in the type lattice.
*
* <p> Note that the merge of SHORT and USHORT is INT, so it is not
* correct to simply use the larger of the types.
*/
private static int mergeTypes(int type0, int type1) {
if (type0 == type1) {
return type0;
}
// Default to second type.
int type = type1;
// Use switch logic to avoid depending on monotonicity of
// DataBuffer.TYPE_*.
switch(type0) {
case PixelAccessor.TYPE_BIT:
case DataBuffer.TYPE_BYTE:
// Do nothing.
break;
case DataBuffer.TYPE_SHORT:
if(type1 == DataBuffer.TYPE_BYTE) {
type = DataBuffer.TYPE_SHORT;
} else if(type1 == DataBuffer.TYPE_USHORT) {
type = DataBuffer.TYPE_INT;
}
break;
case DataBuffer.TYPE_USHORT:
if(type1 == DataBuffer.TYPE_BYTE) {
type = DataBuffer.TYPE_USHORT;
} else if(type1 == DataBuffer.TYPE_SHORT) {
type = DataBuffer.TYPE_INT;
}
break;
case DataBuffer.TYPE_INT:
if(type1 == DataBuffer.TYPE_BYTE ||
type1 == DataBuffer.TYPE_SHORT ||
type1 == DataBuffer.TYPE_USHORT) {
type = DataBuffer.TYPE_INT;
}
break;
case DataBuffer.TYPE_FLOAT:
if(type1 != DataBuffer.TYPE_DOUBLE) {
type = DataBuffer.TYPE_FLOAT;
}
break;
case DataBuffer.TYPE_DOUBLE:
type = DataBuffer.TYPE_DOUBLE;
break;
}
return type;
}
/**
* Constructor.
*
* <p> There must be at least one valid source supplied via the
* <code>sources</code> argument. However, there is no upper limit
* on the number of sources this image may have.
*
* <p> The image's layout is encapsulated in the <code>layout</code>
* argument. If the image bounds are supplied they must be contained
* within the intersected source bounds which must be non-empty.
* If the bounds are not supplied, they are calculated to be the
* intersection of the bounds of all sources.
*
* <p> If no <code>SampleModel</code> is specified in the layout, a new
* <code>SampleModel</code> will be created. This <code>SampleModel</code>
* will have a number of bands equal to the minimum band count of all
* sources and a depth which can accomodate the data of all sources.
* The band count of sources which have an <code>IndexColorModel</code>
* will be set to the number of components of the
* <code>IndexColorModel</code> instead of to the number of bands of the
* <code>SampleModel</code>.
*
* <p> In all cases, the layout is forwarded to the <code>OpImage</code>
* constructor which sets the default layout values in the standard way.
*
* @param layout The layout parameters of the destination image.
* @param sources The source images.
* @param configuration Configurable attributes of the image including
* configuration variables indexed by
* <code>RenderingHints.Key</code>s and image properties indexed
* by <code>String</code>s or <code>CaselessStringKey</code>s.
* This is simply forwarded to the superclass constructor.
* @param cobbleSources <code>true</code> if computeRect() expects
* contiguous sources.
*
* @throws IllegalArgumentException If <code>sources</code> or any
* object in <code>sources</code> is <code>null</code>.
* @throws IllegalArgumentException if <code>sources</code> does not
* contain at least one element.
* @throws ClassCastException If any object in <code>sources</code>
* is not a <code>RenderedImage</code>.
* @throws IllegalArgumentException If combining the intersected
* source bounds with the user-specified bounds, if any,
* yields an empty rectangle, or the user-specified image bounds
* extends beyond the intersection of all the source bounds.
*
* @since JAI 1.1
*/
public PointOpImage(Vector sources,
ImageLayout layout,
Map configuration,
boolean cobbleSources) {
super(checkSourceVector(sources, true),
layoutHelper(layout, sources, configuration),
configuration,
cobbleSources);
}
/**
* Constructs a <code>PointOpImage</code> with one source image.
* The image layout is computed as described in the constructor
* taking a <code>Vector</code> of sources.
*
* @param layout The layout parameters of the destination image.
* @param source The source image.
* @param configuration Configurable attributes of the image including
* configuration variables indexed by
* <code>RenderingHints.Key</code>s and image properties indexed
* by <code>String</code>s or <code>CaselessStringKey</code>s.
* This is simply forwarded to the superclass constructor.
* @param cobbleSources Indicates whether <code>computeRect()</code>
* expects contiguous sources.
*
* @throws IllegalArgumentException if <code>source</code>
* is <code>null</code>.
*
* @since JAI 1.1
*/
public PointOpImage(RenderedImage source,
ImageLayout layout,
Map configuration,
boolean cobbleSources) {
this(vectorize(source), // vectorize() checks for null source.
layout, configuration, cobbleSources);
}
/**
* Constructs a <code>PointOpImage</code> with two source images.
* The image layout is computed as described in the constructor
* taking a <code>Vector</code> of sources.
*
* @param layout The layout parameters of the destination image.
* @param source0 The first source image.
* @param source1 The second source image.
* @param configuration Configurable attributes of the image including
* configuration variables indexed by
* <code>RenderingHints.Key</code>s and image properties indexed
* by <code>String</code>s or <code>CaselessStringKey</code>s.
* This is simply forwarded to the superclass constructor.
* @param cobbleSources Indicates whether <code>computeRect()</code>
* expects contiguous sources.
*
* @throws IllegalArgumentException if <code>source0</code> or
* <code>source1</code> is <code>null</code>.
*
* @since JAI 1.1
*/
public PointOpImage(RenderedImage source0,
RenderedImage source1,
ImageLayout layout,
Map configuration,
boolean cobbleSources) {
this(vectorize(source0, source1), // vectorize() checks for null sources.
layout, configuration, cobbleSources);
}
/**
* Constructs a <code>PointOpImage</code> with three source
* images. The image layout is computed as described in the
* constructor taking a <code>Vector</code> of sources.
*
* @param layout The layout parameters of the destination image.
* @param source0 The first source image.
* @param source1 The second source image.
* @param source2 The third source image.
* @param configuration Configurable attributes of the image including
* configuration variables indexed by
* <code>RenderingHints.Key</code>s and image properties indexed
* by <code>String</code>s or <code>CaselessStringKey</code>s.
* This is simply forwarded to the superclass constructor.
* @param cobbleSources Indicates whether <code>computeRect()</code>
* expects contiguous sources.
*
* @throws IllegalArgumentException if <code>source0</code> or
* <code>source1</code> or <code>source2</code> is
* <code>null</code>.
*
* @since JAI 1.1
*/
public PointOpImage(RenderedImage source0,
RenderedImage source1,
RenderedImage source2,
ImageLayout layout,
Map configuration,
boolean cobbleSources) {
this(vectorize(source0, source1, source2), // vectorize() checks null
layout, configuration, cobbleSources);
}
/*
* Initialize flags and instance variables.
*/
private synchronized void initializeFields() {
if (areFieldsInitialized)
return;
PlanarImage source0 = getSource(0);
if (checkInPlaceOperation) {
// Set the in-place operation flag.
// XXX: In-place operation could work equally well when source0
// is a WritableRenderedImage. However this could produce
// unexpected results so consequently it would be desirable if
// some kind of hint could be set that in-place operation is to
// be used. The following statement should then be changed such
// that instead of
//
// source0 instanceof OpImage &&
//
// we have
//
// (source0 instanceof OpImage ||
// (source0 instanceof WritableRenderedImage &&
// isInPlaceHintSet))
//
Vector source0Sinks = source0.getSinks();
isInPlaceEnabled = source0 != null &&
getTileGridXOffset() == source0.getTileGridXOffset() &&
getTileGridYOffset() == source0.getTileGridYOffset() &&
getBounds().equals(source0.getBounds()) &&
source0 instanceof OpImage &&
hasCompatibleSampleModel(source0) &&
!(source0Sinks != null && source0Sinks.size() > 1);
// Ensure that source0 computes unique tiles, i.e.,
// computesUniqueTiles() returns false. This disqualifies
// for example in-place operations when source0 is an instance
// of NullOpImage or of a subclass of NullOpImage or
// SourcelessOpImage which does not override
// computesUniqueTiles() to return true.
if (isInPlaceEnabled &&
!((OpImage)source0).computesUniqueTiles()) {
isInPlaceEnabled = false;
}
// Unset the in-place flag if getTile() is overridden by the
// class of which source0 is an instance.
if (isInPlaceEnabled) {
try {
Method getTileMethod =
source0.getClass().getMethod("getTile",
new Class[] {int.class,
int.class});
Class opImageClass =
Class.forName("com.lightcrafts.mediax.jai.OpImage");
Class declaringClass = getTileMethod.getDeclaringClass();
// Unset in-place flag if getTile() is overridden.
if(!declaringClass.equals(opImageClass)) {
isInPlaceEnabled = false;
}
} catch(ClassNotFoundException e) {
isInPlaceEnabled = false;
} catch(NoSuchMethodException e) {
isInPlaceEnabled = false;
}
}
// Set local fields as a function of the in-place operation flag.
if (isInPlaceEnabled) {
// Set the flag indicating source0's type.
source0IsWritableRenderedImage =
source0 instanceof WritableRenderedImage;
// Cast the first source to one of the cached image variables.
if (source0IsWritableRenderedImage) {
source0AsWritableRenderedImage =
(WritableRenderedImage)source0;
} else {
source0AsOpImage = (OpImage)source0;
}
}
// Unset this flag.
checkInPlaceOperation = false;
}
// Get the number of sources.
int numSources = getNumSources();
// Initialize the bounds and tile grid flags.
sameBounds = true;
sameTileGrid = true;
// Loop over all sources or until both flags are false.
for(int i = 0; i < numSources && (sameBounds || sameTileGrid); i++) {
PlanarImage source = getSource(i);
// Update the bounds flag.
if (sameBounds) {
sameBounds = sameBounds &&
minX == source.minX && minY == source.minY &&
width == source.width && height == source.height;
}
// Update the tile grid flag.
if (sameTileGrid) {
sameTileGrid = sameTileGrid &&
tileGridXOffset == source.tileGridXOffset &&
tileGridYOffset == source.tileGridYOffset &&
tileWidth == source.tileWidth &&
tileHeight == source.tileHeight;
}
}
// Set this flag.
areFieldsInitialized = true;
}
/*
* Check whether the <code>SampleModel</code> of the argument
* <code>PlanarImage</code>is compatible with that of this
* <code>PointOpImage</code>.
*
* @param src The <code>PlanarImage</code> whose <code>SampleModel</code>
* is to be checked.
* @return Whether the parameter has a compatible <code>SampleModel</code>.
*/
private boolean hasCompatibleSampleModel(PlanarImage src) {
SampleModel srcSM = src.getSampleModel();
int numBands = sampleModel.getNumBands();
boolean isCompatible =
srcSM.getTransferType() == sampleModel.getTransferType() &&
srcSM.getWidth() == sampleModel.getWidth() &&
srcSM.getHeight() == sampleModel.getHeight() &&
srcSM.getNumBands() == numBands &&
srcSM.getClass().equals(sampleModel.getClass());
if (isCompatible) {
if (sampleModel instanceof ComponentSampleModel) {
ComponentSampleModel smSrc = (ComponentSampleModel)srcSM;
ComponentSampleModel smDst = (ComponentSampleModel)sampleModel;
isCompatible = isCompatible &&
smSrc.getPixelStride() == smDst.getPixelStride() &&
smSrc.getScanlineStride() == smDst.getScanlineStride();
int[] biSrc = smSrc.getBankIndices();
int[] biDst = smDst.getBankIndices();
int[] boSrc = smSrc.getBandOffsets();
int[] boDst = smDst.getBandOffsets();
for(int b = 0; b < numBands && isCompatible; b++) {
isCompatible = isCompatible &&
biSrc[b] == biDst[b] &&
boSrc[b] == boDst[b];
}
} else if (sampleModel instanceof
SinglePixelPackedSampleModel) {
SinglePixelPackedSampleModel smSrc =
(SinglePixelPackedSampleModel)srcSM;
SinglePixelPackedSampleModel smDst =
(SinglePixelPackedSampleModel)sampleModel;
isCompatible = isCompatible &&
smSrc.getScanlineStride() == smDst.getScanlineStride();
int[] bmSrc = smSrc.getBitMasks();
int[] bmDst = smDst.getBitMasks();
for(int b = 0; b < numBands && isCompatible; b++) {
isCompatible = isCompatible &&
bmSrc[b] == bmDst[b];
}
} else if (sampleModel instanceof MultiPixelPackedSampleModel) {
MultiPixelPackedSampleModel smSrc =
(MultiPixelPackedSampleModel)srcSM;
MultiPixelPackedSampleModel smDst =
(MultiPixelPackedSampleModel)sampleModel;
isCompatible = isCompatible &&
smSrc.getPixelBitStride() == smDst.getPixelBitStride() &&
smSrc.getScanlineStride() == smDst.getScanlineStride() &&
smSrc.getDataBitOffset() == smDst.getDataBitOffset();
} else {
isCompatible = false;
}
}
return isCompatible;
}
/**
* Causes a flag to be set to indicate that in-place operation should
* be permitted if the image bounds, tile grid offset, tile dimensions,
* and SampleModels of the source and destination images are compatible.
* This method should be invoked in the constructor of the implementation
* of a given operation only if that implementation is amenable to
* in-place computation. Invocation of this method is a necessary but
* not a sufficient condition for in-place computation actually to occur.
* If the system property "com.lightcrafts.mediax.jai.PointOpImage.InPlace" is equal
* to the string "false" in a case-insensitive fashion then in-place
* operation will not be permitted.
*/
protected void permitInPlaceOperation() {
// Retrieve the in-place property.
Object inPlaceProperty = null;
try {
inPlaceProperty =
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
String name = "com.lightcrafts.mediax.jai.PointOpImage.InPlace";
return System.getProperty(name);
}
});
} catch (SecurityException se) {
/// as if the property isn't set
}
// Set the flag to false if and only if the property is set to
// the string "false" (case-insensitive).
checkInPlaceOperation =
!(inPlaceProperty != null &&
inPlaceProperty instanceof String &&
((String)inPlaceProperty).equalsIgnoreCase("false"));
}
/**
* Indicates whether the operation is being effected directly on the
* associated colormap. This method will in general return
* <code>true</code> if the image is the destination of a unary,
* shift-invariant operation with an <code>IndexColorModel</code> equal
* to that of its unique source.
*
* <p> When this method returns <code>true</code> the
* <code>computeTile()</code> method in this class will return either
* a copy of the corresponding region of the first source image or,
* if the operation is being performed in place, the corresponding
* tile of the first source image.
*
* <p> The implementation in this class always returns <code>false</code>.
*
* @since JAI 1.1
*/
protected boolean isColormapOperation() {
return false;
}
/**
* Computes a tile. If source cobbling was requested at
* construction time, the source tile boundaries are overlayed
* onto the destination and <code>computeRect(Raster[],
* WritableRaster, Rectangle)</code> is called for each of the
* resulting regions. Otherwise, <code>computeRect(PlanarImage[],
* WritableRaster, Rectangle)</code> is called once to compute the
* entire active area of the tile.
*
* <p> The image bounds may be larger than the bounds of the
* source image. In this case, samples for which there are no
* corresponding sources are set to zero.
*
* @param tileX The X index of the tile.
* @param tileY The Y index of the tile.
*/
public Raster computeTile(int tileX, int tileY) {
if (!cobbleSources) {
return super.computeTile(tileX, tileY);
}
// Make sure the fields are initialized.
initializeFields();
// Get a WritableRaster to represent this tile.
WritableRaster dest = null;
if (isInPlaceEnabled) {
if (source0IsWritableRenderedImage) {
// Check one out from the WritableRenderedImage source.
dest = source0AsWritableRenderedImage.getWritableTile(tileX,
tileY);
} else { // source0 is OpImage
// Re-use one from the OpImage source.
// First check whether the source raster is cached.
Raster raster =
source0AsOpImage.getTileFromCache(tileX, tileY);
if (raster == null) {
// Compute the tile.
try {
raster = source0AsOpImage.computeTile(tileX, tileY);
if (raster instanceof WritableRaster) {
dest = (WritableRaster)raster;
}
} catch(Exception e) {
// Do nothing: this catch is simply in case the
// OpImage in question does not itself implement
// computeTile() in which case it may be resolved
// to OpImage.computeTile() which will throw an
// Exception.
}
}
}
}
// Set tile recycling flag.
boolean recyclingSource0Tile = dest != null;
if (!recyclingSource0Tile) {
// Create a new WritableRaster.
Point org = new Point(tileXToX(tileX), tileYToY(tileY));
dest = createWritableRaster(sampleModel, org);
}
// Colormap operation: return the source Raster if operating
// in place or a copy thereof otherwise.
if(isColormapOperation()) {
if(!recyclingSource0Tile) {
PlanarImage src = getSource(0);
Raster srcTile = null;
Rectangle srcRect = null;
Rectangle dstRect = dest.getBounds();
// Confirm that the tile grids of the source and destination
// are the same
if (sameTileGrid) {
// Tile grids are aligned so the tile indices correspond
// to pixels at the same locations in source and destination
srcTile = getSource(0).getTile(tileX, tileY);
}
else if (dstRect.intersects(src.getBounds())) {
// Tile grids are not aligned but the destination rectangle
// intersects the source bounds so get the data using
// the destination rectangle
srcTile = src.getData(dstRect);
}
else {
// The destination rectangle does not interest the source
// bounds so just return the destination.
return dest;
}
srcRect = srcTile.getBounds();
// Ensure that the source tile doesn't lie outside the
// destination tile.
if(!dstRect.contains(srcRect)) {
srcRect = dstRect.intersection(srcRect);
srcTile =
srcTile.createChild(srcTile.getMinX(),
srcTile.getMinY(),
srcRect.width,
srcRect.height,
srcRect.x,
srcRect.y,
null);
}
JDKWorkarounds.setRect(dest, srcTile, 0, 0);
}
return dest;
}
// Output bounds are initially equal to the tile bounds.
int destMinX = dest.getMinX();
int destMinY = dest.getMinY();
int destMaxX = destMinX + dest.getWidth();
int destMaxY = destMinY + dest.getHeight();
// Clip output bounds to the dest image bounds.
Rectangle bounds = getBounds();
if (destMinX < bounds.x) {
destMinX = bounds.x;
}
int boundsMaxX = bounds.x + bounds.width;
if (destMaxX > boundsMaxX) {
destMaxX = boundsMaxX;
}
if (destMinY < bounds.y) {
destMinY = bounds.y;
}
int boundsMaxY = bounds.y + bounds.height;
if (destMaxY > boundsMaxY) {
destMaxY = boundsMaxY;
}
// Get the number of sources.
int numSrcs = getNumSources();
// Branch to actual destination rectangle computation as a
// function of in-place operation and layout compatibility.
if (recyclingSource0Tile && numSrcs == 1) {
// Recycling tile from a single source.
Raster[] sources = new Raster[] {dest};
Rectangle destRect = new Rectangle(destMinX, destMinY,
destMaxX - destMinX,
destMaxY - destMinY);
computeRect(sources, dest, destRect);
} else if (recyclingSource0Tile && sameBounds && sameTileGrid) {
// Recycling tile from first of layout-compatible sources.
Raster[] sources = new Raster[numSrcs];
sources[0] = dest;
for(int i = 1; i < numSrcs; i++) {
sources[i] = getSource(i).getTile(tileX, tileY);
}
Rectangle destRect = new Rectangle(destMinX, destMinY,
destMaxX - destMinX,
destMaxY - destMinY);
computeRect(sources, dest, destRect);
} else {
// Clip against source bounds only if necessary.
if (!sameBounds) {
// Clip output bounds to each source image bounds
for (int i = recyclingSource0Tile ? 1 : 0; i < numSrcs; i++) {
bounds = getSource(i).getBounds();
if (destMinX < bounds.x) {
destMinX = bounds.x;
}
boundsMaxX = bounds.x + bounds.width;
if (destMaxX > boundsMaxX) {
destMaxX = boundsMaxX;
}
if (destMinY < bounds.y) {
destMinY = bounds.y;
}
boundsMaxY = bounds.y + bounds.height;
if (destMaxY > boundsMaxY) {
destMaxY = boundsMaxY;
}
if (destMinX >= destMaxX || destMinY >= destMaxY) {
return dest; // no corresponding source region
}
}
}
// Initialize the (possibly clipped) destination Rectangle.
Rectangle destRect = new Rectangle(destMinX, destMinY,
destMaxX - destMinX,
destMaxY - destMinY);
// Allocate memory for source Rasters.
Raster[] sources = new Raster[numSrcs];
if (sameTileGrid) {
// All sources share the tile grid of the destination so
// there is no need for splits.
if (recyclingSource0Tile) {
sources[0] = dest;
}
for (int i = recyclingSource0Tile ? 1 : 0; i < numSrcs; i++) {
sources[i] = getSource(i).getTile(tileX, tileY);
}
computeRect(sources, dest, destRect);
} else {
//
// The tileWidth and tileHeight of the source image
// may differ from this tileWidth and tileHeight.
//
IntegerSequence xSplits =
new IntegerSequence(destMinX, destMaxX);
xSplits.insert(destMinX);
xSplits.insert(destMaxX);
IntegerSequence ySplits =
new IntegerSequence(destMinY, destMaxY);
ySplits.insert(destMinY);
ySplits.insert(destMaxY);
for (int i = recyclingSource0Tile ? 1 : 0; i < numSrcs; i++) {
PlanarImage s = getSource(i);
s.getSplits(xSplits, ySplits, destRect);
}
//
// Divide destRect into sub rectangles based on the source
// splits, and compute each sub rectangle separately.
//
int x1, x2, y1, y2, w, h;
Rectangle subRect = new Rectangle();
ySplits.startEnumeration();
for (y1 = ySplits.nextElement(); ySplits.hasMoreElements(); y1 = y2) {
y2 = ySplits.nextElement();
h = y2 - y1;
xSplits.startEnumeration();
for (x1 = xSplits.nextElement();
xSplits.hasMoreElements(); x1 = x2) {
x2 = xSplits.nextElement();
w = x2 - x1;
// Get sources.
if (recyclingSource0Tile) {
sources[0] = dest;
}
for (int i = recyclingSource0Tile ? 1: 0;
i < numSrcs; i++) {
PlanarImage s = getSource(i);
int tx = s.XToTileX(x1);
int ty = s.YToTileY(y1);
sources[i] = s.getTile(tx, ty);
}
subRect.x = x1;
subRect.y = y1;
subRect.width = w;
subRect.height = h;
computeRect(sources, dest, subRect);
}
}
}
}
if (recyclingSource0Tile && source0IsWritableRenderedImage) {
source0AsWritableRenderedImage.releaseWritableTile(tileX, tileY);
}
return dest;
}
/**
* Returns a conservative estimate of the destination region that
* can potentially be affected by the pixels of a rectangle of a
* given source. The resulting <code>Rectangle</code> is <u>not</u>
* clipped to the destination image bounds.
*
* @param sourceRect the <code>Rectangle</code> in source coordinates.
* @param sourceIndex the index of the source image.
* @return a <code>Rectangle</code> indicating the potentially affected
* destination region, or <code>null</code> if the region is unknown.
* @throws IllegalArgumentException if <code>sourceIndex</code> is
* negative or greater than the index of the last source.
* @throws IllegalArgumentException if <code>sourceRect</code> is
* <code>null</code>.
*/
public final Rectangle mapSourceRect(Rectangle sourceRect,
int sourceIndex) {
if ( sourceRect == null ) {
throw new IllegalArgumentException(JaiI18N.getString("Generic0"));
}
if (sourceIndex < 0 || sourceIndex >= getNumSources()) {
throw new IllegalArgumentException(JaiI18N.getString("Generic1"));
}
return new Rectangle(sourceRect);
}
/**
* Returns a conservative estimate of the region of a specific
* source that is required in order to compute the pixels of a
* given destination rectangle. The resulting <code>Rectangle</code>
* is <u>not</u> clipped to the source image bounds.
*
* @param destRect the <code>Rectangle</code> in source coordinates.
* @param sourceIndex the index of the source image.
* @return a <code>Rectangle</code> indicating the potentially affected
* destination region.
*
* @throws IllegalArgumentException if <code>sourceIndex</code> is
* negative or greater than the index of the last source.
* @throws IllegalArgumentException if <code>destRect</code> is
* <code>null</code>.
*/
public final Rectangle mapDestRect(Rectangle destRect,
int sourceIndex) {
if ( destRect == null ) {
throw new IllegalArgumentException(JaiI18N.getString("Generic0"));
}
if (sourceIndex < 0 || sourceIndex >= getNumSources()) {
throw new IllegalArgumentException(JaiI18N.getString("Generic1"));
}
return new Rectangle(destRect);
}
/**
* Disposes of any remaining tiles in the <code>TileCache</code>.
*
* <p>If <code>cache</code> is non-<code>null</code>, in place operation
* is enabled, and <code>tileRecycler</code> is non-<code>null</code>,
* then all tiles owned by this specific image are removed from the cache.
* Subsequent to this <code>super.dispose()</code> is invoked.</p>
*
* @since JAI 1.1.2
*/
public synchronized void dispose() {
if(isDisposed) {
return;
}
isDisposed = true;
if(cache != null && isInPlaceEnabled && tileRecycler != null) {
cache.removeTiles(this);
}
super.dispose();
}
}