/*
* @(#)MultiPixelPackedSampleModel.java 1.38 06/02/14
*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
/* ****************************************************************
******************************************************************
******************************************************************
*** COPYRIGHT (c) Eastman Kodak Company, 1997
*** As an unpublished work pursuant to Title 17 of the United
*** States Code. All rights reserved.
******************************************************************
******************************************************************
******************************************************************/
package java.awt.image;
/**
* The <code>MultiPixelPackedSampleModel</code> class represents
* one-banded images and can pack multiple one-sample
* pixels into one data element. Pixels are not allowed to span data elements.
* The data type can be DataBuffer.TYPE_BYTE, DataBuffer.TYPE_USHORT,
* or DataBuffer.TYPE_INT. Each pixel must be a power of 2 number of bits
* and a power of 2 number of pixels must fit exactly in one data element.
* Pixel bit stride is equal to the number of bits per pixel. Scanline
* stride is in data elements and the last several data elements might be
* padded with unused pixels. Data bit offset is the offset in bits from
* the beginning of the {@link DataBuffer} to the first pixel and must be
* a multiple of pixel bit stride.
* <p>
* The following code illustrates extracting the bits for pixel
* <code>x, y</code> from <code>DataBuffer</code> <code>data</code>
* and storing the pixel data in data elements of type
* <code>dataType</code>:
* <pre>
* int dataElementSize = DataBuffer.getDataTypeSize(dataType);
* int bitnum = dataBitOffset + x*pixelBitStride;
* int element = data.getElem(y*scanlineStride + bitnum/dataElementSize);
* int shift = dataElementSize - (bitnum & (dataElementSize-1))
* - pixelBitStride;
* int pixel = (element >> shift) & ((1 << pixelBitStride) - 1);
* </pre>
*/
public class MultiPixelPackedSampleModel extends SampleModel
{
/** The number of bits from one pixel to the next. */
int pixelBitStride;
/** Bitmask that extracts the rightmost pixel of a data element. */
int bitMask;
/**
* The number of pixels that fit in a data element. Also used
* as the number of bits per pixel.
*/
int pixelsPerDataElement;
/** The size of a data element in bits. */
int dataElementSize;
/** The bit offset into the data array where the first pixel begins.
*/
int dataBitOffset;
/** ScanlineStride of the data buffer described in data array elements. */
int scanlineStride;
/**
* Constructs a <code>MultiPixelPackedSampleModel</code> with the
* specified data type, width, height and number of bits per pixel.
* @param dataType the data type for storing samples
* @param w the width, in pixels, of the region of
* image data described
* @param h the height, in pixels, of the region of
* image data described
* @param numberOfBits the number of bits per pixel
* @throws IllegalArgumentException if <code>dataType</code> is not
* either <code>DataBuffer.TYPE_BYTE</code>,
* <code>DataBuffer.TYPE_USHORT</code>, or
* <code>DataBuffer.TYPE_INT</code>
*/
public MultiPixelPackedSampleModel(int dataType,
int w,
int h,
int numberOfBits) {
this(dataType,w,h,
numberOfBits,
(w*numberOfBits+DataBuffer.getDataTypeSize(dataType)-1)/
DataBuffer.getDataTypeSize(dataType),
0);
if (dataType != DataBuffer.TYPE_BYTE &&
dataType != DataBuffer.TYPE_USHORT &&
dataType != DataBuffer.TYPE_INT) {
throw new IllegalArgumentException("Unsupported data type "+
dataType);
}
}
/**
* Constructs a <code>MultiPixelPackedSampleModel</code> with
* specified data type, width, height, number of bits per pixel,
* scanline stride and data bit offset.
* @param dataType the data type for storing samples
* @param w the width, in pixels, of the region of
* image data described
* @param h the height, in pixels, of the region of
* image data described
* @param numberOfBits the number of bits per pixel
* @param scanlineStride the line stride of the image data
* @param dataBitOffset the data bit offset for the region of image
* data described
* @exception RasterFormatException if the number of bits per pixel
* is not a power of 2 or if a power of 2 number of
* pixels do not fit in one data element.
* @throws IllegalArgumentException if <code>w</code> or
* <code>h</code> is not greater than 0
* @throws IllegalArgumentException if <code>dataType</code> is not
* either <code>DataBuffer.TYPE_BYTE</code>,
* <code>DataBuffer.TYPE_USHORT</code>, or
* <code>DataBuffer.TYPE_INT</code>
*/
public MultiPixelPackedSampleModel(int dataType, int w, int h,
int numberOfBits,
int scanlineStride,
int dataBitOffset) {
super(dataType, w, h, 1);
if (dataType != DataBuffer.TYPE_BYTE &&
dataType != DataBuffer.TYPE_USHORT &&
dataType != DataBuffer.TYPE_INT) {
throw new IllegalArgumentException("Unsupported data type "+
dataType);
}
this.dataType = dataType;
this.pixelBitStride = numberOfBits;
this.scanlineStride = scanlineStride;
this.dataBitOffset = dataBitOffset;
this.dataElementSize = DataBuffer.getDataTypeSize(dataType);
this.pixelsPerDataElement = dataElementSize/numberOfBits;
if (pixelsPerDataElement*numberOfBits != dataElementSize) {
throw new RasterFormatException("MultiPixelPackedSampleModel " +
"does not allow pixels to " +
"span data element boundaries");
}
this.bitMask = (1 << numberOfBits) - 1;
}
/**
* Creates a new <code>MultiPixelPackedSampleModel</code> with the
* specified width and height. The new
* <code>MultiPixelPackedSampleModel</code> has the
* same storage data type and number of bits per pixel as this
* <code>MultiPixelPackedSampleModel</code>.
* @param w the specified width
* @param h the specified height
* @return a {@link SampleModel} with the specified width and height
* and with the same storage data type and number of bits per pixel
* as this <code>MultiPixelPackedSampleModel</code>.
* @throws IllegalArgumentException if <code>w</code> or
* <code>h</code> is not greater than 0
*/
public SampleModel createCompatibleSampleModel(int w, int h) {
SampleModel sampleModel =
new MultiPixelPackedSampleModel(dataType, w, h, pixelBitStride);
return sampleModel;
}
/**
* Creates a <code>DataBuffer</code> that corresponds to this
* <code>MultiPixelPackedSampleModel</code>. The
* <code>DataBuffer</code> object's data type and size
* is consistent with this <code>MultiPixelPackedSampleModel</code>.
* The <code>DataBuffer</code> has a single bank.
* @return a <code>DataBuffer</code> with the same data type and
* size as this <code>MultiPixelPackedSampleModel</code>.
*/
public DataBuffer createDataBuffer() {
DataBuffer dataBuffer = null;
int size = (int)scanlineStride*height;
switch (dataType) {
case DataBuffer.TYPE_BYTE:
dataBuffer = new DataBufferByte(size+(dataBitOffset+7)/8);
break;
case DataBuffer.TYPE_USHORT:
dataBuffer = new DataBufferUShort(size+(dataBitOffset+15)/16);
break;
case DataBuffer.TYPE_INT:
dataBuffer = new DataBufferInt(size+(dataBitOffset+31)/32);
break;
}
return dataBuffer;
}
/**
* Returns the number of data elements needed to transfer one pixel
* via the {@link #getDataElements} and {@link #setDataElements}
* methods. For a <code>MultiPixelPackedSampleModel</code>, this is
* one.
* @return the number of data elements.
*/
public int getNumDataElements() {
return 1;
}
/**
* Returns the number of bits per sample for all bands.
* @return the number of bits per sample.
*/
public int[] getSampleSize() {
int sampleSize[] = {pixelBitStride};
return sampleSize;
}
/**
* Returns the number of bits per sample for the specified band.
* @param band the specified band
* @return the number of bits per sample for the specified band.
*/
public int getSampleSize(int band) {
return pixelBitStride;
}
/**
* Returns the offset of pixel (x, y) in data array elements.
* @param x the X coordinate of the specified pixel
* @param y the Y coordinate of the specified pixel
* @return the offset of the specified pixel.
*/
public int getOffset(int x, int y) {
int offset = y * scanlineStride;
offset += (x*pixelBitStride+dataBitOffset)/dataElementSize;
return offset;
}
/**
* Returns the offset, in bits, into the data element in which it is
* stored for the <code>x</code>th pixel of a scanline.
* This offset is the same for all scanlines.
* @param x the specified pixel
* @return the bit offset of the specified pixel.
*/
public int getBitOffset(int x){
return (x*pixelBitStride+dataBitOffset)%dataElementSize;
}
/**
* Returns the scanline stride.
* @return the scanline stride of this
* <code>MultiPixelPackedSampleModel</code>.
*/
public int getScanlineStride() {
return scanlineStride;
}
/**
* Returns the pixel bit stride in bits. This value is the same as
* the number of bits per pixel.
* @return the <code>pixelBitStride</code> of this
* <code>MultiPixelPackedSampleModel</code>.
*/
public int getPixelBitStride() {
return pixelBitStride;
}
/**
* Returns the data bit offset in bits.
* @return the <code>dataBitOffset</code> of this
* <code>MultiPixelPackedSampleModel</code>.
*/
public int getDataBitOffset() {
return dataBitOffset;
}
/**
* Returns the TransferType used to transfer pixels by way of the
* <code>getDataElements</code> and <code>setDataElements</code>
* methods. The TransferType might or might not be the same as the
* storage DataType. The TransferType is one of
* DataBuffer.TYPE_BYTE, DataBuffer.TYPE_USHORT,
* or DataBuffer.TYPE_INT.
* @return the transfertype.
*/
public int getTransferType() {
if (pixelBitStride > 16)
return DataBuffer.TYPE_INT;
else if (pixelBitStride > 8)
return DataBuffer.TYPE_USHORT;
else
return DataBuffer.TYPE_BYTE;
}
/**
* Creates a new <code>MultiPixelPackedSampleModel</code> with a
* subset of the bands of this
* <code>MultiPixelPackedSampleModel</code>. Since a
* <code>MultiPixelPackedSampleModel</code> only has one band, the
* bands argument must have a length of one and indicate the zeroth
* band.
* @param bands the specified bands
* @return a new <code>SampleModel</code> with a subset of bands of
* this <code>MultiPixelPackedSampleModel</code>.
* @exception RasterFormatException if the number of bands requested
* is not one.
* @throws IllegalArgumentException if <code>w</code> or
* <code>h</code> is not greater than 0
*/
public SampleModel createSubsetSampleModel(int bands[]) {
if (bands != null) {
if (bands.length != 1)
throw new RasterFormatException("MultiPixelPackedSampleModel has "
+ "only one band.");
}
SampleModel sm = createCompatibleSampleModel(width, height);
return sm;
}
/**
* Returns as <code>int</code> the sample in a specified band for the
* pixel located at (x, y). An
* <code>ArrayIndexOutOfBoundsException</code> is thrown if the
* coordinates are not in bounds.
* @param x the X coordinate of the specified pixel
* @param y the Y coordinate of the specified pixel
* @param b the band to return, which is assumed to be 0
* @param data the <code>DataBuffer</code> containing the image
* data
* @return the specified band containing the sample of the specified
* pixel.
* @exception ArrayIndexOutOfBoundException if the specified
* coordinates are not in bounds.
* @see #setSample(int, int, int, int, DataBuffer)
*/
public int getSample(int x, int y, int b, DataBuffer data) {
// 'b' must be 0
if ((x < 0) || (y < 0) || (x >= width) || (y >= height) ||
(b != 0)) {
throw new ArrayIndexOutOfBoundsException
("Coordinate out of bounds!");
}
int bitnum = dataBitOffset + x*pixelBitStride;
int element = data.getElem(y*scanlineStride + bitnum/dataElementSize);
int shift = dataElementSize - (bitnum & (dataElementSize-1))
- pixelBitStride;
return (element >> shift) & bitMask;
}
/**
* Sets a sample in the specified band for the pixel located at
* (x, y) in the <code>DataBuffer</code> using an
* <code>int</code> for input.
* An <code>ArrayIndexOutOfBoundsException</code> is thrown if the
* coordinates are not in bounds.
* @param x the X coordinate of the specified pixel
* @param y the Y coordinate of the specified pixel
* @param b the band to return, which is assumed to be 0
* @param s the input sample as an <code>int</code>
* @param data the <code>DataBuffer</code> where image data is stored
* @exception ArrayIndexOutOfBoundsException if the coordinates are
* not in bounds.
* @see #getSample(int, int, int, DataBuffer)
*/
public void setSample(int x, int y, int b, int s,
DataBuffer data) {
// 'b' must be 0
if ((x < 0) || (y < 0) || (x >= width) || (y >= height) ||
(b != 0)) {
throw new ArrayIndexOutOfBoundsException
("Coordinate out of bounds!");
}
int bitnum = dataBitOffset + x * pixelBitStride;
int index = y * scanlineStride + (bitnum / dataElementSize);
int shift = dataElementSize - (bitnum & (dataElementSize-1))
- pixelBitStride;
int element = data.getElem(index);
element &= ~(bitMask << shift);
element |= (s & bitMask) << shift;
data.setElem(index,element);
}
/**
* Returns data for a single pixel in a primitive array of type
* TransferType. For a <code>MultiPixelPackedSampleModel</code>,
* the array has one element, and the type is the smallest of
* DataBuffer.TYPE_BYTE, DataBuffer.TYPE_USHORT, or DataBuffer.TYPE_INT
* that can hold a single pixel. Generally, <code>obj</code>
* should be passed in as <code>null</code>, so that the
* <code>Object</code> is created automatically and is the
* correct primitive data type.
* <p>
* The following code illustrates transferring data for one pixel from
* <code>DataBuffer</code> <code>db1</code>, whose storage layout is
* described by <code>MultiPixelPackedSampleModel</code>
* <code>mppsm1</code>, to <code>DataBuffer</code> <code>db2</code>,
* whose storage layout is described by
* <code>MultiPixelPackedSampleModel</code> <code>mppsm2</code>.
* The transfer is generally more efficient than using
* <code>getPixel</code> or <code>setPixel</code>.
* <pre>
* MultiPixelPackedSampleModel mppsm1, mppsm2;
* DataBufferInt db1, db2;
* mppsm2.setDataElements(x, y, mppsm1.getDataElements(x, y, null,
* db1), db2);
* </pre>
* Using <code>getDataElements</code> or <code>setDataElements</code>
* to transfer between two <code>DataBuffer/SampleModel</code> pairs
* is legitimate if the <code>SampleModels</code> have the same number
* of bands, corresponding bands have the same number of
* bits per sample, and the TransferTypes are the same.
* <p>
* If <code>obj</code> is not <code>null</code>, it should be a
* primitive array of type TransferType. Otherwise, a
* <code>ClassCastException</code> is thrown. An
* <code>ArrayIndexOutOfBoundsException</code> is thrown if the
* coordinates are not in bounds, or if <code>obj</code> is not
* <code>null</code> and is not large enough to hold the pixel data.
* @param x the X coordinate of the specified pixel
* @param y the Y coordinate of the specified pixel
* @param obj a primitive array in which to return the pixel data or
* <code>null</code>.
* @param data the <code>DataBuffer</code> containing the image data.
* @return an <code>Object</code> containing data for the specified
* pixel.
* @exception ClassCastException if <code>obj</code> is not a
* primitive array of type TransferType or is not <code>null</code>
* @exception ArrayIndexOutOfBoundsException if the coordinates are
* not in bounds, or if <code>obj</code> is not <code>null</code> or
* not large enough to hold the pixel data
* @see #setDataElements(int, int, Object, DataBuffer)
*/
public Object getDataElements(int x, int y, Object obj, DataBuffer data) {
if ((x < 0) || (y < 0) || (x >= width) || (y >= height)) {
throw new ArrayIndexOutOfBoundsException
("Coordinate out of bounds!");
}
int type = getTransferType();
int bitnum = dataBitOffset + x*pixelBitStride;
int shift = dataElementSize - (bitnum & (dataElementSize-1))
- pixelBitStride;
int element = 0;
switch(type) {
case DataBuffer.TYPE_BYTE:
byte[] bdata;
if (obj == null)
bdata = new byte[1];
else
bdata = (byte[])obj;
element = data.getElem(y*scanlineStride +
bitnum/dataElementSize);
bdata[0] = (byte)((element >> shift) & bitMask);
obj = (Object)bdata;
break;
case DataBuffer.TYPE_USHORT:
short[] sdata;
if (obj == null)
sdata = new short[1];
else
sdata = (short[])obj;
element = data.getElem(y*scanlineStride +
bitnum/dataElementSize);
sdata[0] = (short)((element >> shift) & bitMask);
obj = (Object)sdata;
break;
case DataBuffer.TYPE_INT:
int[] idata;
if (obj == null)
idata = new int[1];
else
idata = (int[])obj;
element = data.getElem(y*scanlineStride +
bitnum/dataElementSize);
idata[0] = (element >> shift) & bitMask;
obj = (Object)idata;
break;
}
return obj;
}
/**
* Returns the specified single band pixel in the first element
* of an <code>int</code> array.
* <code>ArrayIndexOutOfBoundsException</code> is thrown if the
* coordinates are not in bounds.
* @param x the X coordinate of the specified pixel
* @param y the Y coordinate of the specified pixel
* @param iArray the array containing the pixel to be returned or
* <code>null</code>
* @param data the <code>DataBuffer</code> where image data is stored
* @return an array containing the specified pixel.
* @exception ArrayIndexOutOfBoundsException if the coordinates
* are not in bounds
* @see #setPixel(int, int, int[], DataBuffer)
*/
public int[] getPixel(int x, int y, int iArray[], DataBuffer data) {
if ((x < 0) || (y < 0) || (x >= width) || (y >= height)) {
throw new ArrayIndexOutOfBoundsException
("Coordinate out of bounds!");
}
int pixels[];
if (iArray != null) {
pixels = iArray;
} else {
pixels = new int [numBands];
}
int bitnum = dataBitOffset + x*pixelBitStride;
int element = data.getElem(y*scanlineStride + bitnum/dataElementSize);
int shift = dataElementSize - (bitnum & (dataElementSize-1))
- pixelBitStride;
pixels[0] = (element >> shift) & bitMask;
return pixels;
}
/**
* Sets the data for a single pixel in the specified
* <code>DataBuffer</code> from a primitive array of type
* TransferType. For a <code>MultiPixelPackedSampleModel</code>,
* only the first element of the array holds valid data,
* and the type must be the smallest of
* DataBuffer.TYPE_BYTE, DataBuffer.TYPE_USHORT, or DataBuffer.TYPE_INT
* that can hold a single pixel.
* <p>
* The following code illustrates transferring data for one pixel from
* <code>DataBuffer</code> <code>db1</code>, whose storage layout is
* described by <code>MultiPixelPackedSampleModel</code>
* <code>mppsm1</code>, to <code>DataBuffer</code> <code>db2</code>,
* whose storage layout is described by
* <code>MultiPixelPackedSampleModel</code> <code>mppsm2</code>.
* The transfer is generally more efficient than using
* <code>getPixel</code> or <code>setPixel</code>.
* <pre>
* MultiPixelPackedSampleModel mppsm1, mppsm2;
* DataBufferInt db1, db2;
* mppsm2.setDataElements(x, y, mppsm1.getDataElements(x, y, null,
* db1), db2);
* </pre>
* Using <code>getDataElements</code> or <code>setDataElements</code> to
* transfer between two <code>DataBuffer/SampleModel</code> pairs is
* legitimate if the <code>SampleModel</code> objects have
* the same number of bands, corresponding bands have the same number of
* bits per sample, and the TransferTypes are the same.
* <p>
* <code>obj</code> must be a primitive array of type TransferType.
* Otherwise, a <code>ClassCastException</code> is thrown. An
* <code>ArrayIndexOutOfBoundsException</code> is thrown if the
* coordinates are not in bounds, or if <code>obj</code> is not large
* enough to hold the pixel data.
* @param x the X coordinate of the pixel location
* @param y the Y coordinate of the pixel location
* @param obj a primitive array containing pixel data
* @param data the <code>DataBuffer</code> containing the image data
* @see #getDataElements(int, int, Object, DataBuffer)
*/
public void setDataElements(int x, int y, Object obj, DataBuffer data) {
if ((x < 0) || (y < 0) || (x >= width) || (y >= height)) {
throw new ArrayIndexOutOfBoundsException
("Coordinate out of bounds!");
}
int type = getTransferType();
int bitnum = dataBitOffset + x * pixelBitStride;
int index = y * scanlineStride + (bitnum / dataElementSize);
int shift = dataElementSize - (bitnum & (dataElementSize-1))
- pixelBitStride;
int element = data.getElem(index);
element &= ~(bitMask << shift);
switch(type) {
case DataBuffer.TYPE_BYTE:
byte[] barray = (byte[])obj;
element |= ( ((int)(barray[0])&0xff) & bitMask) << shift;
data.setElem(index, element);
break;
case DataBuffer.TYPE_USHORT:
short[] sarray = (short[])obj;
element |= ( ((int)(sarray[0])&0xffff) & bitMask) << shift;
data.setElem(index, element);
break;
case DataBuffer.TYPE_INT:
int[] iarray = (int[])obj;
element |= (iarray[0] & bitMask) << shift;
data.setElem(index, element);
break;
}
}
/**
* Sets a pixel in the <code>DataBuffer</code> using an
* <code>int</code> array for input.
* <code>ArrayIndexOutOfBoundsException</code> is thrown if
* the coordinates are not in bounds.
* @param x the X coordinate of the pixel location
* @param y the Y coordinate of the pixel location
* @param iArray the input pixel in an <code>int</code> array
* @param data the <code>DataBuffer</code> containing the image data
* @see #getPixel(int, int, int[], DataBuffer)
*/
public void setPixel(int x, int y, int[] iArray, DataBuffer data) {
if ((x < 0) || (y < 0) || (x >= width) || (y >= height)) {
throw new ArrayIndexOutOfBoundsException
("Coordinate out of bounds!");
}
int bitnum = dataBitOffset + x * pixelBitStride;
int index = y * scanlineStride + (bitnum / dataElementSize);
int shift = dataElementSize - (bitnum & (dataElementSize-1))
- pixelBitStride;
int element = data.getElem(index);
element &= ~(bitMask << shift);
element |= (iArray[0] & bitMask) << shift;
data.setElem(index,element);
}
public boolean equals(Object o) {
if ((o == null) || !(o instanceof MultiPixelPackedSampleModel)) {
return false;
}
MultiPixelPackedSampleModel that = (MultiPixelPackedSampleModel)o;
return this.width == that.width &&
this.height == that.height &&
this.numBands == that.numBands &&
this.dataType == that.dataType &&
this.pixelBitStride == that.pixelBitStride &&
this.bitMask == that.bitMask &&
this.pixelsPerDataElement == that.pixelsPerDataElement &&
this.dataElementSize == that.dataElementSize &&
this.dataBitOffset == that.dataBitOffset &&
this.scanlineStride == that.scanlineStride;
}
// If we implement equals() we must also implement hashCode
public int hashCode() {
int hash = 0;
hash = width;
hash <<= 8;
hash ^= height;
hash <<= 8;
hash ^= numBands;
hash <<= 8;
hash ^= dataType;
hash <<= 8;
hash ^= pixelBitStride;
hash <<= 8;
hash ^= bitMask;
hash <<= 8;
hash ^= pixelsPerDataElement;
hash <<= 8;
hash ^= dataElementSize;
hash <<= 8;
hash ^= dataBitOffset;
hash <<= 8;
hash ^= scanlineStride;
return hash;
}
}