package org.libtiff.jai.codec;
import java.io.IOException;
import java.awt.image.RenderedImage;
import java.awt.Rectangle;
import java.awt.Point;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferShort;
import java.awt.image.DataBufferUShort;
import java.awt.image.SampleModel;
import java.awt.image.renderable.ParameterBlock;
import javax.media.jai.JAI;
import javax.media.jai.RasterFactory;
import org.libtiff.jai.util.JaiI18N;
/**
* Provides a base class for writing TIFF tile codecs, to
* be registered with the XTIFFDirectory. This codec allows
* for both decoding and (optionally) encoding of tiles, and
* also handles the colorspace conversion in decoding.
* <p>
* At the minimum you will need to implement the two methods
* decodeTilePixels() for byte and short data, as well as
* the methods register() and create(). If your decoder
* requires additional parameters from the tags, set them up in
* initializeDecoding(), and initializeEncoding() for encoding.
* <p>
* To implement encoding, you must override the canEncode() method
* to return true, and implement encodeTilePixels().
*
* @author Niles Ritter
* @see XTIFFTileCodec
*/
public abstract class XTIFFTileCodecImpl implements XTIFFTileCodec {
//////////////////////////////////////////////////////
//// Implementation Section
//// Override or implement methods here
//////////////////////////////////////////////////////
/**
* Registration method. Must be implemented by the
* extended class to register itself with the
* XTIFFDirectory for all compression codes it
* supports (e.g Fax codec supports 3 codes).
*
* @see XTIFFDirectory
*/
public abstract void register();
/**
* Implement this to return the corresponding empty
* codec object.
*/
public abstract XTIFFTileCodec create();
/**
* Indicate whether this codec can encode data.
* Override to return true only if your codec implments encoding.
*/
public boolean canEncode() { return false; }
/**
* The initialization method particular to decoding.
* Extend for whatever compression-specific information
* or parameters is needed. The decoding parameter has
* already been assigned at this point, as well as the
* XTIFFDirectory parsed from the input stream, and so
* all XTIFFFields are available.
*/
public void initializeDecoding() {
}
/**
* The initialization method particular to encoding.
* Extend for whatever compression-specific information
* or parameters is needed. The decoding parameter has
* already been assigned at this point, as well as the
* XTIFFDirectory parsed from the input stream, and so
* all XTIFFFields are available.
*/
public void initializeEncoding() {
}
/**
* decode bpixel byte array of data into pixels, packed
* for 1,2,4 8 bit pixels. Must implment this.
* @param bpixels the byte array of compressed input data
* @param rect the rectangular shape of the target pixels
* @param pixels the target decompressed pixels.
*/
public abstract void decodeTilePixels( byte[] bpixels, Rectangle rect,
byte[] pixels);
/**
* decode bpixel byte array of data into pixels, packed
* for 16 bit pixels. Must implment this.
* @param bpixels the byte array of compressed input data
* @param rect the rectangular shape of the target pixels
* @param pixels the target decompressed pixels.
*/
public abstract void decodeTilePixels( byte[] bpixels, Rectangle rect,
short[] pixels);
/**
* encode the tile in pixels into bpixels and return the byte
* size of the compressed data.
* Override this method if canEncode() = true;
* @param pixels input pixels
* @param rect the array dimensions of samples
* @param bpixels the target array of compressed byte data
*/
public int encodeTilePixels(int[] pixels,Rectangle rect, byte[] bpixels) {
return 0;
}
//////////////////////////////////////////////////////
//// Common Section
//////////////////////////////////////////////////////
protected XTIFFDirectory directory=null;
protected RenderedImage image=null;
protected int minY;
protected int minX;
protected int width;
protected int length;
protected int numBands;
protected int tileLength;
protected int tileWidth;
protected int compression;
protected SampleModel sampleModel;
protected int[] sampleSize;
protected char[] bitsPerSample;
protected char[] colormap=null;
/**
* The empty constructor.
*/
public XTIFFTileCodecImpl() {
}
/**
* The method for initializing information common
* to both encoder and decoder.
*/
public void initialize() {
width = (int)getLongField(XTIFF.TIFFTAG_IMAGE_WIDTH);
length = (int)getLongField(XTIFF.TIFFTAG_IMAGE_LENGTH);
isTiled = directory.isTiled();
if (isTiled) {
tileWidth = (int)getLongField(XTIFF.TIFFTAG_TILE_WIDTH);
tileLength = (int)getLongField(XTIFF.TIFFTAG_TILE_LENGTH);
} else {
tileWidth = width;
tileLength = (int)getLongField(XTIFF.TIFFTAG_ROWS_PER_STRIP);
}
// Figure out what compression if any, is being used.
XTIFFField compField = directory.getField(
XTIFF.TIFFTAG_COMPRESSION);
if (compField != null) {
compression = compField.getAsInt(0);
} else {
compression = XTIFF.COMPRESSION_NONE;
}
XTIFFField cfield = directory.getField(XTIFF.TIFFTAG_COLORMAP);
if (cfield !=null) colormap = cfield.getAsChars();
// Read the TIFFTAG_BITS_PER_SAMPLE field
XTIFFField bitsField =
directory.getField(XTIFF.TIFFTAG_BITS_PER_SAMPLE);
if (bitsField == null) {
// Default
bitsPerSample = new char[1];
bitsPerSample[0] = 1;
} else {
bitsPerSample = bitsField.getAsChars();
}
image_type=directory.getImageType();
}
/**
* A common utility method for accessing the XTIFFFields
* in the current image directory.
*/
protected long getLongField(int fld) {
XTIFFField field= directory.getField(fld);
if (field==null) return 0;
else return field.getAsLong(0);
}
/**
* This method may be used by the implementations register()
* method to register itself with the XTIFFDirectory.
* @see XTIFFDirectory
*/
public void register(int comp) {
XTIFFDirectory.registerTileCodec(comp,this);
}
/**
* One-time common image parameter setup
* @param img the source image that will be
* encoded into a TIFF formatted stream, or
* the TIFF image from which Raster tiles
* will be decoded.
*/
protected void setupSourceImage( RenderedImage img) {
image = img;
// Get raster parameters
minY=image.getMinY();
minX=image.getMinX();
sampleModel = image.getSampleModel();
numBands = sampleModel.getNumBands();
sampleSize = sampleModel.getSampleSize();
}
/**
* Returns the TIFF compression type
*/
public int getCompression() { return compression; }
//////////////////////////////////////////////////////
//// Encoding Section
//////////////////////////////////////////////////////
protected XTIFFEncodeParam encodeParam=null;
private int _pixels[];
protected boolean isTiled;
/**
* The method for creating an encoder from
* the XTIFFEncodeParam information.
*/
public XTIFFTileCodec create(XTIFFEncodeParam param)
throws IOException
{
XTIFFTileCodecImpl codec = (XTIFFTileCodecImpl)create();
codec.initialize(param);
return codec;
}
protected void initialize(XTIFFEncodeParam param)
throws IOException
{
if (!canEncode())
throw new IOException("encoding not supported");
encodeParam=param;
directory = param.getDirectory();
initialize();
initializeEncoding();
}
/**
* Encode the data into buffer and return byte count
* Normally you will not need to override this method,
* but instead implement the <code>encodeTilePixels()</code> method.
*/
public int encode( RenderedImage img,Rectangle rect, byte[] bpixels) {
if (image==null) {
setupSourceImage(img);
setupBufferForEncoding();
}
// Fill tile buffer, padding right with zeroes.
getTilePixels(rect);
// encode and return number of bytes compressed
return encodeTilePixels(_pixels,rect,bpixels);
}
/**
* One-time setup for encoding
*/
protected void setupBufferForEncoding() {
// Set up input tile/strip buffer
_pixels = new int[tileWidth * tileLength * numBands];
// if padding necessary do it now.
int padRight = (tileWidth - (width%tileWidth)) % tileWidth;
int padBottom = (tileLength - (length%tileLength)) % tileLength;
if (!isTiled) padBottom = 0;
if (padRight>0 || padBottom>0) {
ParameterBlock pb = new ParameterBlock();
pb.addSource(image);
pb.add(null).add(padRight).add(null)
.add(padBottom).add(null).add(null);
image = JAI.create("border",pb);
}
}
/**
* Get the portion of tile fitting into buffer.
* You probably won't need to override this.
* @param rect the region to extract from image.
*/
protected void getTilePixels( Rectangle rect ) {
// Grab the pixels
Raster src = image.getData(rect);
int col=(int)rect.getX();
int row=(int)rect.getY();
int rows=(int)rect.getHeight();
int cols=(int)rect.getWidth();
src.getPixels(col, row, cols, rows, _pixels);
}
/**
* If derived classes can make a better estimate
* for the maximum size of a compressed tile,
* they should override this, which assumes
* conservatively that it won't be worse than
* twice the original size.
* @param im the rendered image containing the image data
*/
public int getCompressedTileSize(RenderedImage im) {
sampleModel = im.getSampleModel();
numBands = sampleModel.getNumBands();
sampleSize = sampleModel.getSampleSize();
return (int)Math.ceil(2*tileWidth * tileLength
* numBands *(sampleSize[0]/8.0));
}
//////////////////////////////////////////////////////
//// Decoding Section
//////////////////////////////////////////////////////
protected XTIFFDecodeParam decodeParam=null;
protected boolean decodePaletteAsShorts=false;
protected int unitsInThisTile;
protected byte _bdata[] = null;
protected short _sdata[] = null;
protected byte[] bpixvals=null;
protected short[] spixvals=null;
protected DataBuffer buffer = null;
protected int dataType;
protected int image_type;
/**
* The standard decoder creation method
*/
public XTIFFTileCodec create(XTIFFDecodeParam param)
throws IOException
{
XTIFFTileCodecImpl codec = (XTIFFTileCodecImpl)create();
codec.initialize(param);
return codec;
}
protected void initialize(XTIFFDecodeParam param)
throws IOException
{
decodeParam=param;
decodePaletteAsShorts=param.getDecodePaletteAsShorts();
directory = param.getDirectory();
initialize();
initializeDecoding();
}
/**
* One-time setup for encoding. Some configurations
* require a temp array for unpacking 16-bit palette data.
*/
protected void setupBufferForDecoding() {
int length;
buffer = sampleModel.createDataBuffer();
dataType = sampleModel.getDataType();
if (dataType == DataBuffer.TYPE_BYTE) {
_bdata = ((DataBufferByte)buffer).getData();
bpixvals=_bdata;
} else if (dataType == DataBuffer.TYPE_USHORT) {
_sdata = ((DataBufferUShort)buffer).getData();
if (!decodePaletteAsShorts) spixvals=_sdata;
} else if (dataType == DataBuffer.TYPE_SHORT) {
_sdata = ((DataBufferShort)buffer).getData();
if (!decodePaletteAsShorts) spixvals=_sdata;
}
if (decodePaletteAsShorts) {
int len=_sdata.length;
if (bitsPerSample[0] == 16)
spixvals=new short[len];
else
bpixvals=new byte[len];
}
}
/**
* Decode a rectangle of data stored in bpixels
* into a raster tile. Usually you will not need
* to override this, but instead implement the
* decodeTilePixels methods.
*/
public WritableRaster decode(RenderedImage img,
Rectangle newRect, byte[] bpixels)
{
if (image==null) {
setupSourceImage(img);
}
setupBufferForDecoding(); //set up every time
unitsInThisTile = newRect.width * newRect.height * numBands;
// uncompress data
decodeTilePixels(bpixels,newRect);
// post-processing of color data
decodeColor(newRect);
// put buffer into a tile
return setTilePixels(newRect);
}
/**
* Postprocess the uncompressed color data into
* the appropriate display color model. This implementation
* Does a number of things:
* <ul>
* <li> For RGB color, reverse to BGR which apparently
* is faster for Java 2D display
* <li> For one-bit WHITE_IS_ZERO data, flip the values
* so that they will look correct
* <li> If the decodePaletteAsShorts flag is true then
* unpack the bits and apply the lookup table, as
* 16-bit lookup is not supported in JAI.
* </ul>
* Override this if you have other color types.
* @see XTIFFDecodeParam
*/
protected void decodeColor(Rectangle newRect) {
switch (dataType) {
case DataBuffer.TYPE_BYTE:
decodeColor( bpixvals, _bdata, newRect);
break;
case DataBuffer.TYPE_SHORT:
case DataBuffer.TYPE_USHORT:
if (bpixvals!=null)
decodeColor( bpixvals, _sdata, newRect);
else
decodeColor( spixvals, _sdata, newRect);
}
}
/**
* Decode a tile of data into either byte or short pixel buffers.
* Override this if you have other buffer types (e.g. int)
*/
protected void decodeTilePixels(byte[] bpixels,Rectangle newRect) {
// decodeTilePixels into the appropriate buffer
if (bpixvals!=null)
decodeTilePixels(bpixels, newRect, bpixvals);
else
decodeTilePixels(bpixels, newRect, spixvals);
}
/**
* Take the values from the buffer and store them in
* a WritableRaster object.
*/
protected WritableRaster setTilePixels(Rectangle rect) {
return
(WritableRaster)RasterFactory.createWritableRaster(sampleModel,
buffer,
new Point((int)rect.getX(),
(int)rect.getY()));
}
/**
* A useful Method to interpret a byte array as shorts. Method depends on
* whether the bytes are stored in a big endian or little endian format.
*/
protected void unpackShorts(byte byteArray[],
short output[],
int shortCount) {
int j;
int firstByte, secondByte;
if (directory.isBigEndian()) {
for (int i=0; i<shortCount; i++) {
j = 2*i;
firstByte = byteArray[j] & 0xff;
secondByte = byteArray[j+1] & 0xff;
output[i] = (short)((firstByte << 8) + secondByte);
}
} else {
for (int i=0; i<shortCount; i++) {
j = 2*i;
firstByte = byteArray[j] & 0xff;
secondByte = byteArray[j+1] & 0xff;
output[i] = (short)((secondByte << 8) + firstByte);
}
}
}
//////////////////////////////////////////////////////////////////////////
///// Color decoding section
//////////////////////////////////////////////////////////////////////////
/**
* Decode short pixel data, or interpret palette data
* as short from byte.
*/
protected void decodeColor(byte[] bpix,short[] sdata, Rectangle newRect) {
short sswap;
switch(image_type) {
case XTIFF.TYPE_PALETTE:
if (bitsPerSample[0] == 8) {
// At this point the data is 1 banded and will
// become 3 banded only after we've done the palette
// lookup, since unitsInThisTile was calculated with
// 3 bands, we need to divide this by 3.
int unitsBeforeLookup = unitsInThisTile / 3;
// Expand the palette image into an rgb image with ushort
// data type.
int cmapValue;
int count = 0, lookup, len = colormap.length/3;
int len2 = len * 2;
for (int i=0; i<unitsBeforeLookup; i++) {
// Get the index into the colormap
lookup = bpix[i] & 0xff;
// Get the blue value
cmapValue = colormap[lookup+len2];
sdata[count++] = (short)(cmapValue & 0xffff);
// Get the green value
cmapValue = colormap[lookup+len];
sdata[count++] = (short)(cmapValue & 0xffff);
// Get the red value
cmapValue = colormap[lookup];
sdata[count++] = (short)(cmapValue & 0xffff);
}
} else if (bitsPerSample[0] == 4) {
int padding=newRect.width % 2;
int bytesPostDecoding = ((newRect.width+1)/2) *
newRect.height;
int bytes = unitsInThisTile / 3;
// Unpack the 2 pixels packed into each byte.
byte[] data = new byte[bytes];
int srcCount = 0, dstCount = 0;
for (int j=0; j<newRect.height; j++) {
for (int i=0; i<newRect.width/2; i++) {
data[dstCount++] =
(byte)((bpix[srcCount] & 0xf0) >> 4);
data[dstCount++] =
(byte)(bpix[srcCount++] & 0x0f);
}
if (padding == 1) {
data[dstCount++] =
(byte)((bpix[srcCount++] & 0xf0) >> 4);
}
}
int len = colormap.length/3;
int len2 = len*2;
int cmapValue, lookup;
int count = 0;
for (int i=0; i<bytes; i++) {
lookup = data[i] & 0xff;
cmapValue = colormap[lookup+len2];
sdata[count++] = (short)(cmapValue & 0xffff);
cmapValue = colormap[lookup+len];
sdata[count++] = (short)(cmapValue & 0xffff);
cmapValue = colormap[lookup];
sdata[count++] = (short)(cmapValue & 0xffff);
}
} else {
throw new
RuntimeException(JaiI18N.getString("XTIFFImageDecoder7"));
}
break;
}
}
/**
* Decode short color data, or interpret palette data
* as short.
*/
protected void decodeColor(short[] spix,short[] sdata, Rectangle newRect) {
short sswap;
switch(image_type) {
case XTIFF.TYPE_GREYSCALE_WHITE_IS_ZERO:
case XTIFF.TYPE_GREYSCALE_BLACK_IS_ZERO:
// Since we are using a ComponentColorModel with this image,
// we need to change the WhiteIsZero data to BlackIsZero data
// so it will display properly.
if (image_type == XTIFF.TYPE_GREYSCALE_WHITE_IS_ZERO) {
if (dataType == DataBuffer.TYPE_USHORT) {
for (int l = 0; l < sdata.length; l++) {
sdata[l] = (short)(65535 - spix[l]);
}
} else if (dataType == DataBuffer.TYPE_SHORT) {
for (int l = 0; l < sdata.length; l++) {
sdata[l] = (short)(~spix[l]);
}
}
}
break;
case XTIFF.TYPE_RGB:
// Change to BGR order, as Java2D displays that faster
for (int i=0; i<unitsInThisTile; i+=3) {
sswap = spix[i];
sdata[i] = spix[i+2];
sdata[i+2] = sswap;
}
break;
case XTIFF.TYPE_ORGB:
case XTIFF.TYPE_ARGB_PRE:
case XTIFF.TYPE_ARGB:
// Change from RGBA to ABGR for Java2D's faster special cases
for (int i=0; i<unitsInThisTile; i+=4) {
// Swap R and A
sswap = spix[i];
sdata[i] = spix[i+3];
sdata[i+3] = sswap;
// Swap G and B
sswap = spix[i+1];
sdata[i+1] = spix[i+2];
sdata[i+2] = sswap;
}
break;
case XTIFF.TYPE_RGB_EXTRA:
break;
case XTIFF.TYPE_PALETTE:
if (decodePaletteAsShorts) {
// At this point the data is 1 banded and will
// become 3 banded only after we've done the palette
// lookup, since unitsInThisTile was calculated with
// 3 bands, we need to divide this by 3.
int unitsBeforeLookup = unitsInThisTile / 3;
// Since unitsBeforeLookup is the number of shorts,
// but we do our decompression in terms of bytes, we
// need to multiply it by 2 in order to figure out
// how many bytes we'll get after decompression.
int entries = unitsBeforeLookup * 2;
if (dataType == DataBuffer.TYPE_USHORT) {
// Expand the palette image into an rgb image with ushort
// data type.
int cmapValue;
int count = 0, lookup, len = colormap.length/3;
int len2 = len * 2;
for (int i=0; i<unitsBeforeLookup; i++) {
// Get the index into the colormap
lookup = spix[i] & 0xffff;
// Get the blue value
cmapValue = colormap[lookup+len2];
sdata[count++] = (short)(cmapValue & 0xffff);
// Get the green value
cmapValue = colormap[lookup+len];
sdata[count++] = (short)(cmapValue & 0xffff);
// Get the red value
cmapValue = colormap[lookup];
sdata[count++] = (short)(cmapValue & 0xffff);
}
} else if (dataType == DataBuffer.TYPE_SHORT) {
// Expand the palette image into an rgb image with
// short data type.
int cmapValue;
int count = 0, lookup, len = colormap.length/3;
int len2 = len * 2;
for (int i=0; i<unitsBeforeLookup; i++) {
// Get the index into the colormap
lookup = spix[i] & 0xffff;
// Get the blue value
cmapValue = colormap[lookup+len2];
sdata[count++] = (short)cmapValue;
// Get the green value
cmapValue = colormap[lookup+len];
sdata[count++] = (short)cmapValue;
// Get the red value
cmapValue = colormap[lookup];
sdata[count++] = (short)cmapValue;
}
}//dataType
}//decodePaletteAsShorts
break;
case XTIFF.TYPE_TRANS:
break;
}
}
/**
* Decode byte color data
*/
protected void decodeColor(byte[] bpix,byte[] bdata, Rectangle newRect) {
byte bswap;
switch(image_type) {
case XTIFF.TYPE_BILEVEL_WHITE_IS_ZERO:
case XTIFF.TYPE_BILEVEL_BLACK_IS_ZERO:
case XTIFF.TYPE_GREYSCALE_WHITE_IS_ZERO:
case XTIFF.TYPE_GREYSCALE_BLACK_IS_ZERO:
case XTIFF.TYPE_RGB_EXTRA:
case XTIFF.TYPE_TRANS:
//nothing
break;
case XTIFF.TYPE_RGB:
if (bitsPerSample[0] == 8) {
// Change to BGR order, as Java2D displays that faster
for (int i=0; i<unitsInThisTile; i+=3) {
bswap = bpix[i];
bdata[i] = bpix[i+2];
bdata[i+2] = bswap;
}
}
break;
case XTIFF.TYPE_ORGB:
case XTIFF.TYPE_ARGB_PRE:
case XTIFF.TYPE_ARGB:
if (bitsPerSample[0] == 8) {
// Convert from RGBA to ABGR for Java2D
for (int i=0; i<unitsInThisTile; i+=4) {
// Swap R and A
bswap = bpix[i];
bdata[i] = bpix[i+3];
bdata[i+3] = bswap;
// Swap G and B
bswap = bpix[i+1];
bdata[i+1] = bpix[i+2];
bdata[i+2] = bswap;
}
}
break;
case XTIFF.TYPE_PALETTE:
//
break;
}//switch
}//decodeColor
}