/*

 ============================================================================
                   The Apache Software License, Version 1.1
 ============================================================================

 Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.

 Redistribution and use in source and binary forms, with or without modifica-
 tion, are permitted provided that the following conditions are met:

 1. Redistributions of  source code must  retain the above copyright  notice,
    this list of conditions and the following disclaimer.

 2. Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution.

 3. The end-user documentation included with the redistribution, if any, must
    include  the following  acknowledgment:  "This product includes  software
    developed  by the  Apache Software Foundation  (http://www.apache.org/)."
    Alternately, this  acknowledgment may  appear in the software itself,  if
    and wherever such third-party acknowledgments normally appear.

 4. The names "Batik" and  "Apache Software Foundation" must  not  be
    used to  endorse or promote  products derived from  this software without
    prior written permission. For written permission, please contact
    apache@apache.org.

 5. Products  derived from this software may not  be called "Apache", nor may
    "Apache" appear  in their name,  without prior written permission  of the
    Apache Software Foundation.

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
 APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
 INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
 DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
 OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
 ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
 (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

 This software  consists of voluntary contributions made  by many individuals
 on  behalf of the Apache Software  Foundation. For more  information on the
 Apache Software Foundation, please see <http://www.apache.org/>.

*/

package org.apache.batik.ext.awt.image.codec.tiff;

import java.awt.Rectangle;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.awt.image.DataBufferShort;
import java.awt.image.DataBufferUShort;
import java.awt.image.IndexColorModel;
import java.awt.image.MultiPixelPackedSampleModel;
import java.awt.image.PixelInterleavedSampleModel;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;

import org.apache.batik.ext.awt.image.codec.SeekableStream;
import org.apache.batik.ext.awt.image.rendered.AbstractRed;
import org.apache.batik.ext.awt.image.rendered.CachableRed;

import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGDecodeParam;
import com.sun.image.codec.jpeg.JPEGImageDecoder;

public class TIFFImage extends AbstractRed {

    // Compression types
    public static final int COMP_NONE      = 1;
    public static final int COMP_FAX_G3_1D = 2;
    public static final int COMP_FAX_G3_2D = 3;
    public static final int COMP_FAX_G4_2D = 4;
    public static final int COMP_LZW       = 5;
    public static final int COMP_JPEG_OLD  = 6;
    public static final int COMP_JPEG_TTN2 = 7;
    public static final int COMP_PACKBITS  = 32773;
    public static final int COMP_DEFLATE   = 32946;

    // Image types
    private static final int TYPE_UNSUPPORTED = -1;
    private static final int TYPE_BILEVEL      = 0;
    private static final int TYPE_GRAY_4BIT    = 1;
    private static final int TYPE_GRAY         = 2;
    private static final int TYPE_GRAY_ALPHA   = 3;
    private static final int TYPE_PALETTE      = 4;
    private static final int TYPE_RGB          = 5;
    private static final int TYPE_RGB_ALPHA    = 6;
    private static final int TYPE_YCBCR_SUB    = 7;
    private static final int TYPE_GENERIC      = 8;

    // Incidental tags
    private static final int TIFF_JPEG_TABLES       = 347;
    private static final int TIFF_YCBCR_SUBSAMPLING = 530;

    SeekableStream stream;
    int tileSize;
    int tilesX, tilesY;
    long[] tileOffsets;
    long[] tileByteCounts;
    char[] colormap;
    int sampleSize;
    int compression;
    byte[] palette;
    int numBands;

    int chromaSubH;
    int chromaSubV;

    // Fax compression related variables
    long tiffT4Options;
    long tiffT6Options;
    int fillOrder;

    // LZW compression related variable
    int predictor;

    // TTN2 JPEG related variables
    JPEGDecodeParam decodeParam = null;
    boolean colorConvertJPEG = false;

    // DEFLATE variables
    Inflater inflater = null;

    // Endian-ness indicator
    boolean isBigEndian;
    
    int imageType;
    boolean isWhiteZero = false;
    int dataType;

    boolean decodePaletteAsShorts;
    boolean tiled;

    // Decoders
    private TIFFFaxDecoder decoder = null;
    private TIFFLZWDecoder lzwDecoder = null;

    /**
     * Decode a buffer of data into a Raster with the specified location.
     *
     * @param data buffer contain an interchange or abbreviated datastream.
     * @param decodeParam decoding parameters; may be null unless the
     *        data buffer contains an abbreviated datastream in which case
     *        it may not be null or an error will occur.
     * @param colorConvert whether to perform color conversion; in this
     *        case that would be limited to YCbCr-to-RGB.
     * @param minX the X position of the returned Raster.
     * @param minY the Y position of the returned Raster.
     */
    private static final Raster decodeJPEG(byte[] data,
                                           JPEGDecodeParam decodeParam,
                                           boolean colorConvert,
                                           int minX,
                                           int minY) {
        // Create an InputStream from the compressed data array.
        ByteArrayInputStream jpegStream = new ByteArrayInputStream(data);

        // Create a decoder.
        JPEGImageDecoder decoder = decodeParam == null ?
            JPEGCodec.createJPEGDecoder(jpegStream) :
            JPEGCodec.createJPEGDecoder(jpegStream,
                                        decodeParam);

        // Decode the compressed data into a Raster.
        Raster jpegRaster;
        try {
            jpegRaster = colorConvert ?
                decoder.decodeAsBufferedImage().getWritableTile(0, 0) :
                decoder.decodeAsRaster();
        } catch (IOException ioe) {
            throw new RuntimeException("TIFFImage13");
        }

        // Translate the decoded Raster to the specified location and return.
        return jpegRaster.createTranslatedChild(minX, minY);
    }

    /**
     * Inflates <code>deflated</code> into <code>inflated</code> using the
     * <code>Inflater</code> constructed during class instantiation.
     */
    private final void inflate(byte[] deflated, byte[] inflated) {
        inflater.setInput(deflated);
        try {
            inflater.inflate(inflated);
        } catch(DataFormatException dfe) {
            throw new RuntimeException("TIFFImage17"+": "+
                                       dfe.getMessage());
        }
        inflater.reset();
    }

    private static SampleModel createPixelInterleavedSampleModel
        (int dataType, int tileWidth, int tileHeight, int bands) {
        int [] bandOffsets = new int[bands];
        for (int i=0; i<bands; i++) 
            bandOffsets[i] = i;
        return new PixelInterleavedSampleModel
            (dataType, tileWidth, tileHeight, bands,
             tileWidth*bands, bandOffsets);
    }

    /**
     * Return as a long[] the value of a TIFF_LONG or TIFF_SHORT field.
     */
    private final long[] getFieldAsLongs(TIFFField field) {
        long[] value = null;

        if(field.getType() == TIFFField.TIFF_SHORT) {
            char[] charValue = field.getAsChars();
            value = new long[charValue.length];
            for(int i = 0; i < charValue.length; i++) {
                value[i] = charValue[i]  & 0xffff;
            }
        } else if(field.getType() == TIFFField.TIFF_LONG) {
            value = field.getAsLongs();
        } else {
            throw new RuntimeException();
        }

        return value;
    }

