/*
* $RCSfile: RasterAccessor.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:18 $
* $State: Exp $
*/
package com.lightcrafts.mediax.jai;
import java.awt.image.SampleModel;
import java.awt.image.ComponentSampleModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferUShort;
import java.awt.image.DataBufferShort;
import java.awt.image.DataBufferInt;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.awt.Rectangle;
import java.awt.image.RenderedImage;
import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
import java.awt.image.ComponentColorModel;
import com.lightcrafts.media.jai.util.DataBufferUtils;
import com.lightcrafts.media.jai.util.ImageUtil;
/**
* An adapter class for presenting non-binary image data in a
* <code>ComponentSampleModel</code> format and binary image data in
* a zero-offset byte array format even when the original data are not
* so stored. <code>RasterAccessor</code> is meant to make the common
* (<code>ComponentSampleModel</code>) case fast and other formats
* possible without forcing the <code>OpImage</code> writer to cover more
* than one case per non-binary data type.
*
* <p>When constructing a <code>RasterAccessor</code> with a source(s) that
* has an IndexColorModel and a destination that has a
* non-<code>IndexColorModel</code>, <code>RasterAccessor</code> will
* perform expansion of the source pixels. If the source(s) and the
* destination have an IndexColorModel, then <code>RasterAccessor</code>
* will assume that the operation can correctly process an IndexColorModel
* source and will not expand the source pixels (colormap indices) into
* color components. Refer to {@link JAI#KEY_REPLACE_INDEX_COLOR_MODEL}
* for a mechanism by which the destination image's <code>ColorModel</code>
* is set to a non-<code>IndexColorModel</code> to cause
* <code>RasterAccessor</code> to expand the source's
* <code>IndexColorModel</code>.
*
* <p> Binary data are handled as a special case. In general image data
* are considered to be binary when the image has a single-banded
* <code>MultiPixelPackedSampleModel</code> with one bit per pixel. This
* may be verified by invoking the <code>isBinary()</code> method. For this
* case the methods <code>getBinaryDataArray()</code> and
* <code>copyBinaryDataToRaster()</code> should be used to access and set,
* respectively, the binary data in packed form. If the binary data are
* to be accessed in expanded form, i.e., as bytes, then the usual byte
* methods <code>getByteDataArray()</code>, <code>getByteDataArrays()</code>,
* and <code>copyDataToRaster()</code> should be used.
*
*/
public class RasterAccessor {
/**
* Value indicating how far COPY_MASK info is shifted to avoid
* interfering with the data type info.
*/
private static final int COPY_MASK_SHIFT = 7;
/* Value indicating how many bits the COPY_MASK is */
private static final int COPY_MASK_SIZE = 2;
/** The bits of a FormatTag associated with how dataArrays are obtained. */
public static final int COPY_MASK =
((1 << COPY_MASK_SIZE) - 1) << COPY_MASK_SHIFT;
/** Flag indicating data is raster's data. */
public static final int UNCOPIED = 0x0 << COPY_MASK_SHIFT;
/** Flag indicating data is a copy of the raster's data. */
public static final int COPIED = 0x1 << COPY_MASK_SHIFT;
/**
* Value indicating how far EXPANSION_MASK info is shifted to avoid
* interfering with the data type info.
*/
private static final int EXPANSION_MASK_SHIFT =
COPY_MASK_SHIFT+COPY_MASK_SIZE;
/** Value indicating how many bits the EXPANSION_MASK is */
private static final int EXPANSION_MASK_SIZE = 2;
/** The bits of a FormatTag associated with how ColorModels are used. */
public static final int EXPANSION_MASK =
((1 << EXPANSION_MASK_SIZE) - 1) << EXPANSION_MASK_SHIFT;
/** Flag indicating ColorModel data should be used only in copied case */
public static final int DEFAULTEXPANSION = 0x0 << EXPANSION_MASK_SHIFT;
/** Flag indicating ColorModel data should be interpreted. */
public static final int EXPANDED = 0x1 << EXPANSION_MASK_SHIFT;
/** Flag indicating ColorModel info should be ignored */
public static final int UNEXPANDED = 0x02 << EXPANSION_MASK_SHIFT;
/** The bits of a FormatTagID associated with pixel datatype. */
public static final int DATATYPE_MASK = (0x1 << COPY_MASK_SHIFT) - 1;
/** FormatTagID indicating data in byte arrays and uncopied. */
public static final int
TAG_BYTE_UNCOPIED = DataBuffer.TYPE_BYTE | UNCOPIED;
/** FormatTagID indicating data in unsigned short arrays and uncopied. */
public static final int
TAG_USHORT_UNCOPIED = DataBuffer.TYPE_USHORT | UNCOPIED;
/** FormatTagID indicating data in short arrays and uncopied. */
public static final int
TAG_SHORT_UNCOPIED = DataBuffer.TYPE_SHORT | UNCOPIED;
/** FormatTagID indicating data in int arrays and uncopied. */
public static final int
TAG_INT_UNCOPIED = DataBuffer.TYPE_INT | UNCOPIED;
/** FormatTagID indicating data in float arrays and uncopied. */
public static final int
TAG_FLOAT_UNCOPIED = DataBuffer.TYPE_FLOAT | UNCOPIED;
/** FormatTagID indicating data in double arrays and uncopied. */
public static final int
TAG_DOUBLE_UNCOPIED = DataBuffer.TYPE_DOUBLE | UNCOPIED;
/** FormatTagID indicating data in int arrays and copied. */
public static final int
TAG_INT_COPIED = DataBuffer.TYPE_INT | COPIED;
/** FormatTagID indicating data in float arrays and copied. */
public static final int
TAG_FLOAT_COPIED = DataBuffer.TYPE_FLOAT | COPIED;
/** FormatTagID indicating data in double arrays and copied. */
public static final int
TAG_DOUBLE_COPIED = DataBuffer.TYPE_DOUBLE | COPIED;
/** FormatTagID indicating data in byte arrays and expanded. */
public static final int
TAG_BYTE_EXPANDED = DataBuffer.TYPE_BYTE | EXPANDED;
/**
* FormatTagID corresponding to the binary case. This occurs when
* the image has a <code>MultiPixelPackedSampleModel</code> with a
* single band and one bit per pixel.
*/
private static final int TAG_BINARY =
DataBuffer.TYPE_BYTE | COPIED | UNEXPANDED;
/** The raster that is the source of pixel data. */
protected Raster raster;
/** The width of the rectangle this RasterAccessor addresses. */
protected int rectWidth;
/** The height of the rectangle this RasterAccessor addresses. */
protected int rectHeight;
/** The x coordinate of upper-left corner of the rectangle this
* RasterAccessor addresses.
*/
protected int rectX;
/** The y coordinate of upper-left corner of the rectangle this
* RasterAccessor addresses.
*/
protected int rectY;
/** Tag indicating the data type of the data and whether it's copied */
protected int formatTagID;
/**
* The image data for the binary case. The data will be packed as
* eight bits per byte with no bit offset, i.e., the first bit in each
* image line will be the left-most bit of the first byte of the line.
* The line stride in bytes will be <code>(int)((rectWidth+7)/8)</code>.
* The length of the array will be <code>rectHeight</code> multiplied
* by the line stride.
*
* @since JAI 1.1
*/
protected byte binaryDataArray[] = null;
/**
* The image data in a two-dimensional byte array. This
* value will be non-null only if getDataType() returns
* DataBuffer.TYPE_BYTE. byteDataArrays.length will equal
* numBands. Note that often the numBands subArrays will all
* point to the same place in memory.
*
* <p> For the case of binary data this variable will not be initialized
* until <code>getByteDataArrays()</code> or
* <code>getByteDataArray(int b)</code> is invoked.
*/
protected byte byteDataArrays[][] = null;
/**
* The image data in a two-dimensional short array. This
* value will be non-null only if getDataType() returns
* DataBuffer.TYPE_USHORT or DataBuffer.TYPE_SHORT.
* shortDataArrays.length will equal
* numBands. Note that often the numBands subArrays will all
* point to the same place in memory.
*/
protected short shortDataArrays[][] = null;
/**
* The image data in a two-dimensional int array. This
* value will be non-null only if getDataType() returns
* DataBuffer.TYPE_INT. intDataArrays.length will equal
* numBands. Note that often the numBands subArrays will all
* point to the same place in memory.
*/
protected int intDataArrays[][] = null;
/**
* The image data in a two-dimensional float array. This
* value will be non-null only if getDataType() returns
* DataBuffer.TYPE_FLOAT. floatDataArrays.length will equal
* numBands. Note that often the numBand subArrays will all
* point to the same place in memory.
*/
protected float floatDataArrays[][] = null;
/**
* The image data in a two-dimensional double array. This
* value will be non-null only if getDataType() returns
* DataBuffer.TYPE_DOUBLE. doubleDataArrays.length will equal
* numBands. Note that often the numBand subArrays will all
* point to the same place in memory.
*/
protected double doubleDataArrays[][] = null;
/**
* The bandOffset + subRasterOffset + DataBufferOffset into each of
* the numBand data arrays
*/
protected int bandDataOffsets[];
/** Offset from a pixel's offset to a band of that pixel */
protected int bandOffsets[];
/** The number of bands per pixel in the data array. */
protected int numBands;
/** The scanline stride of the image data in each data array */
protected int scanlineStride;
/** The pixel stride of the image data in each data array */
protected int pixelStride;
/**
* Finds the appropriate tags for the constructor, based on the
* SampleModel and ColorModel of all the source and destination.
*
* @param srcs The operations sources; may be <code>null</code> which
* is taken to be equivalent to zero sources.
* @param dst The operation destination.
* @return An array containing <code>RasterFormatTag</code>s for the
* sources in the first src.length elements and a
* <code>RasterFormatTag</code> for the destination in the last element.
* @throws NullPointerException if <code>dst</code> is <code>null</code>.
*/
public static
RasterFormatTag[] findCompatibleTags(RenderedImage srcs[],
RenderedImage dst) {
int tagIDs[];
if (srcs != null) {
tagIDs = new int[srcs.length + 1];
} else {
tagIDs = new int[1];
}
SampleModel dstSampleModel = dst.getSampleModel();
int dstDataType = dstSampleModel.getTransferType();
int defaultDataType = dstDataType;
boolean binaryDst = ImageUtil.isBinary(dstSampleModel);
if (binaryDst) {
defaultDataType = DataBuffer.TYPE_BYTE;
} else if((dstDataType == DataBuffer.TYPE_BYTE) ||
(dstDataType == DataBuffer.TYPE_USHORT) ||
(dstDataType == DataBuffer.TYPE_SHORT)) {
defaultDataType = DataBuffer.TYPE_INT;
}
// use highest precision datatype of all srcs & dst
if (srcs != null) {
int numSources = srcs.length;
int i;
for (i = 0; i < numSources; i++) {
SampleModel srcSampleModel = srcs[i].getSampleModel();
int srcDataType = srcSampleModel.getTransferType();
if (!(binaryDst && ImageUtil.isBinary(srcSampleModel)) &&
srcDataType > defaultDataType) {
defaultDataType = srcDataType;
}
}
}
// Set the tag. For binary data at this point this should
// equal DataBuffer.TYPE_BYTE | COPIED.
int tagID = defaultDataType | COPIED;
if (dstSampleModel instanceof ComponentSampleModel) {
if (srcs != null) {
int numSources = srcs.length;
int i;
for (i = 0; i < numSources; i++) {
SampleModel srcSampleModel = srcs[i].getSampleModel();
int srcDataType = srcSampleModel.getTransferType();
if (!(srcSampleModel instanceof ComponentSampleModel) ||
(srcDataType != dstDataType)) {
break;
}
}
if (i == numSources) {
tagID = dstDataType | UNCOPIED;
}
} else {
tagID = dstDataType | UNCOPIED;
}
}
// If the source has an IndexColorModel but the dest does not,
// perform expansion of the source pixels. If both have an
// IndexColorModel, assume the operation knows what it is doing.
RasterFormatTag rft[] = new RasterFormatTag[tagIDs.length];
if (srcs != null) {
for (int i = 0; i < srcs.length; i++) {
// dst can't be EXPANDED
if ((srcs[i].getColorModel() instanceof IndexColorModel)) {
if (dst.getColorModel() instanceof IndexColorModel) {
tagIDs[i] = tagID | UNEXPANDED;
} else {
tagIDs[i] = tagID | EXPANDED;
}
} else if (srcs[i].getColorModel() instanceof
ComponentColorModel ||
(binaryDst &&
ImageUtil.isBinary(srcs[i].getSampleModel()))){
tagIDs[i] = tagID | UNEXPANDED;
} else {
tagIDs[i] = tagID | DEFAULTEXPANSION;
}
}
tagIDs[srcs.length] = tagID | UNEXPANDED;
for (int i = 0; i < srcs.length; i++) {
rft[i] =
new RasterFormatTag(srcs[i].getSampleModel(), tagIDs[i]);
}
// get the dest
rft[srcs.length] =
new RasterFormatTag(dstSampleModel,tagIDs[srcs.length]);
} else { // no sources, dest only
rft[0] = new RasterFormatTag(dstSampleModel, tagID | UNEXPANDED);
}
return rft;
}
/**
* Returns the most efficient FormatTagID that is compatible with
* the destination SampleModel and all source SampleModels.
* Since there is no <code>ColorModel</code> associated with
* a <code>SampleModel</code>, this method does not expand the data
* buffer as it has no access to the Raster's ColorModel.
*/
public static int findCompatibleTag(SampleModel[] srcSampleModels,
SampleModel dstSampleModel) {
int dstDataType = dstSampleModel.getTransferType();
int tag = dstDataType | COPIED;
if (ImageUtil.isBinary(dstSampleModel)) {
tag = DataBuffer.TYPE_BYTE | COPIED;
} else if (dstDataType == DataBuffer.TYPE_BYTE ||
dstDataType == DataBuffer.TYPE_USHORT ||
dstDataType == DataBuffer.TYPE_SHORT) {
tag = TAG_INT_COPIED;
}
if (dstSampleModel instanceof ComponentSampleModel) {
if (srcSampleModels != null) {
int numSources = srcSampleModels.length;
int i;
for (i = 0; i < numSources; i++) {
int srcDataType = srcSampleModels[i].getTransferType();
if (!(srcSampleModels[i] instanceof ComponentSampleModel) ||
srcDataType != dstDataType) {
break;
}
}
if (i == numSources) {
tag = dstDataType | UNCOPIED;
}
} else {
tag = dstDataType | UNCOPIED;
}
}
return tag | UNEXPANDED; // only called when colormodel not around
// so never expand
}
/**
* Constructs a RasterAccessor object out of a Raster, Rectangle
* and formatTagID returned from RasterFormat.findCompatibleTag().
*
* <p> The <code>RasterFormatTag</code> must agree with the raster's
* <code>SampleModel</code> and <code>ColorModel</code>. It is best
* to obtain the correct tag using the <code>findCompatibleTags</code>
* static method.
*
* @param raster The raster to be accessed
* @param rect A <code>Rectangle</code> from the raster to be accessed
* @param rft The <code>RasterFormatTag</code> associated with the Raster
* @param theColorModel The <code>ColorModel</code> for color components
*
* @throws ClassCastException if the data type of
* <code>RasterFormatTag</code> does not agree with the actual
* data type of the <code>Raster</code>.
* @throws IllegalArgumentException if <code>raster</code>,
* <code>rect</code>, or <code>rft</code> is <code>null</code>.
* @throws IllegalArgumentException if the <code>Rectangle</code>
* is not contained within <code>Raster</code>'s bounds.
*/
public RasterAccessor(Raster raster,
Rectangle rect,
RasterFormatTag rft,
ColorModel theColorModel) {
if(raster == null || rect == null || rft == null) {
throw new IllegalArgumentException(JaiI18N.getString("Generic0"));
}
// If requesting a region that lies outside the bounds,
// throw an exception.
if (!raster.getBounds().contains(rect)) {
throw new IllegalArgumentException(
JaiI18N.getString("RasterAccessor2"));
}
this.raster = raster;
this.rectX = rect.x;
this.rectY = rect.y;
this.rectWidth = rect.width;
this.rectHeight = rect.height;
this.formatTagID = rft.getFormatTagID();
if ((formatTagID & COPY_MASK) == UNCOPIED) {
this.numBands = rft.getNumBands();
this.pixelStride = rft.getPixelStride();
ComponentSampleModel csm =
(ComponentSampleModel)raster.getSampleModel();
this.scanlineStride = csm.getScanlineStride();
int bankIndices[] = null;
// if the rft isPixelSequential we can rely on it's
// version of bandOffsets and bankIndicies. If it's
// not the SampleModel passed in might not completely
// match the one that was passed to the the
// RasterFormatTag constructor so we have to get them
// from the passed in Raster/SampleModel
if (rft.isPixelSequential()) {
this.bandOffsets = rft.getBandOffsets();
bankIndices = rft.getBankIndices();
} else {
this.bandOffsets = csm.getBandOffsets();
bankIndices = csm.getBankIndices();
}
this.bandDataOffsets = new int[numBands];
int dataBufferOffsets[] = raster.getDataBuffer().getOffsets();
int subRasterOffset =
(rectY-raster.getSampleModelTranslateY())*scanlineStride+
(rectX-raster.getSampleModelTranslateX())*pixelStride;
if (dataBufferOffsets.length == 1) {
int theDataBufferOffset = dataBufferOffsets[0];
for (int i = 0; i < numBands; i++) {
bandDataOffsets[i] = bandOffsets[i] +
theDataBufferOffset + subRasterOffset;
}
} else if (dataBufferOffsets.length == bandDataOffsets.length) {
for (int i = 0; i < numBands; i++) {
bandDataOffsets[i] = bandOffsets[i] +
dataBufferOffsets[i] + subRasterOffset;
}
} else {
throw new RuntimeException(JaiI18N.getString("RasterAccessor0"));
}
switch (formatTagID & DATATYPE_MASK) {
case DataBuffer.TYPE_BYTE:
DataBufferByte dbb = (DataBufferByte)raster.getDataBuffer();
byteDataArrays = new byte[numBands][];
for (int i = 0; i < numBands; i++) {
byteDataArrays[i] = dbb.getData(bankIndices[i]);
}
break;
case DataBuffer.TYPE_USHORT:
DataBufferUShort dbus =
(DataBufferUShort)raster.getDataBuffer();
shortDataArrays = new short[numBands][];
for (int i = 0; i < numBands; i++) {
shortDataArrays[i] = dbus.getData(bankIndices[i]);
}
break;
case DataBuffer.TYPE_SHORT:
DataBufferShort dbs = (DataBufferShort)raster.getDataBuffer();
shortDataArrays = new short[numBands][];
for (int i = 0; i < numBands; i++) {
shortDataArrays[i] = dbs.getData(bankIndices[i]);
}
break;
case DataBuffer.TYPE_INT:
DataBufferInt dbi = (DataBufferInt)raster.getDataBuffer();
intDataArrays = new int[numBands][];
for (int i = 0; i < numBands; i++) {
intDataArrays[i] = dbi.getData(bankIndices[i]);
}
break;
case DataBuffer.TYPE_FLOAT:
DataBuffer dbf = (DataBuffer)raster.getDataBuffer();
floatDataArrays = new float[numBands][];
for (int i = 0; i < numBands; i++) {
floatDataArrays[i] =
DataBufferUtils.getDataFloat(dbf, bankIndices[i]);
}
break;
case DataBuffer.TYPE_DOUBLE:
DataBuffer dbd = (DataBuffer)raster.getDataBuffer();
doubleDataArrays = new double[numBands][];
for (int i = 0; i < numBands; i++) {
doubleDataArrays[i] =
DataBufferUtils.getDataDouble(dbd, bankIndices[i]);
}
break;
}
// only do this if not copied and expanded
if ((formatTagID & EXPANSION_MASK) == EXPANDED &&
theColorModel instanceof IndexColorModel) {
IndexColorModel icm = (IndexColorModel)theColorModel;
int newNumBands = icm.getNumComponents();
int mapSize = icm.getMapSize();
int newBandDataOffsets[] = new int[newNumBands];
int newScanlineStride = rectWidth*newNumBands;
int newPixelStride = newNumBands;
byte ctable[][] = new byte[newNumBands][mapSize];
icm.getReds(ctable[0]);
icm.getGreens(ctable[1]);
icm.getBlues(ctable[2]);
byte rtable[] = ctable[0];
byte gtable[] = ctable[1];
byte btable[] = ctable[2];
byte atable[] = null;
if (newNumBands == 4) {
icm.getAlphas(ctable[3]);
atable = ctable[3];
}
for (int i = 0; i < newNumBands; i++) {
newBandDataOffsets[i] = i;
}
switch (formatTagID & DATATYPE_MASK) {
case DataBuffer.TYPE_BYTE: {
byte newBArray[] =
new byte[rectWidth*rectHeight*newNumBands];
byte byteDataArray[] = byteDataArrays[0];
int scanlineOffset = bandDataOffsets[0];
int newScanlineOffset = 0;
for (int j = 0; j < rectHeight; j++) {
int pixelOffset = scanlineOffset;
int newPixelOffset = newScanlineOffset;
for (int i = 0; i < rectWidth; i++) {
int index = byteDataArray[pixelOffset] & 0xff;
for (int k = 0; k < newNumBands; k++) {
newBArray[newPixelOffset+k] =
ctable[k][index];
}
pixelOffset += pixelStride;
newPixelOffset += newPixelStride;
}
scanlineOffset += scanlineStride;
newScanlineOffset += newScanlineStride;
}
byteDataArrays = new byte[newNumBands][];
for (int i = 0; i < newNumBands; i++) {
byteDataArrays[i] = newBArray;
}
}
break;
case DataBuffer.TYPE_USHORT: {
short newIArray[] =
new short[rectWidth*rectHeight*newNumBands];
short shortDataArray[] = shortDataArrays[0];
int scanlineOffset = bandDataOffsets[0];
int newScanlineOffset = 0;
for (int j = 0; j < rectHeight; j++) {
int pixelOffset = scanlineOffset;
int newPixelOffset = newScanlineOffset;
for (int i = 0; i < rectWidth; i++) {
int index = (shortDataArray[pixelOffset] & 0xffff);
for (int k = 0; k < newNumBands; k++) {
newIArray[newPixelOffset+k] =
(short)(ctable[k][index] & 0xff);
}
pixelOffset += pixelStride;
newPixelOffset += newPixelStride;
}
scanlineOffset += scanlineStride;
newScanlineOffset += newScanlineStride;
}
shortDataArrays = new short[newNumBands][];
for (int i = 0; i < newNumBands; i++) {
shortDataArrays[i] = newIArray;
}
}
break;
case DataBuffer.TYPE_SHORT: {
short newIArray[] =
new short[rectWidth*rectHeight*newNumBands];
short shortDataArray[] = shortDataArrays[0];
int scanlineOffset = bandDataOffsets[0];
int newScanlineOffset = 0;
for (int j = 0; j < rectHeight; j++) {
int pixelOffset = scanlineOffset;
int newPixelOffset = newScanlineOffset;
for (int i = 0; i < rectWidth; i++) {
int index = shortDataArray[pixelOffset];
for (int k = 0; k < newNumBands; k++) {
newIArray[newPixelOffset+k] =
(short)(ctable[k][index] & 0xff);
}
pixelOffset += pixelStride;
newPixelOffset += newPixelStride;
}
scanlineOffset += scanlineStride;
newScanlineOffset += newScanlineStride;
}
shortDataArrays = new short[newNumBands][];
for (int i = 0; i < newNumBands; i++) {
shortDataArrays[i] = newIArray;
}
}
break;
case DataBuffer.TYPE_INT: {
int newIArray[] =
new int[rectWidth*rectHeight*newNumBands];
int intDataArray[] = intDataArrays[0];
int scanlineOffset = bandDataOffsets[0];
int newScanlineOffset = 0;
for (int j = 0; j < rectHeight; j++) {
int pixelOffset = scanlineOffset;
int newPixelOffset = newScanlineOffset;
for (int i = 0; i < rectWidth; i++) {
int index = intDataArray[pixelOffset];
for (int k = 0; k < newNumBands; k++) {
newIArray[newPixelOffset+k] =
(ctable[k][index] & 0xff);
}
pixelOffset += pixelStride;
newPixelOffset += newPixelStride;
}
scanlineOffset += scanlineStride;
newScanlineOffset += newScanlineStride;
}
intDataArrays = new int[newNumBands][];
for (int i = 0; i < newNumBands; i++) {
intDataArrays[i] = newIArray;
}
}
break;
case DataBuffer.TYPE_FLOAT: {
float newFArray[] =
new float[rectWidth*rectHeight*newNumBands];
float floatDataArray[] = floatDataArrays[0];
int scanlineOffset = bandDataOffsets[0];
int newScanlineOffset = 0;
for (int j = 0; j < rectHeight; j++) {
int pixelOffset = scanlineOffset;
int newPixelOffset = newScanlineOffset;
for (int i = 0; i < rectWidth; i++) {
int index = (int)floatDataArray[pixelOffset];
for (int k = 0; k < newNumBands; k++) {
newFArray[newPixelOffset+k] =
(ctable[k][index] & 0xff);
}
pixelOffset += pixelStride;
newPixelOffset += newPixelStride;
}
scanlineOffset += scanlineStride;
newScanlineOffset += newScanlineStride;
}
floatDataArrays = new float[newNumBands][];
for (int i = 0; i < newNumBands; i++) {
floatDataArrays[i] = newFArray;
}
}
break;
case DataBuffer.TYPE_DOUBLE: {
double newDArray[] =
new double[rectWidth*rectHeight*newNumBands];
double doubleDataArray[] = doubleDataArrays[0];
int scanlineOffset = bandDataOffsets[0];
int newScanlineOffset = 0;
for (int j = 0; j < rectHeight; j++) {
int pixelOffset = scanlineOffset;
int newPixelOffset = newScanlineOffset;
for (int i = 0; i < rectWidth; i++) {
int index = (int)doubleDataArray[pixelOffset];
for (int k = 0; k < newNumBands; k++) {
newDArray[newPixelOffset+k] =
(ctable[k][index] & 0xff);
}
pixelOffset += pixelStride;
newPixelOffset += newPixelStride;
}
scanlineOffset += scanlineStride;
newScanlineOffset += newScanlineStride;
}
doubleDataArrays = new double[newNumBands][];
for (int i = 0; i < newNumBands; i++) {
doubleDataArrays[i] = newDArray;
}
}
break;
}
this.numBands = newNumBands;
this.pixelStride = newPixelStride;
this.scanlineStride = newScanlineStride;
this.bandDataOffsets = newBandDataOffsets;
this.bandOffsets = newBandDataOffsets;
}
} else if ((formatTagID & COPY_MASK) == COPIED &&
(formatTagID & EXPANSION_MASK) != UNEXPANDED &&
theColorModel != null) {
this.numBands = theColorModel instanceof IndexColorModel ?
theColorModel.getNumComponents() :
raster.getSampleModel().getNumBands();
this.pixelStride = this.numBands;
this.scanlineStride = rectWidth*numBands;
this.bandOffsets = new int[numBands];
for (int i = 0; i < numBands; i++) {
bandOffsets[i] = i;
}
this.bandDataOffsets = bandOffsets;
Object odata = null;
int offset = 0;
int[] components = new int[theColorModel.getNumComponents()];
switch (formatTagID & DATATYPE_MASK) {
case DataBuffer.TYPE_INT:
int idata[] = new int[rectWidth*rectHeight*numBands];
intDataArrays = new int[numBands][];
for (int i = 0; i < numBands; i++) {
intDataArrays[i] = idata;
}
odata = raster.getDataElements(rectX, rectY, null);
offset = 0;
// Workaround for bug in BytePackedRaster
byte[] bdata = null;
if (raster instanceof sun.awt.image.BytePackedRaster) {
bdata = (byte[])odata;
}
for (int j = rectY; j < rectY+rectHeight; j++) {
for (int i = rectX; i < rectX+rectWidth; i++) {
if (bdata != null) {
bdata[0] = (byte)raster.getSample(i, j, 0);
} else {
raster.getDataElements(i,j,odata);
}
theColorModel.getComponents(odata, components, 0);
idata[offset] = components[0];
idata[offset+1] = components[1];
idata[offset+2] = components[2];
if (numBands > 3) {
idata[offset+3] = components[3];
}
offset += pixelStride;
}
}
break;
case DataBuffer.TYPE_FLOAT:
float fdata[] = new float[rectWidth*rectHeight*numBands];
floatDataArrays = new float[numBands][];
for (int i = 0; i < numBands; i++) {
floatDataArrays[i] = fdata;
}
odata = null;
offset = 0;
for (int j = rectY; j < rectY+rectHeight; j++) {
for (int i = rectX; i < rectX+rectWidth; i++) {
odata = raster.getDataElements(i,j,odata);
theColorModel.getComponents(odata, components, 0);
fdata[offset] = components[0];
fdata[offset+1] = components[1];
fdata[offset+2] = components[2];
if (numBands > 3) {
fdata[offset+3] = components[3];
}
offset += pixelStride;
}
}
break;
case DataBuffer.TYPE_DOUBLE:
double ddata[] = new double[rectWidth*rectHeight*numBands];
doubleDataArrays = new double[numBands][];
for (int i = 0; i < numBands; i++) {
doubleDataArrays[i] = ddata;
}
odata = null;
offset = 0;
for (int j = rectY; j < rectY+rectHeight; j++) {
for (int i = rectX; i < rectX+rectWidth; i++) {
odata = raster.getDataElements(i,j,odata);
theColorModel.getComponents(odata, components, 0);
ddata[offset] = components[0];
ddata[offset+1] = components[1];
ddata[offset+2] = components[2];
if (numBands > 3) {
ddata[offset+3] = components[3];
}
offset += pixelStride;
}
}
break;
}
} else {
// if ((formatTagID & COPY_MASK) == COPIED &&
// (formatTagID & EXPANSION_MASK) == UNEXPANDED) {
// this has become a catchall case. Specifically for
// Rasters with null colormodels. So we take out the
// if as the boolean clause will get way complicated
// otherwise.
this.numBands = rft.getNumBands();
this.pixelStride = this.numBands;
this.scanlineStride = rectWidth*numBands;
this.bandDataOffsets = rft.getBandOffsets();
this.bandOffsets = this.bandDataOffsets;
switch (formatTagID & DATATYPE_MASK) {
case DataBuffer.TYPE_INT:
int idata[] = raster.getPixels(rectX,rectY,
rectWidth,rectHeight,
(int[])null);
intDataArrays = new int[numBands][];
for (int i = 0; i < numBands; i++) {
intDataArrays[i] = idata;
}
break;
case DataBuffer.TYPE_FLOAT:
float fdata[] = raster.getPixels(rectX,rectY,
rectWidth,rectHeight,
(float[])null);
floatDataArrays = new float[numBands][];
for (int i = 0; i < numBands; i++) {
floatDataArrays[i] = fdata;
}
break;
case DataBuffer.TYPE_DOUBLE:
double ddata[] = raster.getPixels(rectX,rectY,
rectWidth,rectHeight,
(double[])null);
doubleDataArrays = new double[numBands][];
for (int i = 0; i < numBands; i++) {
doubleDataArrays[i] = ddata;
}
break;
}
}
}
/**
* Returns the x coordinate of the upper-left corner of the
* RasterAccessor's accessible area.
*/
public int getX() {
return rectX;
}
/**
* Returns the y coordinate of the upper-left corner of the
* RasterAccessor's accessible area.
*/
public int getY() {
return rectY;
}
/** Returns the width of the
* RasterAccessor's accessible area.
*/
public int getWidth() {
return rectWidth;
}
/** Returns the height of the
* RasterAccessor's accessible area.
*/
public int getHeight() {
return rectHeight;
}
/** Returns the numBands of the presented area. */
public int getNumBands() {
return numBands;
}
/**
* Whether the <code>RasterAccessor</code> represents binary data.
* This occurs when the <code>Raster</code> has a
* <code>MultiPixelPackedSampleModel</code> with a single band and
* one bit per pixel.
*
* @since JAI 1.1
*/
public boolean isBinary() {
return (formatTagID & TAG_BINARY) == TAG_BINARY &&
ImageUtil.isBinary(raster.getSampleModel());
}
/**
* For the case of binary data (<code>isBinary()</code> returns
* <code>true</code>), return the binary data as a packed byte array.
* The data will be packed as eight bits per byte with no bit offset,
* i.e., the first bit in each image line will be the left-most of the
* first byte of the line. The line stride in bytes will be
* <code>(int)((getWidth()+7)/8)</code>. The length of the returned
* array will be the line stride multiplied by <code>getHeight()</code>
*
* @return the binary data as a packed array of bytes with zero offset
* of <code>null</code> if the data are not binary.
*
* @since JAI 1.1
*/
public byte[] getBinaryDataArray() {
if(binaryDataArray == null && isBinary()) {
binaryDataArray =
ImageUtil.getPackedBinaryData(raster, new Rectangle(rectX, rectY,
rectWidth,
rectHeight));
}
return binaryDataArray;
}
/**
* Returns the image data as a byte array. Non-null only if
* getDataType = DataBuffer.TYPE_BYTE.
*
* <p> For the case of binary data the corresponding instance variable
* <code>byteDataArrays</code> will not be initialized until this
* method or <code>getByteDataArray(int b)</code> is invoked. The
* binary data will be returned as bytes with value 0 or 1.
*/
public byte[][] getByteDataArrays() {
if(byteDataArrays == null && isBinary()) {
byte[] bdata =
ImageUtil.getUnpackedBinaryData(raster,
new Rectangle(rectX, rectY,
rectWidth,
rectHeight));
byteDataArrays = new byte[][] {bdata};
}
return byteDataArrays;
}
/**
* Returns the image data as a byte array for a specific band.
* Non-null only if getDataType = DataBuffer.TYPE_BYTE.
*/
public byte[] getByteDataArray(int b) {
byte[][] bda = getByteDataArrays();
return (bda == null ? null : bda[b]);
}
/**
* Returns the image data as a short array. Non-null only if
* getDataType = DataBuffer.TYPE_USHORT or DataBuffer.TYPE_SHORT.
*/
public short[][] getShortDataArrays() {
return shortDataArrays;
}
/**
* Returns the image data as a short array for a specific band.
* Non-null only if getDataType = DataBuffer.TYPE_USHORT or
* DataBuffer.TYPE_SHORT.
*/
public short[] getShortDataArray(int b) {
return (shortDataArrays == null ? null : shortDataArrays[b]);
}
/**
* Returns the image data as an int array. Non-null only if
* getDataType = DataBuffer.TYPE_INT.
*/
public int[][] getIntDataArrays() {
return intDataArrays;
}
/**
* Returns the image data as an int array for a specific band.
* Non-null only if getDataType = DataBuffer.TYPE_INT.
*/
public int[] getIntDataArray(int b) {
return (intDataArrays == null ? null : intDataArrays[b]);
}
/**
* Returns the image data as a float array. Non-null only if
* getDataType = DataBuffer.TYPE_FLOAT.
*/
public float[][] getFloatDataArrays() {
return floatDataArrays;
}
/**
* Returns the image data as a float array for a specific band.
* Non-null only if getDataType = DataBuffer.TYPE_FLOAT.
*/
public float[] getFloatDataArray(int b) {
return (floatDataArrays == null ? null : floatDataArrays[b]);
}
/**
* Returns the image data as a double array. Non-null only if
* getDataType = DataBuffer.TYPE_DOUBLE
*/
public double[][] getDoubleDataArrays() {
return doubleDataArrays;
}
/**
* Returns the image data as a double array for a specific band.
* Non-null only if getDataType = DataBuffer.TYPE_DOUBLE
*/
public double[] getDoubleDataArray(int b) {
return (doubleDataArrays == null ? null : doubleDataArrays[b]);
}
/**
* Returns the image data as an Object for a specific band.
*
* @param b The index of the image band of interest.
*/
public Object getDataArray(int b) {
Object dataArray = null;
switch(getDataType()) {
case DataBuffer.TYPE_BYTE:
dataArray = getByteDataArray(b);
break;
case DataBuffer.TYPE_SHORT:
case DataBuffer.TYPE_USHORT:
dataArray = getShortDataArray(b);
break;
case DataBuffer.TYPE_INT:
dataArray = getIntDataArray(b);
break;
case DataBuffer.TYPE_FLOAT:
dataArray = getFloatDataArray(b);
break;
case DataBuffer.TYPE_DOUBLE:
dataArray = getDoubleDataArray(b);
break;
default:
dataArray = null;
}
return dataArray;
}
/** Returns the bandDataOffsets into the dataArrays. */
public int[] getBandOffsets() {
return bandDataOffsets;
}
/**
* Returns the offset of all band's samples from any
* pixel offset.
*/
public int[] getOffsetsForBands() {
return bandOffsets;
}
/**
* Returns the offset of a specific band's first sample into the
* DataBuffer including the DataBuffer's offset.
*/
public int getBandOffset(int b) {
return bandDataOffsets[b];
}
/**
* Returns the offset of a specified band's sample from any
* pixel offset.
*/
public int getOffsetForBand(int b) {
return bandOffsets[b];
}
/**
* Returns the scanlineStride for the image data.
*
* <p> For binary data this stride is applies to the arrays returned by
* <code>getByteDataArray()</code> and <code>getByteDataArrays()</code>
* if the data are accessed as bytes; it does not apply to the array
* returned by <code>getBinaryDataArray()</code> when the data are
* accessed as bits packed into bytes.
*/
public int getScanlineStride() {
return scanlineStride;
}
/** Returns the pixelStride for the image data. */
public int getPixelStride() {
return pixelStride;
}
/**
* Returns the data type of the RasterAccessor object. Note that
* this datatype is not necessarily the same data type as the
* underlying raster.
*/
public int getDataType() {
return formatTagID & DATATYPE_MASK;
}
/**
* Returns true if the RasterAccessors's data is copied from it's
* raster.
*/
public boolean isDataCopy() {
return ((formatTagID & COPY_MASK) == COPIED);
}
/**
* For the case of binary data (<code>isBinary()</code> returns
* <code>true</code>), copy the binary data back into the
* <code>Raster</code> of the <code>RasterAccessor</code>. If
* this method is invoked in the non-binary case it does nothing.
* Any bit offset in the original <code>SampleModel</code> will be
* accounted for.
*
* @since JAI 1.1
*/
// Note: ALL branches of this method have been tested. (bpb 10 May 2000)
public void copyBinaryDataToRaster() {
if(binaryDataArray == null || !isBinary()) {
return;
}
ImageUtil.setPackedBinaryData(binaryDataArray,
(WritableRaster)raster,
new Rectangle(rectX, rectY,
rectWidth, rectHeight));
}
/**
* Copies data back into the RasterAccessor's raster. Note that
* the data is cast from the intermediate data format to
* the raster's format. If clamping is needed, the call
* clampDataArrays() method needs to be called before
* calling the copyDataToRaster() method.
* Note: the raster is expected to be writable - typically a
* destination raster - otherwise, a run-time exception will occur.
*
* <p> If the data are binary, then the target bit will be set if
* and only if the corresponding byte is non-zero.
*/
public void copyDataToRaster() {
if (isDataCopy()) {
// Writeback should only be necessary on destRasters which
// should be writable so this cast should succeed.
WritableRaster wr = (WritableRaster)raster;
switch (getDataType()) {
case DataBuffer.TYPE_BYTE:
// Note: ALL branches of this case have been tested.
// (bpb 10 May 2000)
if(!isBinary()) {
// If this exception occurs then there is a logic
// error within this accessor since the only case
// wherein byte data should be COPIED is when the
// data set is binary.
throw new RuntimeException(JaiI18N.getString("RasterAccessor1"));
}
// This case only occurs for binary src and dst.
ImageUtil.setUnpackedBinaryData(byteDataArrays[0],
wr,
new Rectangle(rectX, rectY,
rectWidth,
rectHeight));
break;
case DataBuffer.TYPE_INT:
wr.setPixels(rectX,rectY,
rectWidth,rectHeight,
intDataArrays[0]);
break;
case DataBuffer.TYPE_FLOAT:
wr.setPixels(rectX,rectY,
rectWidth,rectHeight,
floatDataArrays[0]);
break;
case DataBuffer.TYPE_DOUBLE:
wr.setPixels(rectX,rectY,
rectWidth,rectHeight,
doubleDataArrays[0]);
break;
}
}
}
/**
* Indicates if the RasterAccessor has a larger dynamic range than
* the underlying Raster. Except in special cases, where the op
* knows something special, this call will determine whether or
* not clampDataArrays() needs to be called.
*/
public boolean needsClamping() {
int bits[] = raster.getSampleModel().getSampleSize();
// Do we even need a clamp? We do if there's any band
// of the source image stored in that's less than 32 bits
// and is stored in a byte, short or int format. (The automatic
// casts between floats/doubles and 32-bit ints in setPixel()
// do what we want.)
for (int i = 0; i < bits.length; i++) {
if (bits[i] < 32) {
return true;
}
}
return false;
}
/**
* Clamps data array values to a range that the underlying raster
* can deal with. For example, if the underlying raster stores
* data as bytes, but the samples are unpacked into integer arrays by
* the RasterAccessor for an operation, the operation will
* need to call clampDataArrays() so that the data in the int
* arrays is restricted to the range 0..255 before a setPixels()
* call is made on the underlying raster. Note that some
* operations (for example, lookup) can guarantee that their
* results don't need clamping so they can call
* RasterAccessor.copyDataToRaster() without first calling this
* function.
*/
public void clampDataArrays () {
int bits[] = raster.getSampleModel().getSampleSize();
// Do we even need a clamp? We do if there's any band
// of the source image stored in that's less than 32 bits
// and is stored in a byte, short or int format. (The automatic
// casts between floats/doubles and 32-bit ints in setPixel()
// do what we want.)
boolean needClamp = false;
boolean uniformBitSize = true;
int bitSize = bits[0];
for (int i = 0; i < bits.length; i++) {
if (bits[i] < 32) {
needClamp = true;
}
if (bits[i] != bitSize) {
uniformBitSize = false;
}
}
if (!needClamp) {
return;
}
int dataType = raster.getDataBuffer().getDataType();
double hiVals[] = new double[bits.length];
double loVals[] = new double[bits.length];
if (dataType == DataBuffer.TYPE_USHORT &&
uniformBitSize && bits[0] == 16) {
for (int i = 0; i < bits.length; i++) {
hiVals[i] = (double)0xFFFF;
loVals[i] = (double)0;
}
} else if (dataType == DataBuffer.TYPE_SHORT &&
uniformBitSize && bits[0] == 16) {
for (int i = 0; i < bits.length; i++) {
hiVals[i] = (double)Short.MAX_VALUE;
loVals[i] = (double)Short.MIN_VALUE;
}
} else if (dataType == DataBuffer.TYPE_INT &&
uniformBitSize && bits[0] == 32) {
for (int i = 0; i < bits.length; i++) {
hiVals[i] = (double)Integer.MAX_VALUE;
loVals[i] = (double)Integer.MIN_VALUE;
}
} else {
for (int i = 0; i < bits.length; i++) {
hiVals[i] = (double)((1 << bits[i]) - 1);
loVals[i] = (double)0;
}
}
clampDataArray(hiVals,loVals);
}
private void clampDataArray(double hiVals[], double loVals[]) {
switch (getDataType()) {
case DataBuffer.TYPE_INT:
clampIntArrays(toIntArray(hiVals),toIntArray(loVals));
break;
case DataBuffer.TYPE_FLOAT:
clampFloatArrays(toFloatArray(hiVals),toFloatArray(loVals));
break;
case DataBuffer.TYPE_DOUBLE:
clampDoubleArrays(hiVals,loVals);
break;
}
}
private int[] toIntArray(double vals[]) {
int returnVals[] = new int[vals.length];
for (int i = 0; i < vals.length; i++) {
returnVals[i] = (int)vals[i];
}
return returnVals;
}
private float[] toFloatArray(double vals[]) {
float returnVals[] = new float[vals.length];
for (int i = 0; i < vals.length; i++) {
returnVals[i] = (float)vals[i];
}
return returnVals;
}
private void clampIntArrays(int hiVals[], int loVals[]) {
int width = rectWidth;
int height = rectHeight;
for (int k = 0; k < numBands; k++) {
int data[] = intDataArrays[k];
int scanlineOffset = bandDataOffsets[k];
int hiVal = hiVals[k];
int loVal = loVals[k];
for (int j = 0; j < height; j++) {
int pixelOffset = scanlineOffset;
for (int i = 0; i < width; i++) {
int tmp = data[pixelOffset];
if (tmp < loVal) {
data[pixelOffset] = loVal;
} else if (tmp > hiVal) {
data[pixelOffset] = hiVal;
}
pixelOffset += pixelStride;
}
scanlineOffset += scanlineStride;
}
}
}
private void clampFloatArrays(float hiVals[], float loVals[]) {
int width = rectWidth;
int height = rectHeight;
for (int k = 0; k < numBands; k++) {
float data[] = floatDataArrays[k];
int scanlineOffset = bandDataOffsets[k];
float hiVal = hiVals[k];
float loVal = loVals[k];
for (int j = 0; j < height; j++) {
int pixelOffset = scanlineOffset;
for (int i = 0; i < width; i++) {
float tmp = data[pixelOffset];
if (tmp < loVal) {
data[pixelOffset] = loVal;
} else if (tmp > hiVal) {
data[pixelOffset] = hiVal;
}
pixelOffset += pixelStride;
}
scanlineOffset += scanlineStride;
}
}
}
private void clampDoubleArrays(double hiVals[], double loVals[]) {
int width = rectWidth;
int height = rectHeight;
for (int k = 0; k < numBands; k++) {
double data[] = doubleDataArrays[k];
int scanlineOffset = bandDataOffsets[k];
double hiVal = hiVals[k];
double loVal = loVals[k];
for (int j = 0; j < height; j++) {
int pixelOffset = scanlineOffset;
for (int i = 0; i < width; i++) {
double tmp = data[pixelOffset];
if (tmp < loVal) {
data[pixelOffset] = loVal;
} else if (tmp > hiVal) {
data[pixelOffset] = hiVal;
}
pixelOffset += pixelStride;
}
scanlineOffset += scanlineStride;
}
}
}
}