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 }