jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageReader.java
changeset 34416 68c0d866db5d
child 36448 a07e108d5722
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageReader.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,1329 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.color.ColorSpace;
+import java.awt.color.ICC_ColorSpace;
+import java.awt.color.ICC_Profile;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.ComponentColorModel;
+import java.awt.image.Raster;
+import java.awt.image.RenderedImage;
+import java.awt.image.SampleModel;
+import java.io.IOException;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import javax.imageio.IIOException;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReader;
+import javax.imageio.ImageReadParam;
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.stream.ImageInputStream;
+import org.w3c.dom.Node;
+import com.sun.imageio.plugins.common.ImageUtil;
+import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
+import javax.imageio.plugins.tiff.TIFFField;
+import javax.imageio.plugins.tiff.TIFFImageReadParam;
+import javax.imageio.plugins.tiff.TIFFTagSet;
+
+public class TIFFImageReader extends ImageReader {
+
+    // A somewhat arbitrary upper bound on SamplesPerPixel. Hyperspectral
+    // images as of this writing appear to be under 300 bands so this should
+    // account for those cases should they arise.
+    private static final int SAMPLES_PER_PIXEL_MAX = 1024;
+
+    // In baseline TIFF the largest data types are 64-bit long and double.
+    private static final int BITS_PER_SAMPLE_MAX = 64;
+
+    // The current ImageInputStream source.
+    private ImageInputStream stream = null;
+
+    // True if the file header has been read.
+    private boolean gotHeader = false;
+
+    private ImageReadParam imageReadParam = getDefaultReadParam();
+
+    // Stream metadata, or null.
+    private TIFFStreamMetadata streamMetadata = null;
+
+    // The current image index.
+    private int currIndex = -1;
+
+    // Metadata for image at 'currIndex', or null.
+    private TIFFImageMetadata imageMetadata = null;
+
+    // A <code>List</code> of <code>Long</code>s indicating the stream
+    // positions of the start of the IFD for each image.  Entries
+    // are added as needed.
+    private List<Long> imageStartPosition = new ArrayList<Long>();
+
+    // The number of images in the stream, if known, otherwise -1.
+    private int numImages = -1;
+
+    // The ImageTypeSpecifiers of the images in the stream.
+    // Contains a map of Integers to Lists.
+    private HashMap<Integer, List<ImageTypeSpecifier>> imageTypeMap
+            = new HashMap<Integer, List<ImageTypeSpecifier>>();
+
+    private BufferedImage theImage = null;
+
+    private int width = -1;
+    private int height = -1;
+    private int numBands = -1;
+    private int tileOrStripWidth = -1, tileOrStripHeight = -1;
+
+    private int planarConfiguration = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
+
+    private int compression;
+    private int photometricInterpretation;
+    private int samplesPerPixel;
+    private int[] sampleFormat;
+    private int[] bitsPerSample;
+    private int[] extraSamples;
+    private char[] colorMap;
+
+    private int sourceXOffset;
+    private int sourceYOffset;
+    private int srcXSubsampling;
+    private int srcYSubsampling;
+
+    private int dstWidth;
+    private int dstHeight;
+    private int dstMinX;
+    private int dstMinY;
+    private int dstXOffset;
+    private int dstYOffset;
+
+    private int tilesAcross;
+    private int tilesDown;
+
+    private int pixelsRead;
+    private int pixelsToRead;
+
+    public TIFFImageReader(ImageReaderSpi originatingProvider) {
+        super(originatingProvider);
+    }
+
+    public void setInput(Object input,
+            boolean seekForwardOnly,
+            boolean ignoreMetadata) {
+        super.setInput(input, seekForwardOnly, ignoreMetadata);
+
+        // Clear all local values based on the previous stream contents.
+        resetLocal();
+
+        if (input != null) {
+            if (!(input instanceof ImageInputStream)) {
+                throw new IllegalArgumentException("input not an ImageInputStream!");
+            }
+            this.stream = (ImageInputStream) input;
+        } else {
+            this.stream = null;
+        }
+    }
+
+    // Do not seek to the beginning of the stream so as to allow users to
+    // point us at an IFD within some other file format
+    private void readHeader() throws IIOException {
+        if (gotHeader) {
+            return;
+        }
+        if (stream == null) {
+            throw new IllegalStateException("Input not set!");
+        }
+
+        // Create an object to store the stream metadata
+        this.streamMetadata = new TIFFStreamMetadata();
+
+        try {
+            int byteOrder = stream.readUnsignedShort();
+            if (byteOrder == 0x4d4d) {
+                streamMetadata.byteOrder = ByteOrder.BIG_ENDIAN;
+                stream.setByteOrder(ByteOrder.BIG_ENDIAN);
+            } else if (byteOrder == 0x4949) {
+                streamMetadata.byteOrder = ByteOrder.LITTLE_ENDIAN;
+                stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+            } else {
+                processWarningOccurred(
+                        "Bad byte order in header, assuming little-endian");
+                streamMetadata.byteOrder = ByteOrder.LITTLE_ENDIAN;
+                stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+            }
+
+            int magic = stream.readUnsignedShort();
+            if (magic != 42) {
+                processWarningOccurred(
+                        "Bad magic number in header, continuing");
+            }
+
+            // Seek to start of first IFD
+            long offset = stream.readUnsignedInt();
+            imageStartPosition.add(Long.valueOf(offset));
+            stream.seek(offset);
+        } catch (IOException e) {
+            throw new IIOException("I/O error reading header!", e);
+        }
+
+        gotHeader = true;
+    }
+
+    private int locateImage(int imageIndex) throws IIOException {
+        readHeader();
+
+        try {
+            // Find closest known index
+            int index = Math.min(imageIndex, imageStartPosition.size() - 1);
+
+            // Seek to that position
+            Long l = imageStartPosition.get(index);
+            stream.seek(l.longValue());
+
+            // Skip IFDs until at desired index or last image found
+            while (index < imageIndex) {
+                int count = stream.readUnsignedShort();
+                stream.skipBytes(12 * count);
+
+                long offset = stream.readUnsignedInt();
+                if (offset == 0) {
+                    return index;
+                }
+
+                imageStartPosition.add(Long.valueOf(offset));
+                stream.seek(offset);
+                ++index;
+            }
+        } catch (IOException e) {
+            throw new IIOException("Couldn't seek!", e);
+        }
+
+        if (currIndex != imageIndex) {
+            imageMetadata = null;
+        }
+        currIndex = imageIndex;
+        return imageIndex;
+    }
+
+    public int getNumImages(boolean allowSearch) throws IOException {
+        if (stream == null) {
+            throw new IllegalStateException("Input not set!");
+        }
+        if (seekForwardOnly && allowSearch) {
+            throw new IllegalStateException("seekForwardOnly and allowSearch can't both be true!");
+        }
+
+        if (numImages > 0) {
+            return numImages;
+        }
+        if (allowSearch) {
+            this.numImages = locateImage(Integer.MAX_VALUE) + 1;
+        }
+        return numImages;
+    }
+
+    public IIOMetadata getStreamMetadata() throws IIOException {
+        readHeader();
+        return streamMetadata;
+    }
+
+    // Throw an IndexOutOfBoundsException if index < minIndex,
+    // and bump minIndex if required.
+    private void checkIndex(int imageIndex) {
+        if (imageIndex < minIndex) {
+            throw new IndexOutOfBoundsException("imageIndex < minIndex!");
+        }
+        if (seekForwardOnly) {
+            minIndex = imageIndex;
+        }
+    }
+
+    // Verify that imageIndex is in bounds, find the image IFD, read the
+    // image metadata, initialize instance variables from the metadata.
+    private void seekToImage(int imageIndex) throws IIOException {
+        checkIndex(imageIndex);
+
+        int index = locateImage(imageIndex);
+        if (index != imageIndex) {
+            throw new IndexOutOfBoundsException("imageIndex out of bounds!");
+        }
+
+        readMetadata();
+
+        initializeFromMetadata();
+    }
+
+    // Stream must be positioned at start of IFD for 'currIndex'
+    private void readMetadata() throws IIOException {
+        if (stream == null) {
+            throw new IllegalStateException("Input not set!");
+        }
+
+        if (imageMetadata != null) {
+            return;
+        }
+        try {
+            // Create an object to store the image metadata
+            List<TIFFTagSet> tagSets;
+            if (imageReadParam instanceof TIFFImageReadParam) {
+                tagSets
+                        = ((TIFFImageReadParam) imageReadParam).getAllowedTagSets();
+            } else {
+                tagSets = new ArrayList<TIFFTagSet>(1);
+                tagSets.add(BaselineTIFFTagSet.getInstance());
+            }
+
+            this.imageMetadata = new TIFFImageMetadata(tagSets);
+            imageMetadata.initializeFromStream(stream, ignoreMetadata);
+        } catch (IIOException iioe) {
+            throw iioe;
+        } catch (IOException ioe) {
+            throw new IIOException("I/O error reading image metadata!", ioe);
+        }
+    }
+
+    private int getWidth() {
+        return this.width;
+    }
+
+    private int getHeight() {
+        return this.height;
+    }
+
+    // Returns tile width if image is tiled, else image width
+    private int getTileOrStripWidth() {
+        TIFFField f
+                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH);
+        return (f == null) ? getWidth() : f.getAsInt(0);
+    }
+
+    // Returns tile height if image is tiled, else strip height
+    private int getTileOrStripHeight() {
+        TIFFField f
+                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_LENGTH);
+        if (f != null) {
+            return f.getAsInt(0);
+        }
+
+        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP);
+        // Default for ROWS_PER_STRIP is 2^32 - 1, i.e., infinity
+        int h = (f == null) ? -1 : f.getAsInt(0);
+        return (h == -1) ? getHeight() : h;
+    }
+
+    private int getPlanarConfiguration() {
+        TIFFField f
+                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION);
+        if (f != null) {
+            int planarConfigurationValue = f.getAsInt(0);
+            if (planarConfigurationValue
+                    == BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) {
+                // Some writers (e.g. Kofax standard Multi-Page TIFF
+                // Storage Filter v2.01.000; cf. bug 4929147) do not
+                // correctly set the value of this field. Attempt to
+                // ascertain whether the value is correctly Planar.
+                if (getCompression()
+                        == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG
+                        && imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT)
+                        != null) {
+                    // JPEG interchange format cannot have
+                    // PlanarConfiguration value Chunky so reset.
+                    processWarningOccurred("PlanarConfiguration \"Planar\" value inconsistent with JPEGInterchangeFormat; resetting to \"Chunky\".");
+                    planarConfigurationValue
+                            = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
+                } else {
+                    TIFFField offsetField
+                            = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS);
+                    if (offsetField == null) {
+                        // Tiles
+                        offsetField
+                                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS);
+                        int tw = getTileOrStripWidth();
+                        int th = getTileOrStripHeight();
+                        int tAcross = (getWidth() + tw - 1) / tw;
+                        int tDown = (getHeight() + th - 1) / th;
+                        int tilesPerImage = tAcross * tDown;
+                        long[] offsetArray = offsetField.getAsLongs();
+                        if (offsetArray != null
+                                && offsetArray.length == tilesPerImage) {
+                            // Length of offsets array is
+                            // TilesPerImage for Chunky and
+                            // SamplesPerPixel*TilesPerImage for Planar.
+                            processWarningOccurred("PlanarConfiguration \"Planar\" value inconsistent with TileOffsets field value count; resetting to \"Chunky\".");
+                            planarConfigurationValue
+                                    = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
+                        }
+                    } else {
+                        // Strips
+                        int rowsPerStrip = getTileOrStripHeight();
+                        int stripsPerImage
+                                = (getHeight() + rowsPerStrip - 1) / rowsPerStrip;
+                        long[] offsetArray = offsetField.getAsLongs();
+                        if (offsetArray != null
+                                && offsetArray.length == stripsPerImage) {
+                            // Length of offsets array is
+                            // StripsPerImage for Chunky and
+                            // SamplesPerPixel*StripsPerImage for Planar.
+                            processWarningOccurred("PlanarConfiguration \"Planar\" value inconsistent with StripOffsets field value count; resetting to \"Chunky\".");
+                            planarConfigurationValue
+                                    = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
+                        }
+                    }
+                }
+            }
+            return planarConfigurationValue;
+        }
+
+        return BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
+    }
+
+    private long getTileOrStripOffset(int tileIndex) throws IIOException {
+        TIFFField f
+                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS);
+        if (f == null) {
+            f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS);
+        }
+        if (f == null) {
+            f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT);
+        }
+
+        if (f == null) {
+            throw new IIOException("Missing required strip or tile offsets field.");
+        }
+
+        return f.getAsLong(tileIndex);
+    }
+
+    private long getTileOrStripByteCount(int tileIndex) throws IOException {
+        TIFFField f
+                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS);
+        if (f == null) {
+            f
+                    = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS);
+        }
+        if (f == null) {
+            f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+        }
+
+        long tileOrStripByteCount;
+        if (f != null) {
+            tileOrStripByteCount = f.getAsLong(tileIndex);
+        } else {
+            processWarningOccurred("TIFF directory contains neither StripByteCounts nor TileByteCounts field: attempting to calculate from strip or tile width and height.");
+
+            // Initialize to number of bytes per strip or tile assuming
+            // no compression.
+            int bitsPerPixel = bitsPerSample[0];
+            for (int i = 1; i < samplesPerPixel; i++) {
+                bitsPerPixel += bitsPerSample[i];
+            }
+            int bytesPerRow = (getTileOrStripWidth() * bitsPerPixel + 7) / 8;
+            tileOrStripByteCount = bytesPerRow * getTileOrStripHeight();
+
+            // Clamp to end of stream if possible.
+            long streamLength = stream.length();
+            if (streamLength != -1) {
+                tileOrStripByteCount
+                        = Math.min(tileOrStripByteCount,
+                                streamLength - getTileOrStripOffset(tileIndex));
+            } else {
+                processWarningOccurred("Stream length is unknown: cannot clamp estimated strip or tile byte count to EOF.");
+            }
+        }
+
+        return tileOrStripByteCount;
+    }
+
+    private int getCompression() {
+        TIFFField f
+                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION);
+        if (f == null) {
+            return BaselineTIFFTagSet.COMPRESSION_NONE;
+        } else {
+            return f.getAsInt(0);
+        }
+    }
+
+    public int getWidth(int imageIndex) throws IOException {
+        seekToImage(imageIndex);
+        return getWidth();
+    }
+
+    public int getHeight(int imageIndex) throws IOException {
+        seekToImage(imageIndex);
+        return getHeight();
+    }
+
+    /**
+     * Initializes these instance variables from the image metadata:
+     * <pre>
+     * compression
+     * width
+     * height
+     * samplesPerPixel
+     * numBands
+     * colorMap
+     * photometricInterpretation
+     * sampleFormat
+     * bitsPerSample
+     * extraSamples
+     * tileOrStripWidth
+     * tileOrStripHeight
+     * </pre>
+     */
+    private void initializeFromMetadata() throws IIOException {
+        TIFFField f;
+
+        // Compression
+        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION);
+        if (f == null) {
+            processWarningOccurred("Compression field is missing; assuming no compression");
+            compression = BaselineTIFFTagSet.COMPRESSION_NONE;
+        } else {
+            compression = f.getAsInt(0);
+        }
+
+        // Whether key dimensional information is absent.
+        boolean isMissingDimension = false;
+
+        // ImageWidth -> width
+        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH);
+        if (f != null) {
+            this.width = f.getAsInt(0);
+        } else {
+            processWarningOccurred("ImageWidth field is missing.");
+            isMissingDimension = true;
+        }
+
+        // ImageLength -> height
+        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH);
+        if (f != null) {
+            this.height = f.getAsInt(0);
+        } else {
+            processWarningOccurred("ImageLength field is missing.");
+            isMissingDimension = true;
+        }
+
+        // SamplesPerPixel
+        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL);
+        if (f != null) {
+            samplesPerPixel = f.getAsInt(0);
+        } else {
+            samplesPerPixel = 1;
+            isMissingDimension = true;
+        }
+
+        // If any dimension is missing and there is a JPEG stream available
+        // get the information from it.
+        int defaultBitDepth = 1;
+        if (isMissingDimension
+                && (f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT)) != null) {
+            Iterator<ImageReader> iter = ImageIO.getImageReadersByFormatName("JPEG");
+            if (iter != null && iter.hasNext()) {
+                ImageReader jreader = iter.next();
+                try {
+                    stream.mark();
+                    stream.seek(f.getAsLong(0));
+                    jreader.setInput(stream);
+                    if (imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH) == null) {
+                        this.width = jreader.getWidth(0);
+                    }
+                    if (imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH) == null) {
+                        this.height = jreader.getHeight(0);
+                    }
+                    ImageTypeSpecifier imageType = jreader.getRawImageType(0);
+                    if (imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL) == null) {
+                        this.samplesPerPixel =
+                            imageType != null ?
+                                imageType.getSampleModel().getNumBands() : 3;
+                    }
+                    stream.reset();
+                    defaultBitDepth =
+                        imageType != null ?
+                        imageType.getColorModel().getComponentSize(0) : 8;
+                } catch (IOException e) {
+                    // Ignore it and proceed: an error will occur later.
+                }
+                jreader.dispose();
+            }
+        }
+
+        if (samplesPerPixel < 1) {
+            throw new IIOException("Samples per pixel < 1!");
+        } else if (samplesPerPixel > SAMPLES_PER_PIXEL_MAX) {
+            throw new IIOException
+                ("Samples per pixel (" + samplesPerPixel
+                + ") greater than allowed maximum ("
+                + SAMPLES_PER_PIXEL_MAX + ")");
+        }
+
+        // SamplesPerPixel -> numBands
+        numBands = samplesPerPixel;
+
+        // ColorMap
+        this.colorMap = null;
+        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_COLOR_MAP);
+        if (f != null) {
+            // Grab color map
+            colorMap = f.getAsChars();
+        }
+
+        // PhotometricInterpretation
+        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION);
+        if (f == null) {
+            if (compression == BaselineTIFFTagSet.COMPRESSION_CCITT_RLE
+                    || compression == BaselineTIFFTagSet.COMPRESSION_CCITT_T_4
+                    || compression == BaselineTIFFTagSet.COMPRESSION_CCITT_T_6) {
+                processWarningOccurred("PhotometricInterpretation field is missing; "
+                        + "assuming WhiteIsZero");
+                photometricInterpretation
+                        = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO;
+            } else if (this.colorMap != null) {
+                photometricInterpretation
+                        = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR;
+            } else if (samplesPerPixel == 3 || samplesPerPixel == 4) {
+                photometricInterpretation
+                        = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB;
+            } else {
+                processWarningOccurred("PhotometricInterpretation field is missing; "
+                        + "assuming BlackIsZero");
+                photometricInterpretation
+                        = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO;
+            }
+        } else {
+            photometricInterpretation = f.getAsInt(0);
+        }
+
+        // SampleFormat
+        boolean replicateFirst = false;
+        int first = -1;
+
+        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLE_FORMAT);
+        sampleFormat = new int[samplesPerPixel];
+        replicateFirst = false;
+        if (f == null) {
+            replicateFirst = true;
+            first = BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED;
+        } else if (f.getCount() != samplesPerPixel) {
+            replicateFirst = true;
+            first = f.getAsInt(0);
+        }
+
+        for (int i = 0; i < samplesPerPixel; i++) {
+            sampleFormat[i] = replicateFirst ? first : f.getAsInt(i);
+            if (sampleFormat[i]
+                    != BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER
+                    && sampleFormat[i]
+                    != BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER
+                    && sampleFormat[i]
+                    != BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT
+                    && sampleFormat[i]
+                    != BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED) {
+                processWarningOccurred(
+                        "Illegal value for SAMPLE_FORMAT, assuming SAMPLE_FORMAT_UNDEFINED");
+                sampleFormat[i] = BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED;
+            }
+        }
+
+        // BitsPerSample
+        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
+        this.bitsPerSample = new int[samplesPerPixel];
+        replicateFirst = false;
+        if (f == null) {
+            replicateFirst = true;
+            first = defaultBitDepth;
+        } else if (f.getCount() != samplesPerPixel) {
+            replicateFirst = true;
+            first = f.getAsInt(0);
+        }
+
+        for (int i = 0; i < samplesPerPixel; i++) {
+            // Replicate initial value if not enough values provided
+            bitsPerSample[i] = replicateFirst ? first : f.getAsInt(i);
+            if (bitsPerSample[i] > BITS_PER_SAMPLE_MAX) {
+                throw new IIOException
+                    ("Bits per sample (" + bitsPerSample[i]
+                    + ") greater than allowed maximum ("
+                    + BITS_PER_SAMPLE_MAX + ")");
+            }
+        }
+
+        // ExtraSamples
+        this.extraSamples = null;
+        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES);
+        if (f != null) {
+            extraSamples = f.getAsInts();
+        }
+    }
+
+    public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IIOException {
+        List<ImageTypeSpecifier> l; // List of ImageTypeSpecifiers
+
+        Integer imageIndexInteger = Integer.valueOf(imageIndex);
+        if (imageTypeMap.containsKey(imageIndexInteger)) {
+            // Return the cached ITS List.
+            l = imageTypeMap.get(imageIndexInteger);
+        } else {
+            // Create a new ITS List.
+            l = new ArrayList<ImageTypeSpecifier>(1);
+
+            // Create the ITS and cache if for later use so that this method
+            // always returns an Iterator containing the same ITS objects.
+            seekToImage(imageIndex);
+            ImageTypeSpecifier itsRaw
+                    = TIFFDecompressor.getRawImageTypeSpecifier(photometricInterpretation,
+                            compression,
+                            samplesPerPixel,
+                            bitsPerSample,
+                            sampleFormat,
+                            extraSamples,
+                            colorMap);
+
+            // Check for an ICCProfile field.
+            TIFFField iccProfileField
+                    = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_ICC_PROFILE);
+
+            // If an ICCProfile field is present change the ImageTypeSpecifier
+            // to use it if the data layout is component type.
+            if (iccProfileField != null
+                    && itsRaw.getColorModel() instanceof ComponentColorModel) {
+                // Create a ColorSpace from the profile.
+                byte[] iccProfileValue = iccProfileField.getAsBytes();
+                ICC_Profile iccProfile
+                        = ICC_Profile.getInstance(iccProfileValue);
+                ICC_ColorSpace iccColorSpace
+                        = new ICC_ColorSpace(iccProfile);
+
+                // Get the raw sample and color information.
+                ColorModel cmRaw = itsRaw.getColorModel();
+                ColorSpace csRaw = cmRaw.getColorSpace();
+                SampleModel smRaw = itsRaw.getSampleModel();
+
+                // Get the number of samples per pixel and the number
+                // of color components.
+                int numBands = smRaw.getNumBands();
+                int numComponents = iccColorSpace.getNumComponents();
+
+                // Replace the ColorModel with the ICC ColorModel if the
+                // numbers of samples and color components are amenable.
+                if (numBands == numComponents
+                        || numBands == numComponents + 1) {
+                    // Set alpha flags.
+                    boolean hasAlpha = numComponents != numBands;
+                    boolean isAlphaPre
+                            = hasAlpha && cmRaw.isAlphaPremultiplied();
+
+                    // Create a ColorModel of the same class and with
+                    // the same transfer type.
+                    ColorModel iccColorModel
+                            = new ComponentColorModel(iccColorSpace,
+                                    cmRaw.getComponentSize(),
+                                    hasAlpha,
+                                    isAlphaPre,
+                                    cmRaw.getTransparency(),
+                                    cmRaw.getTransferType());
+
+                    // Prepend the ICC profile-based ITS to the List. The
+                    // ColorModel and SampleModel are guaranteed to be
+                    // compatible as the old and new ColorModels are both
+                    // ComponentColorModels with the same transfer type
+                    // and the same number of components.
+                    l.add(new ImageTypeSpecifier(iccColorModel, smRaw));
+
+                    // Append the raw ITS to the List if and only if its
+                    // ColorSpace has the same type and number of components
+                    // as the ICC ColorSpace.
+                    if (csRaw.getType() == iccColorSpace.getType()
+                            && csRaw.getNumComponents()
+                            == iccColorSpace.getNumComponents()) {
+                        l.add(itsRaw);
+                    }
+                } else { // ICCProfile not compatible with SampleModel.
+                    // Append the raw ITS to the List.
+                    l.add(itsRaw);
+                }
+            } else { // No ICCProfile field or raw ColorModel not component.
+                // Append the raw ITS to the List.
+                l.add(itsRaw);
+            }
+
+            // Cache the ITS List.
+            imageTypeMap.put(imageIndexInteger, l);
+        }
+
+        return l.iterator();
+    }
+
+    public IIOMetadata getImageMetadata(int imageIndex) throws IIOException {
+        seekToImage(imageIndex);
+        TIFFImageMetadata im
+                = new TIFFImageMetadata(imageMetadata.getRootIFD().getTagSetList());
+        Node root
+                = imageMetadata.getAsTree(TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME);
+        im.setFromTree(TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME, root);
+        return im;
+    }
+
+    public IIOMetadata getStreamMetadata(int imageIndex) throws IIOException {
+        readHeader();
+        TIFFStreamMetadata sm = new TIFFStreamMetadata();
+        Node root = sm.getAsTree(TIFFStreamMetadata.NATIVE_METADATA_FORMAT_NAME);
+        sm.setFromTree(TIFFStreamMetadata.NATIVE_METADATA_FORMAT_NAME, root);
+        return sm;
+    }
+
+    public boolean isRandomAccessEasy(int imageIndex) throws IOException {
+        if (currIndex != -1) {
+            seekToImage(currIndex);
+            return getCompression() == BaselineTIFFTagSet.COMPRESSION_NONE;
+        } else {
+            return false;
+        }
+    }
+
+    // Thumbnails
+    public boolean readSupportsThumbnails() {
+        return false;
+    }
+
+    public boolean hasThumbnails(int imageIndex) {
+        return false;
+    }
+
+    public int getNumThumbnails(int imageIndex) throws IOException {
+        return 0;
+    }
+
+    public ImageReadParam getDefaultReadParam() {
+        return new TIFFImageReadParam();
+    }
+
+    public boolean isImageTiled(int imageIndex) throws IOException {
+        seekToImage(imageIndex);
+
+        TIFFField f
+                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH);
+        return f != null;
+    }
+
+    public int getTileWidth(int imageIndex) throws IOException {
+        seekToImage(imageIndex);
+        return getTileOrStripWidth();
+    }
+
+    public int getTileHeight(int imageIndex) throws IOException {
+        seekToImage(imageIndex);
+        return getTileOrStripHeight();
+    }
+
+    public BufferedImage readTile(int imageIndex, int tileX, int tileY)
+            throws IOException {
+
+        int w = getWidth(imageIndex);
+        int h = getHeight(imageIndex);
+        int tw = getTileWidth(imageIndex);
+        int th = getTileHeight(imageIndex);
+
+        int x = tw * tileX;
+        int y = th * tileY;
+
+        if (tileX < 0 || tileY < 0 || x >= w || y >= h) {
+            throw new IllegalArgumentException("Tile indices are out of bounds!");
+        }
+
+        if (x + tw > w) {
+            tw = w - x;
+        }
+
+        if (y + th > h) {
+            th = h - y;
+        }
+
+        ImageReadParam param = getDefaultReadParam();
+        Rectangle tileRect = new Rectangle(x, y, tw, th);
+        param.setSourceRegion(tileRect);
+
+        return read(imageIndex, param);
+    }
+
+    public boolean canReadRaster() {
+        return false;
+    }
+
+    public Raster readRaster(int imageIndex, ImageReadParam param)
+            throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    private int[] sourceBands;
+    private int[] destinationBands;
+
+    private TIFFDecompressor decompressor;
+
+    // floor(num/den)
+    private static int ifloor(int num, int den) {
+        if (num < 0) {
+            num -= den - 1;
+        }
+        return num / den;
+    }
+
+    // ceil(num/den)
+    private static int iceil(int num, int den) {
+        if (num > 0) {
+            num += den - 1;
+        }
+        return num / den;
+    }
+
+    private void prepareRead(int imageIndex, ImageReadParam param)
+            throws IOException {
+        if (stream == null) {
+            throw new IllegalStateException("Input not set!");
+        }
+
+        // A null ImageReadParam means we use the default
+        if (param == null) {
+            param = getDefaultReadParam();
+        }
+
+        this.imageReadParam = param;
+
+        seekToImage(imageIndex);
+
+        this.tileOrStripWidth = getTileOrStripWidth();
+        this.tileOrStripHeight = getTileOrStripHeight();
+        this.planarConfiguration = getPlanarConfiguration();
+
+        this.sourceBands = param.getSourceBands();
+        if (sourceBands == null) {
+            sourceBands = new int[numBands];
+            for (int i = 0; i < numBands; i++) {
+                sourceBands[i] = i;
+            }
+        }
+
+        // Initialize the destination image
+        Iterator<ImageTypeSpecifier> imageTypes = getImageTypes(imageIndex);
+        ImageTypeSpecifier theImageType
+                = ImageUtil.getDestinationType(param, imageTypes);
+
+        int destNumBands = theImageType.getSampleModel().getNumBands();
+
+        this.destinationBands = param.getDestinationBands();
+        if (destinationBands == null) {
+            destinationBands = new int[destNumBands];
+            for (int i = 0; i < destNumBands; i++) {
+                destinationBands[i] = i;
+            }
+        }
+
+        if (sourceBands.length != destinationBands.length) {
+            throw new IllegalArgumentException(
+                    "sourceBands.length != destinationBands.length");
+        }
+
+        for (int i = 0; i < sourceBands.length; i++) {
+            int sb = sourceBands[i];
+            if (sb < 0 || sb >= numBands) {
+                throw new IllegalArgumentException(
+                        "Source band out of range!");
+            }
+            int db = destinationBands[i];
+            if (db < 0 || db >= destNumBands) {
+                throw new IllegalArgumentException(
+                        "Destination band out of range!");
+            }
+        }
+    }
+
+    public RenderedImage readAsRenderedImage(int imageIndex,
+            ImageReadParam param)
+            throws IOException {
+        prepareRead(imageIndex, param);
+        return new TIFFRenderedImage(this, imageIndex, imageReadParam,
+                width, height);
+    }
+
+    private void decodeTile(int ti, int tj, int band) throws IOException {
+        // Compute the region covered by the strip or tile
+        Rectangle tileRect = new Rectangle(ti * tileOrStripWidth,
+                tj * tileOrStripHeight,
+                tileOrStripWidth,
+                tileOrStripHeight);
+
+        // Clip against the image bounds if the image is not tiled. If it
+        // is tiled, the tile may legally extend beyond the image bounds.
+        if (!isImageTiled(currIndex)) {
+            tileRect
+                    = tileRect.intersection(new Rectangle(0, 0, width, height));
+        }
+
+        // Return if the intersection is empty.
+        if (tileRect.width <= 0 || tileRect.height <= 0) {
+            return;
+        }
+
+        int srcMinX = tileRect.x;
+        int srcMinY = tileRect.y;
+        int srcWidth = tileRect.width;
+        int srcHeight = tileRect.height;
+
+        // Determine dest region that can be derived from the
+        // source region
+        dstMinX = iceil(srcMinX - sourceXOffset, srcXSubsampling);
+        int dstMaxX = ifloor(srcMinX + srcWidth - 1 - sourceXOffset,
+                srcXSubsampling);
+
+        dstMinY = iceil(srcMinY - sourceYOffset, srcYSubsampling);
+        int dstMaxY = ifloor(srcMinY + srcHeight - 1 - sourceYOffset,
+                srcYSubsampling);
+
+        dstWidth = dstMaxX - dstMinX + 1;
+        dstHeight = dstMaxY - dstMinY + 1;
+
+        dstMinX += dstXOffset;
+        dstMinY += dstYOffset;
+
+        // Clip against image bounds
+        Rectangle dstRect = new Rectangle(dstMinX, dstMinY,
+                dstWidth, dstHeight);
+        dstRect
+                = dstRect.intersection(theImage.getRaster().getBounds());
+
+        dstMinX = dstRect.x;
+        dstMinY = dstRect.y;
+        dstWidth = dstRect.width;
+        dstHeight = dstRect.height;
+
+        if (dstWidth <= 0 || dstHeight <= 0) {
+            return;
+        }
+
+        // Backwards map dest region to source to determine
+        // active source region
+        int activeSrcMinX = (dstMinX - dstXOffset) * srcXSubsampling
+                + sourceXOffset;
+        int sxmax
+                = (dstMinX + dstWidth - 1 - dstXOffset) * srcXSubsampling
+                + sourceXOffset;
+        int activeSrcWidth = sxmax - activeSrcMinX + 1;
+
+        int activeSrcMinY = (dstMinY - dstYOffset) * srcYSubsampling
+                + sourceYOffset;
+        int symax
+                = (dstMinY + dstHeight - 1 - dstYOffset) * srcYSubsampling
+                + sourceYOffset;
+        int activeSrcHeight = symax - activeSrcMinY + 1;
+
+        decompressor.setSrcMinX(srcMinX);
+        decompressor.setSrcMinY(srcMinY);
+        decompressor.setSrcWidth(srcWidth);
+        decompressor.setSrcHeight(srcHeight);
+
+        decompressor.setDstMinX(dstMinX);
+        decompressor.setDstMinY(dstMinY);
+        decompressor.setDstWidth(dstWidth);
+        decompressor.setDstHeight(dstHeight);
+
+        decompressor.setActiveSrcMinX(activeSrcMinX);
+        decompressor.setActiveSrcMinY(activeSrcMinY);
+        decompressor.setActiveSrcWidth(activeSrcWidth);
+        decompressor.setActiveSrcHeight(activeSrcHeight);
+
+        int tileIndex = tj * tilesAcross + ti;
+
+        if (planarConfiguration
+                == BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) {
+            tileIndex += band * tilesAcross * tilesDown;
+        }
+
+        long offset = getTileOrStripOffset(tileIndex);
+        long byteCount = getTileOrStripByteCount(tileIndex);
+
+        decompressor.setStream(stream);
+        decompressor.setOffset(offset);
+        decompressor.setByteCount((int) byteCount);
+
+        decompressor.beginDecoding();
+
+        stream.mark();
+        decompressor.decode();
+        stream.reset();
+    }
+
+    private void reportProgress() {
+        // Report image progress/update to listeners after each tile
+        pixelsRead += dstWidth * dstHeight;
+        processImageProgress(100.0f * pixelsRead / pixelsToRead);
+        processImageUpdate(theImage,
+                dstMinX, dstMinY, dstWidth, dstHeight,
+                1, 1,
+                destinationBands);
+    }
+
+    public BufferedImage read(int imageIndex, ImageReadParam param)
+            throws IOException {
+        prepareRead(imageIndex, param);
+        this.theImage = getDestination(param,
+                getImageTypes(imageIndex),
+                width, height);
+
+        srcXSubsampling = imageReadParam.getSourceXSubsampling();
+        srcYSubsampling = imageReadParam.getSourceYSubsampling();
+
+        Point p = imageReadParam.getDestinationOffset();
+        dstXOffset = p.x;
+        dstYOffset = p.y;
+
+        // This could probably be made more efficient...
+        Rectangle srcRegion = new Rectangle(0, 0, 0, 0);
+        Rectangle destRegion = new Rectangle(0, 0, 0, 0);
+
+        computeRegions(imageReadParam, width, height, theImage,
+                srcRegion, destRegion);
+
+        // Initial source pixel, taking source region and source
+        // subsamplimg offsets into account
+        sourceXOffset = srcRegion.x;
+        sourceYOffset = srcRegion.y;
+
+        pixelsToRead = destRegion.width * destRegion.height;
+        pixelsRead = 0;
+
+        processImageStarted(imageIndex);
+        processImageProgress(0.0f);
+
+        tilesAcross = (width + tileOrStripWidth - 1) / tileOrStripWidth;
+        tilesDown = (height + tileOrStripHeight - 1) / tileOrStripHeight;
+
+        int compression = getCompression();
+
+        // Set the decompressor
+        if (compression == BaselineTIFFTagSet.COMPRESSION_NONE) {
+            // Get the fillOrder field.
+            TIFFField fillOrderField
+                    = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_FILL_ORDER);
+
+            // Set the decompressor based on the fill order.
+            if (fillOrderField != null && fillOrderField.getAsInt(0) == 2) {
+                this.decompressor = new TIFFLSBDecompressor();
+            } else {
+                this.decompressor = new TIFFNullDecompressor();
+            }
+        } else if (compression
+                == BaselineTIFFTagSet.COMPRESSION_CCITT_T_6) {
+            this.decompressor = new TIFFFaxDecompressor();
+        } else if (compression
+                == BaselineTIFFTagSet.COMPRESSION_CCITT_T_4) {
+            this.decompressor = new TIFFFaxDecompressor();
+        } else if (compression
+                == BaselineTIFFTagSet.COMPRESSION_CCITT_RLE) {
+            this.decompressor = new TIFFFaxDecompressor();
+        } else if (compression
+                == BaselineTIFFTagSet.COMPRESSION_PACKBITS) {
+            this.decompressor = new TIFFPackBitsDecompressor();
+        } else if (compression
+                == BaselineTIFFTagSet.COMPRESSION_LZW) {
+            TIFFField predictorField
+                    = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PREDICTOR);
+            int predictor = ((predictorField == null)
+                    ? BaselineTIFFTagSet.PREDICTOR_NONE
+                    : predictorField.getAsInt(0));
+            this.decompressor = new TIFFLZWDecompressor(predictor);
+        } else if (compression
+                == BaselineTIFFTagSet.COMPRESSION_JPEG) {
+            this.decompressor = new TIFFJPEGDecompressor();
+        } else if (compression
+                == BaselineTIFFTagSet.COMPRESSION_ZLIB
+                || compression
+                == BaselineTIFFTagSet.COMPRESSION_DEFLATE) {
+            TIFFField predictorField
+                    = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PREDICTOR);
+            int predictor = ((predictorField == null)
+                    ? BaselineTIFFTagSet.PREDICTOR_NONE
+                    : predictorField.getAsInt(0));
+            this.decompressor = new TIFFDeflateDecompressor(predictor);
+        } else if (compression
+                == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
+            TIFFField JPEGProcField
+                    = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_PROC);
+            if (JPEGProcField == null) {
+                processWarningOccurred("JPEGProc field missing; assuming baseline sequential JPEG process.");
+            } else if (JPEGProcField.getAsInt(0)
+                    != BaselineTIFFTagSet.JPEG_PROC_BASELINE) {
+                throw new IIOException("Old-style JPEG supported for baseline sequential JPEG process only!");
+            }
+            this.decompressor = new TIFFOldJPEGDecompressor();
+            //throw new IIOException("Old-style JPEG not supported!");
+        } else {
+            throw new IIOException("Unsupported compression type (tag value = "
+                    + compression + ")!");
+        }
+
+        if (photometricInterpretation
+                == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR
+                && compression != BaselineTIFFTagSet.COMPRESSION_JPEG
+                && compression != BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
+            boolean convertYCbCrToRGB
+                    = theImage.getColorModel().getColorSpace().getType()
+                    == ColorSpace.TYPE_RGB;
+            TIFFDecompressor wrappedDecompressor
+                    = this.decompressor instanceof TIFFNullDecompressor
+                            ? null : this.decompressor;
+            this.decompressor
+                    = new TIFFYCbCrDecompressor(wrappedDecompressor,
+                            convertYCbCrToRGB);
+        }
+
+        TIFFColorConverter colorConverter = null;
+        if (photometricInterpretation
+                == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CIELAB
+                && theImage.getColorModel().getColorSpace().getType()
+                == ColorSpace.TYPE_RGB) {
+            colorConverter = new TIFFCIELabColorConverter();
+        } else if (photometricInterpretation
+                == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR
+                && !(this.decompressor instanceof TIFFYCbCrDecompressor)
+                && compression != BaselineTIFFTagSet.COMPRESSION_JPEG
+                && compression != BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
+            colorConverter = new TIFFYCbCrColorConverter(imageMetadata);
+        }
+
+        decompressor.setReader(this);
+        decompressor.setMetadata(imageMetadata);
+        decompressor.setImage(theImage);
+
+        decompressor.setPhotometricInterpretation(photometricInterpretation);
+        decompressor.setCompression(compression);
+        decompressor.setSamplesPerPixel(samplesPerPixel);
+        decompressor.setBitsPerSample(bitsPerSample);
+        decompressor.setSampleFormat(sampleFormat);
+        decompressor.setExtraSamples(extraSamples);
+        decompressor.setColorMap(colorMap);
+
+        decompressor.setColorConverter(colorConverter);
+
+        decompressor.setSourceXOffset(sourceXOffset);
+        decompressor.setSourceYOffset(sourceYOffset);
+        decompressor.setSubsampleX(srcXSubsampling);
+        decompressor.setSubsampleY(srcYSubsampling);
+
+        decompressor.setDstXOffset(dstXOffset);
+        decompressor.setDstYOffset(dstYOffset);
+
+        decompressor.setSourceBands(sourceBands);
+        decompressor.setDestinationBands(destinationBands);
+
+        // Compute bounds on the tile indices for this source region.
+        int minTileX
+                = TIFFImageWriter.XToTileX(srcRegion.x, 0, tileOrStripWidth);
+        int minTileY
+                = TIFFImageWriter.YToTileY(srcRegion.y, 0, tileOrStripHeight);
+        int maxTileX
+                = TIFFImageWriter.XToTileX(srcRegion.x + srcRegion.width - 1,
+                        0, tileOrStripWidth);
+        int maxTileY
+                = TIFFImageWriter.YToTileY(srcRegion.y + srcRegion.height - 1,
+                        0, tileOrStripHeight);
+
+        if (planarConfiguration
+                == BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) {
+
+            decompressor.setPlanar(true);
+
+            int[] sb = new int[1];
+            int[] db = new int[1];
+            for (int tj = minTileY; tj <= maxTileY; tj++) {
+                for (int ti = minTileX; ti <= maxTileX; ti++) {
+                    for (int band = 0; band < numBands; band++) {
+                        sb[0] = sourceBands[band];
+                        decompressor.setSourceBands(sb);
+                        db[0] = destinationBands[band];
+                        decompressor.setDestinationBands(db);
+
+                        decodeTile(ti, tj, band);
+                    }
+
+                    reportProgress();
+                }
+            }
+        } else {
+            for (int tj = minTileY; tj <= maxTileY; tj++) {
+                for (int ti = minTileX; ti <= maxTileX; ti++) {
+                    decodeTile(ti, tj, -1);
+
+                    reportProgress();
+                }
+            }
+        }
+
+        if (abortRequested()) {
+            processReadAborted();
+        } else {
+            processImageComplete();
+        }
+
+        return theImage;
+    }
+
+    public void reset() {
+        super.reset();
+        resetLocal();
+    }
+
+    protected void resetLocal() {
+        stream = null;
+        gotHeader = false;
+        imageReadParam = getDefaultReadParam();
+        streamMetadata = null;
+        currIndex = -1;
+        imageMetadata = null;
+        imageStartPosition = new ArrayList<Long>();
+        numImages = -1;
+        imageTypeMap = new HashMap<Integer, List<ImageTypeSpecifier>>();
+        width = -1;
+        height = -1;
+        numBands = -1;
+        tileOrStripWidth = -1;
+        tileOrStripHeight = -1;
+        planarConfiguration = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
+    }
+
+    /**
+     * Package scope method to allow decompressors, for example, to emit warning
+     * messages.
+     */
+    void forwardWarningMessage(String warning) {
+        processWarningOccurred(warning);
+    }
+}