    /**
     * Constructs a TIFFImage 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 TIFFImage(SeekableStream stream,
                     TIFFDecodeParam param,
                     int directory)
        throws IOException {

        this.stream = stream;
        if (param == null) {
            param = new TIFFDecodeParam();
        } 

        decodePaletteAsShorts = param.getDecodePaletteAsShorts();

        // Read the specified directory.
        TIFFDirectory dir = param.getIFDOffset() == null ?
            new TIFFDirectory(stream, directory) :
            new TIFFDirectory(stream, param.getIFDOffset().longValue(),
                              directory);

        // Get the number of samples per pixel
        TIFFField sfield = dir.getField(TIFFImageDecoder.TIFF_SAMPLES_PER_PIXEL);
        int samplesPerPixel = sfield == null ? 1 : (int)sfield.getAsLong(0);

        // Read the TIFF_PLANAR_CONFIGURATION field
        TIFFField planarConfigurationField = 
            dir.getField(TIFFImageDecoder.TIFF_PLANAR_CONFIGURATION);
        char[] planarConfiguration = planarConfigurationField == null ?
            new char[] {1} :
            planarConfigurationField.getAsChars();

            // Support planar format (band sequential) only for 1 sample/pixel.
            if (planarConfiguration[0] != 1 && samplesPerPixel != 1) {
                throw new RuntimeException("TIFFImage0");
            }

            // Read the TIFF_BITS_PER_SAMPLE field
            TIFFField bitsField = 
                dir.getField(TIFFImageDecoder.TIFF_BITS_PER_SAMPLE);
            char[] bitsPerSample = null;
            if(bitsField != null) {
                bitsPerSample = bitsField.getAsChars();
            } else {
                bitsPerSample = new char[] {1};

                // Ensure that all samples have the same bit depth.
                for (int i = 1; i < bitsPerSample.length; i++) {
                    if (bitsPerSample[i] != bitsPerSample[0]) {
                        throw new RuntimeException("TIFFImage1");
                    }
                }
            }
            sampleSize = bitsPerSample[0];

            // Read the TIFF_SAMPLE_FORMAT tag to see whether the data might be
            // signed or floating point
            TIFFField sampleFormatField = 
                dir.getField(TIFFImageDecoder.TIFF_SAMPLE_FORMAT);

            char[] sampleFormat = null;
            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("TIFFImage2");
                    }
                }

            } else {
                sampleFormat = new char[] {1};
            }

            // Set the data type based on the sample size and format.
            boolean isValidDataFormat = false;
            switch(sampleSize) {
            case 1:
            case 4:
            case 8:
                if(sampleFormat[0] != 3) {
                    // Ignore whether signed or unsigned: treat all as unsigned.
                    dataType = DataBuffer.TYPE_BYTE;
                    isValidDataFormat = true;
                }
                break;
            case 16:
                if(sampleFormat[0] != 3) {
                    dataType = sampleFormat[0] == 2 ?
                        DataBuffer.TYPE_SHORT : DataBuffer.TYPE_USHORT;
                    isValidDataFormat = true;
                }
                break;
            case 32:
              if (sampleFormat[0] == 3)
                isValidDataFormat = false;
              else {
                dataType = DataBuffer.TYPE_INT;
                isValidDataFormat = true;
              }
              break;
            }

            if(!isValidDataFormat) {
                throw new RuntimeException("TIFFImage3");
            }

            // Figure out what compression if any, is being used.
            TIFFField compField = dir.getField(TIFFImageDecoder.TIFF_COMPRESSION);
            compression = compField == null ? COMP_NONE : compField.getAsInt(0);

            // Get the photometric interpretation.
            int photometricType = (int)dir.getFieldAsLong(
                                                          TIFFImageDecoder.TIFF_PHOTOMETRIC_INTERPRETATION);

            // Determine which kind of image we are dealing with.
            imageType = TYPE_UNSUPPORTED;
            switch(photometricType) {
            case 0: // WhiteIsZero
                isWhiteZero = true;
            case 1: // BlackIsZero
                if(sampleSize == 1 && samplesPerPixel == 1) {
                    imageType = TYPE_BILEVEL;
                } else if(sampleSize == 4 && samplesPerPixel == 1) {
                    imageType = TYPE_GRAY_4BIT;
                } else if(sampleSize % 8 == 0) {
                    if(samplesPerPixel == 1) {
                        imageType = TYPE_GRAY;
                    } else if(samplesPerPixel == 2) {
                        imageType = TYPE_GRAY_ALPHA;
                    } else {
                        imageType = TYPE_GENERIC;
                    }
                }
                break;
            case 2: // RGB
                if(sampleSize % 8 == 0) {
                    if(samplesPerPixel == 3) {
                        imageType = TYPE_RGB;
                    } else if(samplesPerPixel == 4) {
                        imageType = TYPE_RGB_ALPHA;
                    } else {
                        imageType = TYPE_GENERIC;
                    }
                }
                break;
            case 3: // RGB Palette
                if(samplesPerPixel == 1 &&
                   (sampleSize == 4 || sampleSize == 8 || sampleSize == 16)) {
                    imageType = TYPE_PALETTE;
                }
                break;
            case 4: // Transparency mask
                if(sampleSize == 1 && samplesPerPixel == 1) {
                    imageType = TYPE_BILEVEL;
                }
                break;
            case 6: // YCbCr
                if(compression == COMP_JPEG_TTN2 &&
                   sampleSize == 8 && samplesPerPixel == 3) {
                    // Set color conversion flag.
                    colorConvertJPEG = param.getJPEGDecompressYCbCrToRGB();

                    // Set type to RGB if color converting.
                    imageType = colorConvertJPEG ? TYPE_RGB : TYPE_GENERIC;
                } else {
                    TIFFField chromaField = dir.getField(TIFF_YCBCR_SUBSAMPLING);
                    if(chromaField != null) {
                        chromaSubH = chromaField.getAsInt(0);
                        chromaSubV = chromaField.getAsInt(1);
                    } else {
                        chromaSubH = chromaSubV = 2;
                    }

                    if(chromaSubH*chromaSubV == 1) {
                        imageType = TYPE_GENERIC;
                    } else if(sampleSize == 8 && samplesPerPixel == 3) {
                        imageType = TYPE_YCBCR_SUB;
                    }
                }
                break;
            default: // Other including CMYK, CIE L*a*b*, unknown.
                if(sampleSize % 8 == 0) {
                    imageType = TYPE_GENERIC;
                }
            }

            // Bail out if not one of the supported types.
            if(imageType == TYPE_UNSUPPORTED) {
                throw new RuntimeException("TIFFImage4");
            }

            // Set basic image layout
            Rectangle bounds = new Rectangle
                (0, 0,
                 (int)dir.getFieldAsLong(TIFFImageDecoder.TIFF_IMAGE_WIDTH),
                 (int)dir.getFieldAsLong(TIFFImageDecoder.TIFF_IMAGE_LENGTH));

            // Set a preliminary band count. This may be changed later as needed.
            numBands = samplesPerPixel;

            // Figure out if any extra samples are present.
            TIFFField efield = dir.getField(TIFFImageDecoder.TIFF_EXTRA_SAMPLES);
            int extraSamples = efield == null ? 0 : (int)efield.getAsLong(0);

            int tileWidth, tileHeight;
            if (dir.getField(TIFFImageDecoder.TIFF_TILE_OFFSETS) != null) {
                tiled = true;
                // Image is in tiled format
                tileWidth = 
                    (int)dir.getFieldAsLong(TIFFImageDecoder.TIFF_TILE_WIDTH);
                tileHeight = 
                    (int)dir.getFieldAsLong(TIFFImageDecoder.TIFF_TILE_LENGTH);
                tileOffsets = 
                    (dir.getField(TIFFImageDecoder.TIFF_TILE_OFFSETS)).getAsLongs();
                tileByteCounts = 
                    getFieldAsLongs(dir.getField(TIFFImageDecoder.TIFF_TILE_BYTE_COUNTS));

            } else {
                tiled = false;

                // Image is in stripped format, looks like tiles to us
                // Note: Some legacy files may have tile width and height
                // written but use the strip offsets and byte counts fields
                // instead of the tile offsets and byte counts. Therefore
                // we default here to the tile dimensions if they are written.
                tileWidth =
                    dir.getField(TIFFImageDecoder.TIFF_TILE_WIDTH) != null ?
                    (int)dir.getFieldAsLong(TIFFImageDecoder.TIFF_TILE_WIDTH) :
                    bounds.width;
                TIFFField field =
                    dir.getField(TIFFImageDecoder.TIFF_ROWS_PER_STRIP);
                if (field == null) {
                    // Default is infinity (2^32 -1), basically the entire image
		
                    tileHeight =
                        dir.getField(TIFFImageDecoder.TIFF_TILE_LENGTH) != null ?
                        (int)dir.getFieldAsLong(TIFFImageDecoder.TIFF_TILE_LENGTH):
                        bounds.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 = bounds.height;
                    } else {
                        tileHeight = (int)l;
                    }
                }

                TIFFField tileOffsetsField = 
                    dir.getField(TIFFImageDecoder.TIFF_STRIP_OFFSETS);
                if (tileOffsetsField == null) {
                    throw new RuntimeException("TIFFImage5");
                } else {
                    tileOffsets = getFieldAsLongs(tileOffsetsField);
                }

                TIFFField tileByteCountsField = 
                    dir.getField(TIFFImageDecoder.TIFF_STRIP_BYTE_COUNTS);
                if (tileByteCountsField == null) {
                    throw new RuntimeException("TIFFImage6");
                } else {
                    tileByteCounts = getFieldAsLongs(tileByteCountsField);
                }
            }

            // Calculate number of tiles and the tileSize in bytes
            tilesX = (bounds.width + tileWidth - 1)/tileWidth;
            tilesY = (bounds.height + tileHeight - 1)/tileHeight;
            tileSize = tileWidth * tileHeight * numBands;

            // Check whether big endian or little endian format is used.
            isBigEndian = dir.isBigEndian();

            TIFFField fillOrderField = 
                dir.getField(TIFFImageDecoder.TIFF_FILL_ORDER);
            if (fillOrderField != null) {
                fillOrder = fillOrderField.getAsInt(0);
            } else {
                // Default Fill Order
                fillOrder = 1;
            }

            switch(compression) {
            case COMP_NONE:
            case COMP_PACKBITS:
                // Do nothing.
                break;
            case COMP_DEFLATE:
                inflater = new Inflater();
                break;
            case COMP_FAX_G3_1D:
            case COMP_FAX_G3_2D:
            case COMP_FAX_G4_2D:
                if(sampleSize != 1) {
                    throw new RuntimeException("TIFFImage7"); 
                }

                // Fax T.4 compression options
                if (compression == 3) {
                    TIFFField t4OptionsField = 
                        dir.getField(TIFFImageDecoder.TIFF_T4_OPTIONS);
                    if (t4OptionsField != null) {
                        tiffT4Options = t4OptionsField.getAsLong(0);
                    } else {
                        // Use default value
                        tiffT4Options = 0;
                    }
                }

                // Fax T.6 compression options
                if (compression == 4) {
                    TIFFField t6OptionsField =
                        dir.getField(TIFFImageDecoder.TIFF_T6_OPTIONS);
                    if (t6OptionsField != null) {
                        tiffT6Options = t6OptionsField.getAsLong(0);
                    } else {
                        // Use default value
                        tiffT6Options = 0;
                    }
                }

                // Fax encoding, need to create the Fax decoder.
                decoder = new TIFFFaxDecoder(fillOrder,
                                             tileWidth, tileHeight);
                break;

            case COMP_LZW:
                // LZW compression used, need to create the LZW decoder.
                TIFFField predictorField = 
                    dir.getField(TIFFImageDecoder.TIFF_PREDICTOR);

                if (predictorField == null) {
                    predictor = 1;
                } else {
                    predictor = predictorField.getAsInt(0);
		    
                    if (predictor != 1 && predictor != 2) {
                        throw new RuntimeException("TIFFImage8"); 
                    }
		    
                    if (predictor == 2 && sampleSize != 8) {
                        throw new RuntimeException(sampleSize + 
                                                   "TIFFImage9");
                    }
                }
		
                lzwDecoder = new TIFFLZWDecoder(tileWidth, predictor, 
                                                samplesPerPixel); 
                break;

            case COMP_JPEG_OLD:
                throw new RuntimeException("TIFFImage15");

            case COMP_JPEG_TTN2:
                if(!(sampleSize == 8 &&
                     ((imageType == TYPE_GRAY && samplesPerPixel == 1) ||
                      (imageType == TYPE_PALETTE && samplesPerPixel == 1) ||
                      (imageType == TYPE_RGB && samplesPerPixel == 3)))) {
                    throw new RuntimeException("TIFFImage16");
                }

                // Create decodeParam from JPEGTables field if present.
                if(dir.isTagPresent(TIFF_JPEG_TABLES)) {
                    TIFFField jpegTableField = dir.getField(TIFF_JPEG_TABLES);
                    byte[] jpegTable = jpegTableField.getAsBytes();
                    ByteArrayInputStream tableStream =
                        new ByteArrayInputStream(jpegTable);
                    JPEGImageDecoder decoder =
                        JPEGCodec.createJPEGDecoder(tableStream);
                    decoder.decodeAsRaster();
                    decodeParam = decoder.getJPEGDecodeParam();
                }

                break;
            default:
                throw new RuntimeException("TIFFImage10");
            }

            ColorModel  colorModel  = null;
            SampleModel sampleModel = null;
            switch(imageType) {
            case TYPE_BILEVEL:
            case TYPE_GRAY_4BIT:
                sampleModel = 
                    new MultiPixelPackedSampleModel(dataType,
                                                    tileWidth, 
                                                    tileHeight,
                                                    sampleSize);
                if(imageType == TYPE_BILEVEL) {
                    byte[] map = new byte[] {(byte)(isWhiteZero ? 255 : 0),
                                             (byte)(isWhiteZero ? 0 : 255)};
                    colorModel = new IndexColorModel(1, 2, map, map, map);
                } else {
                    byte [] map = new byte[16];
                    if (isWhiteZero) {
                        for (int i=0; i<map.length; i++)
                            map[i] = (byte)(255-(16*i));
                    } else {
                        for (int i=0; i<map.length; i++)
                            map[i] = (byte)(16*i);
                    }
                    colorModel = new IndexColorModel(4, 16, map, map, map);
                }
                break;

            case TYPE_GRAY:
            case TYPE_GRAY_ALPHA:
            case TYPE_RGB:
            case TYPE_RGB_ALPHA:
                // Create a pixel interleaved SampleModel with decreasing
                // band offsets.
                int[] reverseOffsets = new int[numBands];
                for (int i=0; i<numBands; i++) {
                    reverseOffsets[i] = numBands - 1 - i;
                }
                sampleModel = new PixelInterleavedSampleModel
                    (dataType, tileWidth, tileHeight,
                     numBands, numBands*tileWidth, reverseOffsets);

                if(imageType == TYPE_GRAY) {
                  colorModel = new ComponentColorModel
                    (ColorSpace.getInstance(ColorSpace.CS_GRAY),
                     new int[] { sampleSize }, false, false, 
                     Transparency.OPAQUE, dataType);
                } else if (imageType == TYPE_RGB) {
                  colorModel = new ComponentColorModel
                    (ColorSpace.getInstance(ColorSpace.CS_sRGB),
                     new int[] { sampleSize, sampleSize, sampleSize }, 
                     false, false, Transparency.OPAQUE, dataType);
                } else { // hasAlpha
                    // 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.
                    int transparency = Transparency.OPAQUE;
                    if(extraSamples == 1) { // associated (premultiplied) alpha
                        transparency = Transparency.TRANSLUCENT;
                    } else if(extraSamples == 2) { // unassociated alpha
                        transparency = Transparency.BITMASK;
                    }

                    colorModel =
                        createAlphaComponentColorModel(dataType,
                                                       numBands,
                                                       extraSamples == 1,
                                                       transparency);
                }
                break;

            case TYPE_GENERIC:
            case TYPE_YCBCR_SUB:
                // For this case we can't display the image, so we create a
                // SampleModel with increasing bandOffsets, and keep the
                // ColorModel as null, as there is no appropriate ColorModel.

                int[] bandOffsets = new int[numBands];
                for (int i=0; i<numBands; i++) {
                    bandOffsets[i] = i;
                }
		
                sampleModel = new PixelInterleavedSampleModel
                    (dataType, tileWidth, tileHeight,
                     numBands, numBands * tileWidth, bandOffsets);
                colorModel = null;
                break;

            case TYPE_PALETTE:
                // Get the colormap
                TIFFField cfield = dir.getField(TIFFImageDecoder.TIFF_COLORMAP);
                if (cfield == null) {
                    throw new RuntimeException("TIFFImage11");
                } else {
                    colormap = cfield.getAsChars();
                }

                // Could be either 1 or 3 bands depending on whether we use
                // IndexColorModel or not.
                if (decodePaletteAsShorts) {
                    numBands = 3;

                    // If no SampleFormat tag was specified and if the 
                    // sampleSize is 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 = createPixelInterleavedSampleModel
                        (dataType, tileWidth, tileHeight, numBands);

                  colorModel = new ComponentColorModel
                    (ColorSpace.getInstance(ColorSpace.CS_sRGB),
                     new int[] { 16, 16, 16 }, false, false, 
                     Transparency.OPAQUE, dataType);

                } else {

                    numBands = 1;
		
                    if (sampleSize == 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, 
                             sampleSize);
                    } else if (sampleSize == 8) {
                        
                        sampleModel = createPixelInterleavedSampleModel
                            (DataBuffer.TYPE_BYTE, tileWidth, tileHeight,
                             numBands);
                    } else if (sampleSize == 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.
                        dataType = DataBuffer.TYPE_USHORT;
                        sampleModel = createPixelInterleavedSampleModel
                            (DataBuffer.TYPE_USHORT, tileWidth, tileHeight,
                             numBands);
                    }
		
                    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(sampleSize, 
                                                     bandLength, r, g, b);
                }
                break;

            default:
                throw new RuntimeException("TIFFImage4");
            }

        Map properties = new HashMap();
        // Set a property "tiff_directory".
        properties.put("tiff_directory", dir);

        // System.out.println("Constructed TIFF");

        init((CachableRed)null, bounds, colorModel, sampleModel, 
             0, 0, properties);
    }

    /**
     * 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 TIFFDirectory getPrivateIFD(long offset) throws IOException {
        return new TIFFDirectory(stream, offset, 0);
    }


    public WritableRaster copyData(WritableRaster wr) {
        copyToRaster(wr);
        return wr;
    }


    /**
     * Returns tile (tileX, tileY) as a Raster.
     */
    public synchronized Raster getTile(int tileX, int tileY) {
        if ((tileX < 0) || (tileX >= tilesX) ||
            (tileY < 0) || (tileY >= tilesY)) {
            throw new IllegalArgumentException("TIFFImage12");
        }

        // System.out.println("Called TIFF getTile:" + tileX + "," + tileY);


        // Get the data array out of the DataBuffer
        byte bdata[] = null;
        short sdata[] = null;
        int idata[] = null;

        SampleModel sampleModel = getSampleModel();
        WritableRaster tile = makeTile(tileX,tileY);

        DataBuffer buffer = tile.getDataBuffer();

        int dataType = sampleModel.getDataType();
        if (dataType == DataBuffer.TYPE_BYTE) {
            bdata = ((DataBufferByte)buffer).getData();
        } else if (dataType == DataBuffer.TYPE_USHORT) {
            sdata = ((DataBufferUShort)buffer).getData();
        } else if (dataType == DataBuffer.TYPE_SHORT) {
            sdata = ((DataBufferShort)buffer).getData();
        } else if (dataType == DataBuffer.TYPE_INT) {
            idata = ((DataBufferInt)buffer).getData();
        }

        // Variables used for swapping when converting from RGB to BGR
        byte bswap;
        short sswap;
        int iswap;

        // 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("TIFFImage13");
        }

