package org.libtiff.jai.codecimpl; /* * XTIFF: eXtensible TIFF libraries for JAI. * * The contents of this file are subject to the JAVA ADVANCED IMAGING * SAMPLE INPUT-OUTPUT CODECS AND WIDGET HANDLING SOURCE CODE License * Version 1.0 (the "License"); You may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.sun.com/software/imaging/JAI/index.html * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * The Original Code is JAVA ADVANCED IMAGING SAMPLE INPUT-OUTPUT CODECS * AND WIDGET HANDLING SOURCE CODE. * The Initial Developer of the Original Code is: Sun Microsystems, Inc.. * Portions created by: Niles Ritter * are Copyright (C): Niles Ritter, GeoTIFF.org, 1999,2000. * All Rights Reserved. * Contributor(s): Niles Ritter */ import java.awt.Rectangle; import java.awt.Transparency; import java.awt.color.ColorSpace; import java.awt.image.ComponentColorModel; import java.awt.image.DataBuffer; import java.awt.image.IndexColorModel; import java.awt.image.MultiPixelPackedSampleModel; import java.awt.image.PixelInterleavedSampleModel; import java.awt.image.Raster; import java.awt.image.WritableRaster; import java.io.IOException; import javax.media.jai.RasterFactory; import org.libtiff.jai.codec.XTIFF; import org.libtiff.jai.codec.XTIFFDecodeParam; import org.libtiff.jai.codec.XTIFFDirectory; import org.libtiff.jai.codec.XTIFFField; import org.libtiff.jai.codec.XTIFFTileCodec; import org.libtiff.jai.util.JaiI18N; import com.sun.media.jai.codec.ImageCodec; import com.sun.media.jai.codec.SeekableStream; import com.sun.media.jai.codec.TIFFDecodeParam; import com.sun.media.jai.codecimpl.SimpleRenderedImage; public class XTIFFImage extends SimpleRenderedImage { XTIFFTileCodec codec; XTIFFDirectory dir; TIFFDecodeParam param; int photometric_interp; SeekableStream stream; int tileSize; int tilesX, tilesY; long[] tileOffsets; long tileByteCounts[]; char colormap[]; char bitsPerSample[]; int samplesPerPixel; int extraSamples; byte palette[]; int bands; char sampleFormat[]; boolean decodePaletteAsShorts; boolean isBigEndian; // Image types int image_type; int dataType; /** * Constructs a XTIFFImage that acquires its data from a given * SeekableStream and reads from a particular IFD of the stream. The index * of the first IFD is 0. * * @param stream the SeekableStream to read from. * @param param an instance of TIFFDecodeParam, or null. * @param directory the index of the IFD to read from. */ public XTIFFImage(SeekableStream stream, TIFFDecodeParam param, int directory) throws IOException { this.stream = stream; if (param == null || !(param instanceof XTIFFDecodeParam)) { param = new XTIFFDecodeParam(param); } this.param = param; decodePaletteAsShorts = param.getDecodePaletteAsShorts(); // Read the specified directory. dir = XTIFFDirectory.create(stream, directory); properties.put("tiff.directory", dir); ((XTIFFDecodeParam) param).setDirectory(dir); // Check whether big endian or little endian format is used. isBigEndian = dir.isBigEndian(); setupImageParameters(); setupSamplesAndColor(); dir.setImageType(image_type); // Calculate number of tiles and the tileSize in bytes tilesX = (width + tileWidth - 1) / tileWidth; tilesY = (height + tileHeight - 1) / tileHeight; tileSize = tileWidth * tileHeight * bands; try { codec = dir.createTileCodec((XTIFFDecodeParam) param); } catch (Exception e) { } } /** * This method gets the image parameters from fields */ protected void setupImageParameters() { // Set basic image layout minX = minY = 0; width = (int) dir.getFieldAsLong(XTIFF.TIFFTAG_IMAGE_WIDTH); height = (int) dir.getFieldAsLong(XTIFF.TIFFTAG_IMAGE_LENGTH); photometric_interp = (int) dir.getFieldAsLong(XTIFF.TIFFTAG_PHOTOMETRIC_INTERPRETATION); // Read the TIFFTAG_BITS_PER_SAMPLE field XTIFFField bitsField = dir.getField(XTIFF.TIFFTAG_BITS_PER_SAMPLE); if (bitsField == null) { // Default bitsPerSample = new char[1]; bitsPerSample[0] = 1; } else { bitsPerSample = bitsField.getAsChars(); } for (int i = 1; i < bitsPerSample.length; i++) { if (bitsPerSample[i] != bitsPerSample[1]) { throw new RuntimeException(JaiI18N.getString("XTIFFImageDecoder19")); } } // Get the number of samples per pixel XTIFFField sfield = dir.getField(XTIFF.TIFFTAG_SAMPLES_PER_PIXEL); if (sfield == null) { samplesPerPixel = 1; } else { samplesPerPixel = (int) sfield.getAsLong(0); } // Figure out if any extra samples are present. XTIFFField efield = dir.getField(XTIFF.TIFFTAG_EXTRA_SAMPLES); if (efield == null) { extraSamples = 0; } else { extraSamples = (int) efield.getAsLong(0); } // Read the TIFFTAG_SAMPLE_FORMAT tag to see whether the data might be // signed or floating point XTIFFField sampleFormatField = dir.getField(XTIFF.TIFFTAG_SAMPLE_FORMAT); if (sampleFormatField != null) { sampleFormat = sampleFormatField.getAsChars(); // Check that all the samples have the same format for (int l = 1; l < sampleFormat.length; l++) { if (sampleFormat[l] != sampleFormat[0]) { throw new RuntimeException(JaiI18N.getString("XTIFFImageDecoder20")); } } } else { sampleFormat = new char[] { 1 }; } if (sampleFormat[0] == 1 || sampleFormat[0] == 4) { // Unsigned or unknown if (bitsPerSample[0] == 8) { dataType = DataBuffer.TYPE_BYTE; } else if (bitsPerSample[0] == 16) { dataType = DataBuffer.TYPE_USHORT; } else if (bitsPerSample[0] == 32) { dataType = DataBuffer.TYPE_INT; } } else if (sampleFormat[0] == 2) { // Signed if (bitsPerSample[0] == 1 || bitsPerSample[0] == 4 || bitsPerSample[0] == 8) { throw new RuntimeException(JaiI18N.getString("XTIFFImageDecoder21")); } else if (bitsPerSample[0] == 16) { dataType = DataBuffer.TYPE_SHORT; } else if (bitsPerSample[0] == 32) { dataType = DataBuffer.TYPE_INT; } } else if (sampleFormat[0] == 3) { // Floating point // dataType = DataBuffer.TYPE_FLOAT; throw new RuntimeException(JaiI18N.getString("XTIFFImageDecoder22")); } if (dir.getField(XTIFF.TIFFTAG_TILE_WIDTH) != null) { // Image is in tiled format tileWidth = (int) dir.getFieldAsLong(XTIFF.TIFFTAG_TILE_WIDTH); tileHeight = (int) dir.getFieldAsLong(XTIFF.TIFFTAG_TILE_LENGTH); tileOffsets = (dir.getField(XTIFF.TIFFTAG_TILE_OFFSETS)).getAsLongs(); tileByteCounts = dir.getField(XTIFF.TIFFTAG_TILE_BYTE_COUNTS) .getAsLongs(); } else { // Image is in stripped format, looks like tiles to us tileWidth = width; XTIFFField field = dir.getField(XTIFF.TIFFTAG_ROWS_PER_STRIP); if (field == null) { // Default is infinity (2^32 -1), basically the entire image // TODO: Can do a better job of tiling here tileHeight = height; } else { long l = field.getAsLong(0); long infinity = 1; infinity = (infinity << 32) - 1; if (l == infinity) { // 2^32 - 1 (effectively infinity, entire image is 1 strip) tileHeight = height; } else { tileHeight = (int) l; } } XTIFFField tileOffsetsField = dir.getField(XTIFF.TIFFTAG_STRIP_OFFSETS); if (tileOffsetsField == null) { throw new RuntimeException(JaiI18N.getString("XTIFFImageDecoder11")); } else { tileOffsets = tileOffsetsField.getAsLongs(); } XTIFFField tileByteCountsField = dir.getField(XTIFF.TIFFTAG_STRIP_BYTE_COUNTS); if (tileByteCountsField == null) { throw new RuntimeException(JaiI18N.getString("XTIFFImageDecoder12")); } else { tileByteCounts = tileByteCountsField.getAsLongs(); } } } /** * This method constructs the sampleModel, colorModel, determines the * image_type and the bands parameter. */ protected void setupSamplesAndColor() { // Figure out which kind of image we are dealing with. switch (photometric_interp) { case XTIFF.PHOTOMETRIC_WHITE_IS_ZERO: bands = 1; // Bilevel or Grayscale - WhiteIsZero if (bitsPerSample[0] == 1) { image_type = XTIFF.TYPE_BILEVEL_WHITE_IS_ZERO; // Keep pixels packed, use IndexColorModel sampleModel = new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE, tileWidth, tileHeight, 1); // Set up the palette byte r[] = new byte[] { (byte) 255, (byte) 0 }; byte g[] = new byte[] { (byte) 255, (byte) 0 }; byte b[] = new byte[] { (byte) 255, (byte) 0 }; colorModel = new IndexColorModel(1, 2, r, g, b); } else { image_type = XTIFF.TYPE_GREYSCALE_WHITE_IS_ZERO; if (bitsPerSample[0] == 4) { sampleModel = new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE, tileWidth, tileHeight, 4); colorModel = ImageCodec.createGrayIndexColorModel(sampleModel, false); } else if (bitsPerSample[0] == 8) { sampleModel = RasterFactory.createPixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, tileWidth, tileHeight, bands); colorModel = ImageCodec.createGrayIndexColorModel(sampleModel, false); } else if (bitsPerSample[0] == 16) { sampleModel = RasterFactory.createPixelInterleavedSampleModel(dataType, tileWidth, tileHeight, bands); colorModel = ImageCodec.createComponentColorModel(sampleModel); } else { throw new IllegalArgumentException(JaiI18N.getString("XTIFFImageDecoder14")); } } break; case XTIFF.PHOTOMETRIC_BLACK_IS_ZERO: bands = 1; // Bilevel or Grayscale - BlackIsZero if (bitsPerSample[0] == 1) { image_type = XTIFF.TYPE_BILEVEL_BLACK_IS_ZERO; // Keep pixels packed, use IndexColorModel sampleModel = new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE, tileWidth, tileHeight, 1); // Set up the palette byte r[] = new byte[] { (byte) 0, (byte) 255 }; byte g[] = new byte[] { (byte) 0, (byte) 255 }; byte b[] = new byte[] { (byte) 0, (byte) 255 }; // 1 Bit pixels packed into a byte, use IndexColorModel colorModel = new IndexColorModel(1, 2, r, g, b); } else { image_type = XTIFF.TYPE_GREYSCALE_BLACK_IS_ZERO; if (bitsPerSample[0] == 4) { sampleModel = new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE, tileWidth, tileHeight, 4); colorModel = ImageCodec.createGrayIndexColorModel(sampleModel, true); } else if (bitsPerSample[0] == 8) { sampleModel = RasterFactory.createPixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, tileWidth, tileHeight, bands); colorModel = ImageCodec.createComponentColorModel(sampleModel); } else if (bitsPerSample[0] == 16) { sampleModel = RasterFactory.createPixelInterleavedSampleModel(dataType, tileWidth, tileHeight, bands); colorModel = ImageCodec.createComponentColorModel(sampleModel); } else { throw new IllegalArgumentException(JaiI18N.getString("XTIFFImageDecoder14")); } } break; case XTIFF.PHOTOMETRIC_RGB: bands = samplesPerPixel; // RGB full color image if (bitsPerSample[0] == 8) { sampleModel = RasterFactory.createPixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, tileWidth, tileHeight, bands); } else if (bitsPerSample[0] == 16) { sampleModel = RasterFactory.createPixelInterleavedSampleModel(dataType, tileWidth, tileHeight, bands); } else { throw new RuntimeException(JaiI18N.getString("XTIFFImageDecoder15")); } if (samplesPerPixel < 3) { throw new RuntimeException(JaiI18N.getString("XTIFFImageDecoder1")); } else if (samplesPerPixel == 3) { image_type = XTIFF.TYPE_RGB; // No alpha colorModel = ImageCodec.createComponentColorModel(sampleModel); } else if (samplesPerPixel == 4) { if (extraSamples == 0) { image_type = XTIFF.TYPE_ORGB; // Transparency.OPAQUE signifies image data that is // completely opaque, meaning that all pixels have an alpha // value of 1.0. So the extra band gets ignored, which is // what we want. colorModel = createAlphaComponentColorModel(dataType, true, false, Transparency.OPAQUE); } else if (extraSamples == 1) { image_type = XTIFF.TYPE_ARGB_PRE; // Pre multiplied alpha. colorModel = createAlphaComponentColorModel(dataType, true, true, Transparency.TRANSLUCENT); } else if (extraSamples == 2) { image_type = XTIFF.TYPE_ARGB; // The extra sample here is unassociated alpha, usually a // transparency mask, also called soft matte. colorModel = createAlphaComponentColorModel(dataType, true, false, Transparency.BITMASK); } } else { image_type = XTIFF.TYPE_RGB_EXTRA; // For this case we can't display the image, so there is no // point in trying to reformat the data to be BGR followed by // the ExtraSamples, the way Java2D would like it, because // Java2D can't display it anyway. Therefore create a sample // model with increasing bandOffsets, and keep the colorModel // as null, as there is no appropriate ColorModel. int bandOffsets[] = new int[bands]; for (int i = 0; i < bands; i++) { bandOffsets[i] = i; } if (bitsPerSample[0] == 8) { sampleModel = new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, tileWidth, tileHeight, bands, bands * tileWidth, bandOffsets); colorModel = null; } else if (bitsPerSample[0] == 16) { sampleModel = new PixelInterleavedSampleModel(dataType, tileWidth, tileHeight, bands, bands * tileWidth, bandOffsets); colorModel = null; } } break; case XTIFF.PHOTOMETRIC_PALETTE: image_type = XTIFF.TYPE_PALETTE; // Get the colormap XTIFFField cfield = dir.getField(XTIFF.TIFFTAG_COLORMAP); if (cfield == null) { throw new RuntimeException(JaiI18N.getString("XTIFFImageDecoder2")); } else { colormap = cfield.getAsChars(); } // Could be either 1 or 3 bands depending on whether we use // IndexColorModel or not. if (decodePaletteAsShorts) { bands = 3; if (bitsPerSample[0] != 4 && bitsPerSample[0] != 8 && bitsPerSample[0] != 16) { throw new RuntimeException(JaiI18N.getString("XTIFFImageDecoder13")); } // If no SampleFormat tag was specified and if the // bitsPerSample are less than or equal to 8, then the // dataType was initially set to byte, but now we want to // expand the palette as shorts, so the dataType should // be ushort. if (dataType == DataBuffer.TYPE_BYTE) { dataType = DataBuffer.TYPE_USHORT; } // Data will have to be unpacked into a 3 band short image // as we do not have a IndexColorModel that can deal with // a colormodel whose entries are of short data type. sampleModel = RasterFactory.createPixelInterleavedSampleModel(dataType, tileWidth, tileHeight, bands); colorModel = ImageCodec.createComponentColorModel(sampleModel); } else { bands = 1; if (bitsPerSample[0] == 4) { // Pixel data will not be unpacked, will use MPPSM to store // packed data and IndexColorModel to do the unpacking. sampleModel = new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE, tileWidth, tileHeight, bitsPerSample[0]); } else if (bitsPerSample[0] == 8) { sampleModel = RasterFactory.createPixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, tileWidth, tileHeight, bands); } else if (bitsPerSample[0] == 16) { // Here datatype has to be unsigned since we are storing // indices into the IndexColorModel palette. Ofcourse // the actual palette entries are allowed to be negative. sampleModel = RasterFactory.createPixelInterleavedSampleModel(DataBuffer.TYPE_USHORT, tileWidth, tileHeight, bands); } else { throw new RuntimeException(JaiI18N.getString("XTIFFImageDecoder13")); } int bandLength = colormap.length / 3; byte r[] = new byte[bandLength]; byte g[] = new byte[bandLength]; byte b[] = new byte[bandLength]; int gIndex = bandLength; int bIndex = bandLength * 2; if (dataType == DataBuffer.TYPE_SHORT) { for (int i = 0; i < bandLength; i++) { r[i] = param.decodeSigned16BitsTo8Bits((short) colormap[i]); g[i] = param.decodeSigned16BitsTo8Bits((short) colormap[gIndex + i]); b[i] = param.decodeSigned16BitsTo8Bits((short) colormap[bIndex + i]); } } else { for (int i = 0; i < bandLength; i++) { r[i] = param.decode16BitsTo8Bits(colormap[i] & 0xffff); g[i] = param.decode16BitsTo8Bits(colormap[gIndex + i] & 0xffff); b[i] = param.decode16BitsTo8Bits(colormap[bIndex + i] & 0xffff); } } colorModel = new IndexColorModel(bitsPerSample[0], bandLength, r, g, b); } break; case XTIFF.PHOTOMETRIC_TRANSPARENCY: image_type = XTIFF.TYPE_TRANS; // Transparency Mask throw new RuntimeException(JaiI18N.getString("XTIFFImageDecoder3")); // break; default: throw new RuntimeException(JaiI18N.getString("XTIFFImageDecoder4")); } } /** * Reads a private IFD from a given offset in the stream. This method may be * used to obtain IFDs that are referenced only by private tag values. */ public XTIFFDirectory getPrivateIFD(long offset) throws IOException { return XTIFFDirectory.create(stream, offset); } private WritableRaster tile00 = null; /** * Returns tile (tileX, tileY) as a Raster. */ public synchronized Raster getTile(int tileX, int tileY) { if (tileX == 0 && tileY == 0 && tile00 != null) { return tile00; } if ((tileX < 0) || (tileX >= tilesX) || (tileY < 0) || (tileY >= tilesY)) { throw new IllegalArgumentException(JaiI18N.getString("XTIFFImageDecoder5")); } // file setup1 // Save original file pointer position and seek to tile data location. long save_offset = 0; try { save_offset = stream.getFilePointer(); stream.seek(tileOffsets[tileY * tilesX + tileX]); } catch (IOException ioe) { throw new RuntimeException(JaiI18N.getString("XTIFFImageDecoder8")); } // Number of bytes in this tile (strip) after compression. int byteCount = (int) tileByteCounts[tileY * tilesX + tileX]; // Find out the number of bytes in the current tile Rectangle tileRect = new Rectangle(tileXToX(tileX), tileYToY(tileY), tileWidth, tileHeight); Rectangle newRect = tileRect.intersection(getBounds()); // file setup2 byte data[] = new byte[byteCount]; WritableRaster tile = null; try { stream.readFully(data, 0, byteCount); tile = codec.decode(this, newRect, data); stream.seek(save_offset); } catch (IOException e) { throw new RuntimeException("Failed to read raw tile data:" + e); } if (tileX == 0 && tileY == 0) { tile00 = tile; } return tile; } // Create ComponentColorModel for TYPE_RGB images private ComponentColorModel createAlphaComponentColorModel( int dataType, boolean hasAlpha, boolean isAlphaPremultiplied, int transparency) { ComponentColorModel ccm = null; int RGBBits[][] = new int[3][]; RGBBits[0] = new int[] { 8, 8, 8, 8 }; // Byte RGBBits[1] = new int[] { 16, 16, 16, 16 }; // Short RGBBits[2] = new int[] { 16, 16, 16, 16 }; // UShort RGBBits[2] = new int[] { 32, 32, 32, 32 }; // Int ccm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), RGBBits[dataType], hasAlpha, isAlphaPremultiplied, transparency, dataType); return ccm; } }