        // 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 newRect;
        if (!tiled)
            newRect = tile.getBounds();
        else
            newRect = new Rectangle(tile.getMinX(), tile.getMinY(),
                                    tileWidth, tileHeight);
            
        int unitsInThisTile = newRect.width * newRect.height * numBands;

        // Allocate read buffer if needed.
        byte data[] = compression != COMP_NONE || imageType == TYPE_PALETTE ?
            new byte[byteCount] : null;

        // Read the data, uncompressing as needed. There are four cases:
        // bilevel, palette-RGB, 4-bit grayscale, and everything else.
        if(imageType == TYPE_BILEVEL) { // bilevel
            try {
                if (compression == COMP_PACKBITS) {
                    stream.readFully(data, 0, byteCount);

                    // Since the decompressed data will still be packed 
                    // 8 pixels into 1 byte, calculate bytesInThisTile
                    int bytesInThisTile;
                    if ((newRect.width % 8) == 0) {
                        bytesInThisTile = (newRect.width/8) * newRect.height;
                    } else {
                        bytesInThisTile =
                            (newRect.width/8 + 1) * newRect.height;
                    }
                    decodePackbits(data, bytesInThisTile, bdata);
                } else if (compression == COMP_LZW) {
                    stream.readFully(data, 0, byteCount);
                    lzwDecoder.decode(data, bdata, newRect.height);
                } else if (compression == COMP_FAX_G3_1D) {
                    stream.readFully(data, 0, byteCount);
                    decoder.decode1D(bdata, data, 0, newRect.height);
                } else if (compression == COMP_FAX_G3_2D) {
                    stream.readFully(data, 0, byteCount);
                    decoder.decode2D(bdata, data, 0, newRect.height,
                                     tiffT4Options);
                } else if (compression == COMP_FAX_G4_2D) {
                    stream.readFully(data, 0, byteCount);
                    decoder.decodeT6(bdata, data, 0, newRect.height,
                                     tiffT6Options);
                } else if (compression == COMP_DEFLATE) {
                    stream.readFully(data, 0, byteCount);
                    inflate(data, bdata);
                } else if (compression == COMP_NONE) {
                    stream.readFully(bdata, 0, byteCount);
                }

                stream.seek(save_offset);
            } catch (IOException ioe) {
                throw new RuntimeException("TIFFImage13");
            }
        } else if(imageType == TYPE_PALETTE) { // palette-RGB
            if (sampleSize == 16) {

                if (decodePaletteAsShorts) {
		    
                    short tempData[]= null;
		    		    
                    // 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;
		    
                    // Read the data, if compressed, decode it, reset the pointer
                    try {
			
                        if (compression == COMP_PACKBITS) {

                            stream.readFully(data, 0, byteCount);
			    
                            byte byteArray[] = new byte[entries];
                            decodePackbits(data, entries, byteArray);
                            tempData = new short[unitsBeforeLookup];
                            interpretBytesAsShorts(byteArray, tempData,
                                                   unitsBeforeLookup);
			    
                        }  else if (compression == COMP_LZW) {

                            // Read in all the compressed data for this tile
                            stream.readFully(data, 0, byteCount);

                            byte byteArray[] = new byte[entries];
                            lzwDecoder.decode(data, byteArray, newRect.height);
                            tempData = new short[unitsBeforeLookup];
                            interpretBytesAsShorts(byteArray, tempData,
                                                   unitsBeforeLookup);

                        }  else if (compression == COMP_DEFLATE) {

                            stream.readFully(data, 0, byteCount);
                            byte byteArray[] = new byte[entries];
                            inflate(data, byteArray);
                            tempData = new short[unitsBeforeLookup];
                            interpretBytesAsShorts(byteArray, tempData,
                                                   unitsBeforeLookup);

                        } else if (compression == COMP_NONE) {

                            // byteCount tells us how many bytes are there
                            // in this tile, but we need to read in shorts, 
                            // which will take half the space, so while
                            // allocating we divide byteCount by 2.
                            tempData = new short[byteCount/2];
                            readShorts(byteCount/2, tempData);
                        }			

                        stream.seek(save_offset);

                    } catch (IOException ioe) {
                        throw new RuntimeException("TIFFImage13");
                    }

                    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 = tempData[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 = tempData[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;
                        }
                    }

                } else {
		    
                    // No lookup being done here, when RGB values are needed,
                    // the associated IndexColorModel can be used to get them.
		    
                    try {

                        if (compression == COMP_PACKBITS) {

                            stream.readFully(data, 0, byteCount);

                            // Since unitsInThisTile is the number of shorts, 
                            // but we do our decompression in terms of bytes, we 
                            // need to multiply unitsInThisTile by 2 in order to 
                            // figure out how many bytes we'll get after 
                            // decompression.
                            int bytesInThisTile = unitsInThisTile * 2;

                            byte byteArray[] = new byte[bytesInThisTile];
                            decodePackbits(data, bytesInThisTile, byteArray);
                            interpretBytesAsShorts(byteArray, sdata,
                                                   unitsInThisTile);
			    
                        } else if (compression == COMP_LZW) {
			    
                            stream.readFully(data, 0, byteCount);

                            // Since unitsInThisTile is the number of shorts, 
                            // but we do our decompression in terms of bytes, we 
                            // need to multiply unitsInThisTile by 2 in order to 
                            // figure out how many bytes we'll get after 
                            // decompression.
                            byte byteArray[] = new byte[unitsInThisTile * 2];
                            lzwDecoder.decode(data, byteArray, newRect.height);
                            interpretBytesAsShorts(byteArray, sdata, 
                                                   unitsInThisTile);

                        }  else if (compression == COMP_DEFLATE) {

                            stream.readFully(data, 0, byteCount);
                            byte byteArray[] = new byte[unitsInThisTile * 2];
                            inflate(data, byteArray);
                            interpretBytesAsShorts(byteArray, sdata, 
                                                   unitsInThisTile);

                        } else if (compression == COMP_NONE) {

                            readShorts(byteCount/2, sdata);
                        }
			
                        stream.seek(save_offset);

                    } catch (IOException ioe) {
                        throw new RuntimeException("TIFFImage13");
                    }		    
                }
		
            } else if (sampleSize == 8) {

                if (decodePaletteAsShorts) {
		    
                    byte tempData[]= null;

                    // 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;
		    
                    // Read the data, if compressed, decode it, reset the pointer
                    try {
			
                        if (compression == COMP_PACKBITS) {
			    
                            stream.readFully(data, 0, byteCount);
                            tempData = new byte[unitsBeforeLookup];
                            decodePackbits(data, unitsBeforeLookup, tempData);
			    
                        }  else if (compression == COMP_LZW) {
			    
                            stream.readFully(data, 0, byteCount);
                            tempData = new byte[unitsBeforeLookup];
                            lzwDecoder.decode(data, tempData, newRect.height);
			    
                        } else if (compression == COMP_JPEG_TTN2) {

                            stream.readFully(data, 0, byteCount);
                            Raster tempTile = decodeJPEG(data,
                                                         decodeParam,
                                                         colorConvertJPEG,
                                                         tile.getMinX(),
                                                         tile.getMinY());
                            int[] tempPixels = new int[unitsBeforeLookup];
                            tempTile.getPixels(tile.getMinX(),
                                               tile.getMinY(),
                                               tile.getWidth(),
                                               tile.getHeight(),
                                               tempPixels);
                            tempData = new byte[unitsBeforeLookup];
                            for(int i = 0; i < unitsBeforeLookup; i++) {
                                tempData[i] = (byte)tempPixels[i];
                            }

                        }  else if (compression == COMP_DEFLATE) {

                            stream.readFully(data, 0, byteCount);
                            tempData = new byte[unitsBeforeLookup];
                            inflate(data, tempData);

                        } else if (compression == COMP_NONE) {

                            tempData = new byte[byteCount];
                            stream.readFully(tempData, 0, byteCount);
                        }			

                        stream.seek(save_offset);

                    } catch (IOException ioe) {
                        throw new RuntimeException("TIFFImage13");
                    }

                    // 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 = tempData[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 {
		    
                    // No lookup being done here, when RGB values are needed,
                    // the associated IndexColorModel can be used to get them.
		    
                    try {

                        if (compression == COMP_PACKBITS) {
			    
                            stream.readFully(data, 0, byteCount);
                            decodePackbits(data, unitsInThisTile, bdata);
			    
                        } else if (compression == COMP_LZW) {
			    
                            stream.readFully(data, 0, byteCount);
                            lzwDecoder.decode(data, bdata, newRect.height);
			    
                        } else if (compression == COMP_JPEG_TTN2) {

                            stream.readFully(data, 0, byteCount);
                            tile.setRect(decodeJPEG(data,
                                                    decodeParam,
                                                    colorConvertJPEG,
                                                    tile.getMinX(),
                                                    tile.getMinY()));

                        }  else if (compression == COMP_DEFLATE) {
                            
                            stream.readFully(data, 0, byteCount);
                            inflate(data, bdata);

                        } else if (compression == COMP_NONE) {

                            stream.readFully(bdata, 0, byteCount);
                        }
			
                        stream.seek(save_offset);

                    } catch (IOException ioe) {
                        throw new RuntimeException("TIFFImage13");
                    }		    
                }
		
            } else if (sampleSize == 4) {
		
                int padding = (newRect.width % 2 == 0) ? 0 : 1;
                int bytesPostDecoding = ((newRect.width/2 + padding) *
                                         newRect.height);
		
                // Output short images
                if (decodePaletteAsShorts) {		
		    
                    byte tempData[] = null;

                    try {
                        stream.readFully(data, 0, byteCount);
                        stream.seek(save_offset);
                    } catch (IOException ioe) {
                        throw new RuntimeException("TIFFImage13");
                    }
		    
                    // If compressed, decode the data.
                    if (compression == COMP_PACKBITS) {
			
                        tempData = new byte[bytesPostDecoding];
                        decodePackbits(data, bytesPostDecoding, tempData);
			
                    }  else if (compression == COMP_LZW) {
			
                        tempData = new byte[bytesPostDecoding];
                        lzwDecoder.decode(data, tempData, newRect.height);
			
                    }  else if (compression == COMP_DEFLATE) {

                        tempData = new byte[bytesPostDecoding];
                        inflate(data, tempData);
			
                    } else if (compression == COMP_NONE) {
			
                        tempData = data;
                    } 
		    
                    int bytes = unitsInThisTile / 3;
		    
                    // Unpack the 2 pixels packed into each 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)((tempData[srcCount] & 0xf0) >> 4);
                            data[dstCount++] = 
                                (byte)(tempData[srcCount++] & 0x0f);
                        }
		       
                        if (padding == 1) {
                            data[dstCount++] = 
                                (byte)((tempData[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 {
		    
                    // Output byte values, use IndexColorModel for unpacking
                    try {
			
                        // If compressed, decode the data.
                        if (compression == COMP_PACKBITS) {
			    
                            stream.readFully(data, 0, byteCount);
                            decodePackbits(data, bytesPostDecoding, bdata);
			    
                        }  else if (compression == COMP_LZW) {
			    
                            stream.readFully(data, 0, byteCount);
                            lzwDecoder.decode(data, bdata, newRect.height);
			    
                        }  else if (compression == COMP_DEFLATE) {

                            stream.readFully(data, 0, byteCount);
                            inflate(data, bdata);

                        } else if (compression == COMP_NONE) {
			    
                            stream.readFully(bdata, 0, byteCount);
                        } 
			
                        stream.seek(save_offset);

                    } catch (IOException ioe) {
                        throw new RuntimeException("TIFFImage13");
                    }
                }
            }
        } else if(imageType == TYPE_GRAY_4BIT) { // 4-bit gray
            try {
                if (compression == COMP_PACKBITS) {
			
                    stream.readFully(data, 0, byteCount);
			
                    // Since the decompressed data will still be packed 
                    // 2 pixels into 1 byte, calculate bytesInThisTile
                    int bytesInThisTile;
                    if ((newRect.width % 8) == 0) {
                        bytesInThisTile = (newRect.width/2) * newRect.height;
                    } else {
                        bytesInThisTile = (newRect.width/2 + 1) * 
                            newRect.height;
                    }
			
                    decodePackbits(data, bytesInThisTile, bdata);
			
                } else if (compression == COMP_LZW) {
			
                    stream.readFully(data, 0, byteCount);
                    lzwDecoder.decode(data, bdata, newRect.height);
			
                }  else if (compression == COMP_DEFLATE) {
			
                    stream.readFully(data, 0, byteCount);
                    inflate(data, bdata);
			
                } else {

                    stream.readFully(bdata, 0, byteCount);
                }
		
                stream.seek(save_offset);
            } catch (IOException ioe) {
                throw new RuntimeException("TIFFImage13");
            }
        } else { // everything else
            try {
		
                if (sampleSize == 8) {
		    
                    if (compression == COMP_NONE) {
                        stream.readFully(bdata, 0, byteCount);
			
                    } else if (compression == COMP_LZW) {
			
                        stream.readFully(data, 0, byteCount);
                        lzwDecoder.decode(data, bdata, newRect.height);
			
                    } else if (compression == COMP_PACKBITS) {
			
                        stream.readFully(data, 0, byteCount);
                        decodePackbits(data, unitsInThisTile, bdata);
			
                    } else if (compression == COMP_JPEG_TTN2) {

                        stream.readFully(data, 0, byteCount);
                        tile.setRect(decodeJPEG(data,
                                                decodeParam,
                                                colorConvertJPEG,
                                                tile.getMinX(),
                                                tile.getMinY()));
                    } else if (compression == COMP_DEFLATE) {

                        stream.readFully(data, 0, byteCount);
                        inflate(data, bdata);
                    }
		    
                } else if (sampleSize == 16) {
		    
                    if (compression == COMP_NONE) {
			
                        readShorts(byteCount/2, sdata);
			
                    } else if (compression == COMP_LZW) {
			
                        stream.readFully(data, 0, byteCount);

                        // Since unitsInThisTile is the number of shorts, 
                        // but we do our decompression in terms of bytes, we 
                        // need to multiply unitsInThisTile by 2 in order to 
                        // figure out how many bytes we'll get after 
                        // decompression.
                        byte byteArray[] = new byte[unitsInThisTile * 2];
                        lzwDecoder.decode(data, byteArray, newRect.height);
                        interpretBytesAsShorts(byteArray, sdata, 
                                               unitsInThisTile);
			
                    } else if (compression == COMP_PACKBITS) {
			
                        stream.readFully(data, 0, byteCount);

                        // Since unitsInThisTile is the number of shorts, 
                        // but we do our decompression in terms of bytes, we 
                        // need to multiply unitsInThisTile by 2 in order to 
                        // figure out how many bytes we'll get after 
                        // decompression.
                        int bytesInThisTile = unitsInThisTile * 2;

                        byte byteArray[] = new byte[bytesInThisTile];
                        decodePackbits(data, bytesInThisTile, byteArray);
                        interpretBytesAsShorts(byteArray, sdata, 
                                               unitsInThisTile);
                    } else if (compression == COMP_DEFLATE) {

                        stream.readFully(data, 0, byteCount);
                        byte byteArray[] = new byte[unitsInThisTile * 2];
                        inflate(data, byteArray);
                        interpretBytesAsShorts(byteArray, sdata, 
                                               unitsInThisTile);

                    }
                } else if (sampleSize == 32 &&
                           dataType == DataBuffer.TYPE_INT) { // redundant
                    if (compression == COMP_NONE) {
			
                        readInts(byteCount/4, idata);

                    } else if (compression == COMP_LZW) {
			
                        stream.readFully(data, 0, byteCount);

                        // Since unitsInThisTile is the number of ints, 
                        // but we do our decompression in terms of bytes, we 
                        // need to multiply unitsInThisTile by 4 in order to 
                        // figure out how many bytes we'll get after 
                        // decompression.
                        byte byteArray[] = new byte[unitsInThisTile * 4];
                        lzwDecoder.decode(data, byteArray, newRect.height);
                        interpretBytesAsInts(byteArray, idata, 
                                             unitsInThisTile);
			
                    } else if (compression == COMP_PACKBITS) {
			
                        stream.readFully(data, 0, byteCount);

                        // Since unitsInThisTile is the number of ints, 
                        // but we do our decompression in terms of bytes, we 
                        // need to multiply unitsInThisTile by 4 in order to 
                        // figure out how many bytes we'll get after 
                        // decompression.
                        int bytesInThisTile = unitsInThisTile * 4;

                        byte byteArray[] = new byte[bytesInThisTile];
                        decodePackbits(data, bytesInThisTile, byteArray);
                        interpretBytesAsInts(byteArray, idata, 
                                             unitsInThisTile);
                    } else if (compression == COMP_DEFLATE) {

                        stream.readFully(data, 0, byteCount);
                        byte byteArray[] = new byte[unitsInThisTile * 4];
                        inflate(data, byteArray);
                        interpretBytesAsInts(byteArray, idata, 
                                             unitsInThisTile);

                    }
                }

                stream.seek(save_offset);

            } catch (IOException ioe) {
                throw new RuntimeException("TIFFImage13");
            }

            // Modify the data for certain special cases.
            switch(imageType) {
            case TYPE_GRAY:
            case TYPE_GRAY_ALPHA:
                if(isWhiteZero) {
                    // 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 (dataType == DataBuffer.TYPE_BYTE &&
                        !(getColorModel() instanceof IndexColorModel)) {

                        for (int l = 0; l < bdata.length; l += numBands) {
                            bdata[l] = (byte)(255 - bdata[l]);
                        }
                    } else if (dataType == DataBuffer.TYPE_USHORT) {

                        int ushortMax = Short.MAX_VALUE - Short.MIN_VALUE;
                        for (int l = 0; l < sdata.length; l += numBands) {
                            sdata[l] = (short)(ushortMax - sdata[l]);
                        }

                    } else if (dataType == DataBuffer.TYPE_SHORT) {

                        for (int l = 0; l < sdata.length; l += numBands) {
                            sdata[l] = (short)(~sdata[l]);
                        }
                    } else if (dataType == DataBuffer.TYPE_INT) {

                        long uintMax = ((long)Integer.MAX_VALUE - 
                                        (long)Integer.MIN_VALUE);
                        for (int l = 0; l < idata.length; l += numBands) {
                            idata[l] = (int)(uintMax - idata[l]);
                        }
                    }
                }
                break;
            case TYPE_RGB:
                // Change RGB to BGR order, as Java2D displays that faster.
                // Unnecessary for JPEG-in-TIFF as the decoder handles it.
                if (sampleSize == 8 && compression != COMP_JPEG_TTN2) {
                    for (int i=0; i<unitsInThisTile; i+=3) {
                        bswap = bdata[i];
                        bdata[i] = bdata[i+2];
                        bdata[i+2] = bswap;
                    }
                } else if (sampleSize == 16) {
                    for (int i=0; i<unitsInThisTile; i+=3) {
                        sswap = sdata[i];
                        sdata[i] = sdata[i+2];
                        sdata[i+2] = sswap;
                    }
                } else if (sampleSize == 32) {
                    if(dataType == DataBuffer.TYPE_INT) {
                        for (int i=0; i<unitsInThisTile; i+=3) {
                            iswap = idata[i];
                            idata[i] = idata[i+2];
                            idata[i+2] = iswap;
                        }
                    }
                }
                break;
            case TYPE_RGB_ALPHA:
                // Convert from RGBA to ABGR for Java2D
                if (sampleSize == 8) {
                    for (int i=0; i<unitsInThisTile; i+=4) {
                        // Swap R and A
                        bswap = bdata[i];
                        bdata[i] = bdata[i+3];
                        bdata[i+3] = bswap;
			
                        // Swap G and B
                        bswap = bdata[i+1];
                        bdata[i+1] = bdata[i+2];
                        bdata[i+2] = bswap;
                    }
                } else if (sampleSize == 16) {
                    for (int i=0; i<unitsInThisTile; i+=4) {
                        // Swap R and A
                        sswap = sdata[i];
                        sdata[i] = sdata[i+3];
                        sdata[i+3] = sswap;

                        // Swap G and B
                        sswap = sdata[i+1];
                        sdata[i+1] = sdata[i+2];
                        sdata[i+2] = sswap;
                    }
                } else if (sampleSize == 32) {
                    if(dataType == DataBuffer.TYPE_INT) {
                        for (int i=0; i<unitsInThisTile; i+=4) {
                            // Swap R and A
                            iswap = idata[i];
                            idata[i] = idata[i+3];
                            idata[i+3] = iswap;

                            // Swap G and B
                            iswap = idata[i+1];
                            idata[i+1] = idata[i+2];
                            idata[i+2] = iswap;
                        }
                    }
                }
                break;
            case TYPE_YCBCR_SUB:
                // Post-processing for YCbCr with subsampled chrominance:
                // simply replicate the chroma channels for displayability.
                int pixelsPerDataUnit = chromaSubH*chromaSubV;

                int numH = newRect.width/chromaSubH;
                int numV = newRect.height/chromaSubV;

                byte[] tempData = new byte[numH*numV*(pixelsPerDataUnit + 2)];
                System.arraycopy(bdata, 0, tempData, 0, tempData.length);

                int samplesPerDataUnit = pixelsPerDataUnit*3;
                int[] pixels = new int[samplesPerDataUnit];

                int bOffset = 0;
                int offsetCb = pixelsPerDataUnit;
                int offsetCr = offsetCb + 1;

                int y = newRect.y;
                for(int j = 0; j < numV; j++) {
                    int x = newRect.x;
                    for(int i = 0; i < numH; i++) {
                        int Cb = tempData[bOffset + offsetCb];
                        int Cr = tempData[bOffset + offsetCr];
                        int k = 0;
                        while(k < samplesPerDataUnit) {
                            pixels[k++] = tempData[bOffset++];
                            pixels[k++] = Cb;
                            pixels[k++] = Cr;
                        }
                        bOffset += 2;
                        tile.setPixels(x, y, chromaSubH, chromaSubV, pixels);
                        x += chromaSubH;
                    }
                    y += chromaSubV;
                }

                break;
            }
        }
        
        return tile;
    }

    private void readShorts(int shortCount, short shortArray[]) {
	
        // Since each short consists of 2 bytes, we need a 
        // byte array of double size
        int byteCount = 2 * shortCount;
        byte byteArray[] = new byte[byteCount];

        try {	    
            stream.readFully(byteArray, 0, byteCount);
        } catch (IOException ioe) {
            throw new RuntimeException("TIFFImage13");
        }

        interpretBytesAsShorts(byteArray, shortArray, shortCount);
    }

    private void readInts(int intCount, int intArray[]) {
	
        // Since each int consists of 4 bytes, we need a 
        // byte array of quadruple size
        int byteCount = 4 * intCount;
        byte byteArray[] = new byte[byteCount];

        try {	    
            stream.readFully(byteArray, 0, byteCount);
        } catch (IOException ioe) {
            throw new RuntimeException("TIFFImage13");
        }

        interpretBytesAsInts(byteArray, intArray, intCount);
    }

    // Method to interpret a byte array to a short array, depending on 
    // whether the bytes are stored in a big endian or little endian format.
    private void interpretBytesAsShorts(byte byteArray[], 
                                        short shortArray[], 
                                        int shortCount) {

        int j = 0;
        int firstByte, secondByte;

        if (isBigEndian) {

            for (int i=0; i<shortCount; i++) {
                firstByte = byteArray[j++] & 0xff;
                secondByte = byteArray[j++] & 0xff;
                shortArray[i] = (short)((firstByte << 8) + secondByte);
            }
	    
        } else {
	    
            for (int i=0; i<shortCount; i++) {
                firstByte = byteArray[j++] & 0xff;
                secondByte = byteArray[j++] & 0xff;
                shortArray[i] = (short)((secondByte << 8) + firstByte);
            }
        }
    }

    // Method to interpret a byte array to a int array, depending on 
    // whether the bytes are stored in a big endian or little endian format.
    private void interpretBytesAsInts(byte byteArray[],
                                      int intArray[], 
                                      int intCount) {

        int j = 0;

        if (isBigEndian) {
	    
            for (int i=0; i<intCount; i++) {
                intArray[i] = (((byteArray[j++] & 0xff) << 24) |
                               ((byteArray[j++] & 0xff) << 16) |
                               ((byteArray[j++] & 0xff) << 8) |
                               ( byteArray[j++] & 0xff));
            }
	    
        } else {
	    
            for (int i=0; i<intCount; i++) {
                intArray[i] = ((byteArray[j++] & 0xff) |
                              ((byteArray[j++] & 0xff) << 8) |
                              ((byteArray[j++] & 0xff) << 16) |
                              ((byteArray[j++] & 0xff) << 24));
            }
        }
    }

    // Uncompress packbits compressed image data.
    private byte[] decodePackbits(byte data[], int arraySize, byte[] dst) {

        if (dst == null) {
            dst = new byte[arraySize];
        }

        int srcCount = 0, dstCount = 0;
        byte repeat, b;

        try {
	    
            while (dstCount < arraySize) {
		
                b = data[srcCount++];
		
                if (b >= 0 && b <= 127) {
		    
                    // literal run packet
                    for (int i=0; i<(b + 1); i++) {
                        dst[dstCount++] = data[srcCount++];
                    }
		    		    
                } else if (b <= -1 && b >= -127) {
		    
                    // 2 byte encoded run packet
                    repeat = data[srcCount++];
                    for (int i=0; i<(-b + 1); i++) {
                        dst[dstCount++] = repeat;
                    }
		    
                } else {
                    // no-op packet. Do nothing
                    srcCount++;
                }
            }
        } catch (java.lang.ArrayIndexOutOfBoundsException ae) {
            throw new RuntimeException("TIFFImage14");
        }

        return dst;
    }

    // Need a createColorModel().
    // Create ComponentColorModel for TYPE_RGB images
    private ComponentColorModel createAlphaComponentColorModel
    (int dataType, int numBands, 
     boolean isAlphaPremultiplied, int transparency) {
	
        ComponentColorModel ccm = null;
        int RGBBits[] = null;
        ColorSpace cs = null;
        switch(numBands) {
            case 2: // gray+alpha
                cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
                break;
            case 4: // RGB+alpha
                cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
                break;
            default:
                throw new IllegalArgumentException();
        }
        
        int componentSize = 0;
        switch(dataType) {
            case DataBuffer.TYPE_BYTE:
                componentSize = 8;
                break;
            case DataBuffer.TYPE_USHORT:
            case DataBuffer.TYPE_SHORT:
                componentSize = 16;
                break;
            case DataBuffer.TYPE_INT:
                componentSize = 32;
                break;
            default:
                throw new IllegalArgumentException();
        }

        RGBBits = new int[numBands];
        for(int i = 0; i < numBands; i++) {
            RGBBits[i] = componentSize;
        }

        ccm = new ComponentColorModel(cs,
                                      RGBBits,
                                      true,
                                      isAlphaPremultiplied,
                                      transparency,
                                      dataType);


        return ccm;		
    }
        
}
