1/*
2 * Copyright (c) 2005, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25package com.sun.imageio.plugins.tiff;
26
27import java.awt.Point;
28import java.awt.Rectangle;
29import java.awt.color.ColorSpace;
30import java.awt.color.ICC_ColorSpace;
31import java.awt.color.ICC_Profile;
32import java.awt.image.BufferedImage;
33import java.awt.image.ColorModel;
34import java.awt.image.ComponentColorModel;
35import java.awt.image.Raster;
36import java.awt.image.RenderedImage;
37import java.awt.image.SampleModel;
38import java.io.EOFException;
39import java.io.IOException;
40import java.nio.ByteOrder;
41import java.util.ArrayList;
42import java.util.HashMap;
43import java.util.Iterator;
44import java.util.List;
45import javax.imageio.IIOException;
46import javax.imageio.ImageIO;
47import javax.imageio.ImageReader;
48import javax.imageio.ImageReadParam;
49import javax.imageio.ImageTypeSpecifier;
50import javax.imageio.metadata.IIOMetadata;
51import javax.imageio.spi.ImageReaderSpi;
52import javax.imageio.stream.ImageInputStream;
53import org.w3c.dom.Node;
54import com.sun.imageio.plugins.common.ImageUtil;
55import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
56import javax.imageio.plugins.tiff.TIFFField;
57import javax.imageio.plugins.tiff.TIFFImageReadParam;
58import javax.imageio.plugins.tiff.TIFFTagSet;
59
60public class TIFFImageReader extends ImageReader {
61
62    // A somewhat arbitrary upper bound on SamplesPerPixel. Hyperspectral
63    // images as of this writing appear to be under 300 bands so this should
64    // account for those cases should they arise.
65    private static final int SAMPLES_PER_PIXEL_MAX = 1024;
66
67    // In baseline TIFF the largest data types are 64-bit long and double.
68    private static final int BITS_PER_SAMPLE_MAX = 64;
69
70    // The current ImageInputStream source.
71    private ImageInputStream stream = null;
72
73    // True if the file header has been read.
74    private boolean gotHeader = false;
75
76    private ImageReadParam imageReadParam = getDefaultReadParam();
77
78    // Stream metadata, or null.
79    private TIFFStreamMetadata streamMetadata = null;
80
81    // The current image index.
82    private int currIndex = -1;
83
84    // Metadata for image at 'currIndex', or null.
85    private TIFFImageMetadata imageMetadata = null;
86
87    // A {@code List} of {@code Long}s indicating the stream
88    // positions of the start of the IFD for each image.  Entries
89    // are added as needed.
90    private List<Long> imageStartPosition = new ArrayList<Long>();
91
92    // The number of images in the stream, if known, otherwise -1.
93    private int numImages = -1;
94
95    // The ImageTypeSpecifiers of the images in the stream.
96    // Contains a map of Integers to Lists.
97    private HashMap<Integer, List<ImageTypeSpecifier>> imageTypeMap
98            = new HashMap<Integer, List<ImageTypeSpecifier>>();
99
100    private BufferedImage theImage = null;
101
102    private int width = -1;
103    private int height = -1;
104    private int numBands = -1;
105    private int tileOrStripWidth = -1, tileOrStripHeight = -1;
106
107    private int planarConfiguration = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
108
109    private int compression;
110    private int photometricInterpretation;
111    private int samplesPerPixel;
112    private int[] sampleFormat;
113    private int[] bitsPerSample;
114    private int[] extraSamples;
115    private char[] colorMap;
116
117    private int sourceXOffset;
118    private int sourceYOffset;
119    private int srcXSubsampling;
120    private int srcYSubsampling;
121
122    private int dstWidth;
123    private int dstHeight;
124    private int dstMinX;
125    private int dstMinY;
126    private int dstXOffset;
127    private int dstYOffset;
128
129    private int tilesAcross;
130    private int tilesDown;
131
132    private int pixelsRead;
133    private int pixelsToRead;
134
135    public TIFFImageReader(ImageReaderSpi originatingProvider) {
136        super(originatingProvider);
137    }
138
139    public void setInput(Object input,
140            boolean seekForwardOnly,
141            boolean ignoreMetadata) {
142        super.setInput(input, seekForwardOnly, ignoreMetadata);
143
144        // Clear all local values based on the previous stream contents.
145        resetLocal();
146
147        if (input != null) {
148            if (!(input instanceof ImageInputStream)) {
149                throw new IllegalArgumentException("input not an ImageInputStream!");
150            }
151            this.stream = (ImageInputStream) input;
152        } else {
153            this.stream = null;
154        }
155    }
156
157    // Do not seek to the beginning of the stream so as to allow users to
158    // point us at an IFD within some other file format
159    private void readHeader() throws IIOException {
160        if (gotHeader) {
161            return;
162        }
163        if (stream == null) {
164            throw new IllegalStateException("Input not set!");
165        }
166
167        // Create an object to store the stream metadata
168        this.streamMetadata = new TIFFStreamMetadata();
169
170        try {
171            int byteOrder = stream.readUnsignedShort();
172            if (byteOrder == 0x4d4d) {
173                streamMetadata.byteOrder = ByteOrder.BIG_ENDIAN;
174                stream.setByteOrder(ByteOrder.BIG_ENDIAN);
175            } else if (byteOrder == 0x4949) {
176                streamMetadata.byteOrder = ByteOrder.LITTLE_ENDIAN;
177                stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
178            } else {
179                processWarningOccurred(
180                        "Bad byte order in header, assuming little-endian");
181                streamMetadata.byteOrder = ByteOrder.LITTLE_ENDIAN;
182                stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
183            }
184
185            int magic = stream.readUnsignedShort();
186            if (magic != 42) {
187                processWarningOccurred(
188                        "Bad magic number in header, continuing");
189            }
190
191            // Seek to start of first IFD
192            long offset = stream.readUnsignedInt();
193            stream.seek(offset);
194            imageStartPosition.add(Long.valueOf(offset));
195        } catch (IOException e) {
196            throw new IIOException("I/O error reading header!", e);
197        }
198
199        gotHeader = true;
200    }
201
202    private int locateImage(int imageIndex) throws IIOException {
203        readHeader();
204
205        // Find closest known index
206        int index = Math.min(imageIndex, imageStartPosition.size() - 1);
207
208        try {
209            // Seek to that position
210            Long l = imageStartPosition.get(index);
211            stream.seek(l.longValue());
212
213            // Skip IFDs until at desired index or last image found
214            while (index < imageIndex) {
215                int count = stream.readUnsignedShort();
216                // If zero-entry IFD, decrement the index and exit the loop
217                if (count == 0) {
218                    imageIndex = index > 0 ? index - 1 : 0;
219                    break;
220                }
221                stream.skipBytes(12 * count);
222
223                long offset = stream.readUnsignedInt();
224                if (offset == 0) {
225                    return index;
226                }
227
228                stream.seek(offset);
229                imageStartPosition.add(Long.valueOf(offset));
230                ++index;
231            }
232        } catch (EOFException eofe) {
233            forwardWarningMessage("Ignored " + eofe);
234
235            // Ran off the end of stream: decrement index
236            imageIndex = index > 0 ? index - 1 : 0;
237        } catch (IOException ioe) {
238            throw new IIOException("Couldn't seek!", ioe);
239        }
240
241        if (currIndex != imageIndex) {
242            imageMetadata = null;
243        }
244        currIndex = imageIndex;
245        return imageIndex;
246    }
247
248    public int getNumImages(boolean allowSearch) throws IOException {
249        if (stream == null) {
250            throw new IllegalStateException("Input not set!");
251        }
252        if (seekForwardOnly && allowSearch) {
253            throw new IllegalStateException("seekForwardOnly and allowSearch can't both be true!");
254        }
255
256        if (numImages > 0) {
257            return numImages;
258        }
259        if (allowSearch) {
260            this.numImages = locateImage(Integer.MAX_VALUE) + 1;
261        }
262        return numImages;
263    }
264
265    public IIOMetadata getStreamMetadata() throws IIOException {
266        readHeader();
267        return streamMetadata;
268    }
269
270    // Throw an IndexOutOfBoundsException if index < minIndex,
271    // and bump minIndex if required.
272    private void checkIndex(int imageIndex) {
273        if (imageIndex < minIndex) {
274            throw new IndexOutOfBoundsException("imageIndex < minIndex!");
275        }
276        if (seekForwardOnly) {
277            minIndex = imageIndex;
278        }
279    }
280
281    // Verify that imageIndex is in bounds, find the image IFD, read the
282    // image metadata, initialize instance variables from the metadata.
283    private void seekToImage(int imageIndex) throws IIOException {
284        checkIndex(imageIndex);
285
286        int index = locateImage(imageIndex);
287        if (index != imageIndex) {
288            throw new IndexOutOfBoundsException("imageIndex out of bounds!");
289        }
290
291        readMetadata();
292
293        initializeFromMetadata();
294    }
295
296    // Stream must be positioned at start of IFD for 'currIndex'
297    private void readMetadata() throws IIOException {
298        if (stream == null) {
299            throw new IllegalStateException("Input not set!");
300        }
301
302        if (imageMetadata != null) {
303            return;
304        }
305        try {
306            // Create an object to store the image metadata
307            List<TIFFTagSet> tagSets;
308            boolean readUnknownTags = false;
309            if (imageReadParam instanceof TIFFImageReadParam) {
310                TIFFImageReadParam tp = (TIFFImageReadParam)imageReadParam;
311                tagSets = tp.getAllowedTagSets();
312                readUnknownTags = tp.getReadUnknownTags();
313            } else {
314                tagSets = new ArrayList<TIFFTagSet>(1);
315                tagSets.add(BaselineTIFFTagSet.getInstance());
316            }
317
318            this.imageMetadata = new TIFFImageMetadata(tagSets);
319            imageMetadata.initializeFromStream(stream, ignoreMetadata,
320                                               readUnknownTags);
321        } catch (IIOException iioe) {
322            throw iioe;
323        } catch (IOException ioe) {
324            throw new IIOException("I/O error reading image metadata!", ioe);
325        }
326    }
327
328    private int getWidth() {
329        return this.width;
330    }
331
332    private int getHeight() {
333        return this.height;
334    }
335
336    // Returns tile width if image is tiled, else image width
337    private int getTileOrStripWidth() {
338        TIFFField f
339                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH);
340        return (f == null) ? getWidth() : f.getAsInt(0);
341    }
342
343    // Returns tile height if image is tiled, else strip height
344    private int getTileOrStripHeight() {
345        TIFFField f
346                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_LENGTH);
347        if (f != null) {
348            return f.getAsInt(0);
349        }
350
351        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP);
352        // Default for ROWS_PER_STRIP is 2^32 - 1, i.e., infinity
353        int h = (f == null) ? -1 : f.getAsInt(0);
354        return (h == -1) ? getHeight() : h;
355    }
356
357    private int getPlanarConfiguration() {
358        TIFFField f
359                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION);
360        if (f != null) {
361            int planarConfigurationValue = f.getAsInt(0);
362            if (planarConfigurationValue
363                    == BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) {
364                // Some writers (e.g. Kofax standard Multi-Page TIFF
365                // Storage Filter v2.01.000; cf. bug 4929147) do not
366                // correctly set the value of this field. Attempt to
367                // ascertain whether the value is correctly Planar.
368                if (getCompression()
369                        == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG
370                        && imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT)
371                        != null) {
372                    // JPEG interchange format cannot have
373                    // PlanarConfiguration value Chunky so reset.
374                    processWarningOccurred("PlanarConfiguration \"Planar\" value inconsistent with JPEGInterchangeFormat; resetting to \"Chunky\".");
375                    planarConfigurationValue
376                            = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
377                } else {
378                    TIFFField offsetField
379                            = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS);
380                    if (offsetField == null) {
381                        // Tiles
382                        offsetField
383                                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS);
384                        int tw = getTileOrStripWidth();
385                        int th = getTileOrStripHeight();
386                        int tAcross = (getWidth() + tw - 1) / tw;
387                        int tDown = (getHeight() + th - 1) / th;
388                        int tilesPerImage = tAcross * tDown;
389                        long[] offsetArray = offsetField.getAsLongs();
390                        if (offsetArray != null
391                                && offsetArray.length == tilesPerImage) {
392                            // Length of offsets array is
393                            // TilesPerImage for Chunky and
394                            // SamplesPerPixel*TilesPerImage for Planar.
395                            processWarningOccurred("PlanarConfiguration \"Planar\" value inconsistent with TileOffsets field value count; resetting to \"Chunky\".");
396                            planarConfigurationValue
397                                    = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
398                        }
399                    } else {
400                        // Strips
401                        int rowsPerStrip = getTileOrStripHeight();
402                        int stripsPerImage
403                                = (getHeight() + rowsPerStrip - 1) / rowsPerStrip;
404                        long[] offsetArray = offsetField.getAsLongs();
405                        if (offsetArray != null
406                                && offsetArray.length == stripsPerImage) {
407                            // Length of offsets array is
408                            // StripsPerImage for Chunky and
409                            // SamplesPerPixel*StripsPerImage for Planar.
410                            processWarningOccurred("PlanarConfiguration \"Planar\" value inconsistent with StripOffsets field value count; resetting to \"Chunky\".");
411                            planarConfigurationValue
412                                    = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
413                        }
414                    }
415                }
416            }
417            return planarConfigurationValue;
418        }
419
420        return BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
421    }
422
423    private long getTileOrStripOffset(int tileIndex) throws IIOException {
424        TIFFField f
425                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS);
426        if (f == null) {
427            f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS);
428        }
429        if (f == null) {
430            f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT);
431        }
432
433        if (f == null) {
434            throw new IIOException("Missing required strip or tile offsets field.");
435        }
436
437        return f.getAsLong(tileIndex);
438    }
439
440    private long getTileOrStripByteCount(int tileIndex) throws IOException {
441        TIFFField f
442                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS);
443        if (f == null) {
444            f
445                    = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS);
446        }
447        if (f == null) {
448            f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
449        }
450
451        long tileOrStripByteCount;
452        if (f != null) {
453            tileOrStripByteCount = f.getAsLong(tileIndex);
454        } else {
455            processWarningOccurred("TIFF directory contains neither StripByteCounts nor TileByteCounts field: attempting to calculate from strip or tile width and height.");
456
457            // Initialize to number of bytes per strip or tile assuming
458            // no compression.
459            int bitsPerPixel = bitsPerSample[0];
460            for (int i = 1; i < samplesPerPixel; i++) {
461                bitsPerPixel += bitsPerSample[i];
462            }
463            int bytesPerRow = (getTileOrStripWidth() * bitsPerPixel + 7) / 8;
464            tileOrStripByteCount = bytesPerRow * getTileOrStripHeight();
465
466            // Clamp to end of stream if possible.
467            long streamLength = stream.length();
468            if (streamLength != -1) {
469                tileOrStripByteCount
470                        = Math.min(tileOrStripByteCount,
471                                streamLength - getTileOrStripOffset(tileIndex));
472            } else {
473                processWarningOccurred("Stream length is unknown: cannot clamp estimated strip or tile byte count to EOF.");
474            }
475        }
476
477        return tileOrStripByteCount;
478    }
479
480    private int getCompression() {
481        TIFFField f
482                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION);
483        if (f == null) {
484            return BaselineTIFFTagSet.COMPRESSION_NONE;
485        } else {
486            return f.getAsInt(0);
487        }
488    }
489
490    public int getWidth(int imageIndex) throws IOException {
491        seekToImage(imageIndex);
492        return getWidth();
493    }
494
495    public int getHeight(int imageIndex) throws IOException {
496        seekToImage(imageIndex);
497        return getHeight();
498    }
499
500    /**
501     * Initializes these instance variables from the image metadata:
502     * <pre>
503     * compression
504     * width
505     * height
506     * samplesPerPixel
507     * numBands
508     * colorMap
509     * photometricInterpretation
510     * sampleFormat
511     * bitsPerSample
512     * extraSamples
513     * tileOrStripWidth
514     * tileOrStripHeight
515     * </pre>
516     */
517    private void initializeFromMetadata() throws IIOException {
518        TIFFField f;
519
520        // Compression
521        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION);
522        if (f == null) {
523            processWarningOccurred("Compression field is missing; assuming no compression");
524            compression = BaselineTIFFTagSet.COMPRESSION_NONE;
525        } else {
526            compression = f.getAsInt(0);
527        }
528
529        // Whether key dimensional information is absent.
530        boolean isMissingDimension = false;
531
532        // ImageWidth -> width
533        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH);
534        if (f != null) {
535            this.width = f.getAsInt(0);
536        } else {
537            processWarningOccurred("ImageWidth field is missing.");
538            isMissingDimension = true;
539        }
540
541        // ImageLength -> height
542        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH);
543        if (f != null) {
544            this.height = f.getAsInt(0);
545        } else {
546            processWarningOccurred("ImageLength field is missing.");
547            isMissingDimension = true;
548        }
549
550        // SamplesPerPixel
551        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL);
552        if (f != null) {
553            samplesPerPixel = f.getAsInt(0);
554        } else {
555            samplesPerPixel = 1;
556            isMissingDimension = true;
557        }
558
559        // If any dimension is missing and there is a JPEG stream available
560        // get the information from it.
561        int defaultBitDepth = 1;
562        if (isMissingDimension
563                && (f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT)) != null) {
564            Iterator<ImageReader> iter = ImageIO.getImageReadersByFormatName("JPEG");
565            if (iter != null && iter.hasNext()) {
566                ImageReader jreader = iter.next();
567                try {
568                    stream.mark();
569                    stream.seek(f.getAsLong(0));
570                    jreader.setInput(stream);
571                    if (imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH) == null) {
572                        this.width = jreader.getWidth(0);
573                    }
574                    if (imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH) == null) {
575                        this.height = jreader.getHeight(0);
576                    }
577                    ImageTypeSpecifier imageType = jreader.getRawImageType(0);
578                    if (imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL) == null) {
579                        this.samplesPerPixel =
580                            imageType != null ?
581                                imageType.getSampleModel().getNumBands() : 3;
582                    }
583                    stream.reset();
584                    defaultBitDepth =
585                        imageType != null ?
586                        imageType.getColorModel().getComponentSize(0) : 8;
587                } catch (IOException e) {
588                    // Ignore it and proceed: an error will occur later.
589                }
590                jreader.dispose();
591            }
592        }
593
594        if (samplesPerPixel < 1) {
595            throw new IIOException("Samples per pixel < 1!");
596        } else if (samplesPerPixel > SAMPLES_PER_PIXEL_MAX) {
597            throw new IIOException
598                ("Samples per pixel (" + samplesPerPixel
599                + ") greater than allowed maximum ("
600                + SAMPLES_PER_PIXEL_MAX + ")");
601        }
602
603        // SamplesPerPixel -> numBands
604        numBands = samplesPerPixel;
605
606        // ColorMap
607        this.colorMap = null;
608        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_COLOR_MAP);
609        if (f != null) {
610            // Grab color map
611            colorMap = f.getAsChars();
612        }
613
614        // PhotometricInterpretation
615        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION);
616        if (f == null) {
617            if (compression == BaselineTIFFTagSet.COMPRESSION_CCITT_RLE
618                    || compression == BaselineTIFFTagSet.COMPRESSION_CCITT_T_4
619                    || compression == BaselineTIFFTagSet.COMPRESSION_CCITT_T_6) {
620                processWarningOccurred("PhotometricInterpretation field is missing; "
621                        + "assuming WhiteIsZero");
622                photometricInterpretation
623                        = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO;
624            } else if (this.colorMap != null) {
625                photometricInterpretation
626                        = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR;
627            } else if (samplesPerPixel == 3 || samplesPerPixel == 4) {
628                photometricInterpretation
629                        = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB;
630            } else {
631                processWarningOccurred("PhotometricInterpretation field is missing; "
632                        + "assuming BlackIsZero");
633                photometricInterpretation
634                        = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO;
635            }
636        } else {
637            photometricInterpretation = f.getAsInt(0);
638        }
639
640        // SampleFormat
641        boolean replicateFirst = false;
642        int first = -1;
643
644        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLE_FORMAT);
645        sampleFormat = new int[samplesPerPixel];
646        replicateFirst = false;
647        if (f == null) {
648            replicateFirst = true;
649            first = BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED;
650        } else if (f.getCount() != samplesPerPixel) {
651            replicateFirst = true;
652            first = f.getAsInt(0);
653        }
654
655        for (int i = 0; i < samplesPerPixel; i++) {
656            sampleFormat[i] = replicateFirst ? first : f.getAsInt(i);
657            if (sampleFormat[i]
658                    != BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER
659                    && sampleFormat[i]
660                    != BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER
661                    && sampleFormat[i]
662                    != BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT
663                    && sampleFormat[i]
664                    != BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED) {
665                processWarningOccurred(
666                        "Illegal value for SAMPLE_FORMAT, assuming SAMPLE_FORMAT_UNDEFINED");
667                sampleFormat[i] = BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED;
668            }
669        }
670
671        // BitsPerSample
672        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
673        this.bitsPerSample = new int[samplesPerPixel];
674        replicateFirst = false;
675        if (f == null) {
676            replicateFirst = true;
677            first = defaultBitDepth;
678        } else if (f.getCount() != samplesPerPixel) {
679            replicateFirst = true;
680            first = f.getAsInt(0);
681        }
682
683        for (int i = 0; i < samplesPerPixel; i++) {
684            // Replicate initial value if not enough values provided
685            bitsPerSample[i] = replicateFirst ? first : f.getAsInt(i);
686            if (bitsPerSample[i] > BITS_PER_SAMPLE_MAX) {
687                throw new IIOException
688                    ("Bits per sample (" + bitsPerSample[i]
689                    + ") greater than allowed maximum ("
690                    + BITS_PER_SAMPLE_MAX + ")");
691            }
692        }
693
694        // ExtraSamples
695        this.extraSamples = null;
696        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES);
697        if (f != null) {
698            extraSamples = f.getAsInts();
699        }
700    }
701
702    public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IIOException {
703        List<ImageTypeSpecifier> l; // List of ImageTypeSpecifiers
704
705        Integer imageIndexInteger = Integer.valueOf(imageIndex);
706        if (imageTypeMap.containsKey(imageIndexInteger)) {
707            // Return the cached ITS List.
708            l = imageTypeMap.get(imageIndexInteger);
709        } else {
710            // Create a new ITS List.
711            l = new ArrayList<ImageTypeSpecifier>(1);
712
713            // Create the ITS and cache if for later use so that this method
714            // always returns an Iterator containing the same ITS objects.
715            seekToImage(imageIndex);
716            ImageTypeSpecifier itsRaw
717                    = TIFFDecompressor.getRawImageTypeSpecifier(photometricInterpretation,
718                            compression,
719                            samplesPerPixel,
720                            bitsPerSample,
721                            sampleFormat,
722                            extraSamples,
723                            colorMap);
724
725            // Check for an ICCProfile field.
726            TIFFField iccProfileField
727                    = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_ICC_PROFILE);
728
729            // If an ICCProfile field is present change the ImageTypeSpecifier
730            // to use it if the data layout is component type.
731            if (iccProfileField != null
732                    && itsRaw.getColorModel() instanceof ComponentColorModel) {
733                // Get the raw sample and color information.
734                ColorModel cmRaw = itsRaw.getColorModel();
735                ColorSpace csRaw = cmRaw.getColorSpace();
736                SampleModel smRaw = itsRaw.getSampleModel();
737
738                ColorSpace iccColorSpace = null;
739                try {
740                    // Create a ColorSpace from the profile.
741                    byte[] iccProfileValue = iccProfileField.getAsBytes();
742                    ICC_Profile iccProfile
743                        = ICC_Profile.getInstance(iccProfileValue);
744                    iccColorSpace = new ICC_ColorSpace(iccProfile);
745
746                    // Workaround for JDK-8145241: test a conversion and fall
747                    // back to a standard ColorSpace if it fails. This
748                    // workaround could be removed if JDK-8145241 is fixed.
749                    float[] rgb =
750                        iccColorSpace.toRGB(new float[] {1.0F, 1.0F, 1.0F});
751                } catch (Exception iccProfileException) {
752                    processWarningOccurred("Superseding bad ICC profile: "
753                        + iccProfileException.getMessage());
754
755                    if (iccColorSpace != null) {
756                        switch (iccColorSpace.getType()) {
757                            case ColorSpace.TYPE_GRAY:
758                                iccColorSpace =
759                                    ColorSpace.getInstance(ColorSpace.CS_GRAY);
760                                break;
761                            case ColorSpace.TYPE_RGB:
762                                iccColorSpace =
763                                    ColorSpace.getInstance(ColorSpace.CS_sRGB);
764                                break;
765                            default:
766                                iccColorSpace = csRaw;
767                                break;
768                        }
769                    } else {
770                        iccColorSpace = csRaw;
771                    }
772                }
773
774                // Get the number of samples per pixel and the number
775                // of color components.
776                int numBands = smRaw.getNumBands();
777                int numComponents = iccColorSpace.getNumComponents();
778
779                // Replace the ColorModel with the ICC ColorModel if the
780                // numbers of samples and color components are amenable.
781                if (numBands == numComponents
782                        || numBands == numComponents + 1) {
783                    // Set alpha flags.
784                    boolean hasAlpha = numComponents != numBands;
785                    boolean isAlphaPre
786                            = hasAlpha && cmRaw.isAlphaPremultiplied();
787
788                    // Create a ColorModel of the same class and with
789                    // the same transfer type.
790                    ColorModel iccColorModel
791                            = new ComponentColorModel(iccColorSpace,
792                                    cmRaw.getComponentSize(),
793                                    hasAlpha,
794                                    isAlphaPre,
795                                    cmRaw.getTransparency(),
796                                    cmRaw.getTransferType());
797
798                    // Prepend the ICC profile-based ITS to the List. The
799                    // ColorModel and SampleModel are guaranteed to be
800                    // compatible as the old and new ColorModels are both
801                    // ComponentColorModels with the same transfer type
802                    // and the same number of components.
803                    l.add(new ImageTypeSpecifier(iccColorModel, smRaw));
804
805                    // Append the raw ITS to the List if and only if its
806                    // ColorSpace has the same type and number of components
807                    // as the ICC ColorSpace.
808                    if (csRaw.getType() == iccColorSpace.getType()
809                            && csRaw.getNumComponents()
810                            == iccColorSpace.getNumComponents()) {
811                        l.add(itsRaw);
812                    }
813                } else { // ICCProfile not compatible with SampleModel.
814                    // Append the raw ITS to the List.
815                    l.add(itsRaw);
816                }
817            } else { // No ICCProfile field or raw ColorModel not component.
818                // Append the raw ITS to the List.
819                l.add(itsRaw);
820            }
821
822            // Cache the ITS List.
823            imageTypeMap.put(imageIndexInteger, l);
824        }
825
826        return l.iterator();
827    }
828
829    public IIOMetadata getImageMetadata(int imageIndex) throws IIOException {
830        seekToImage(imageIndex);
831        TIFFImageMetadata im
832                = new TIFFImageMetadata(imageMetadata.getRootIFD().getTagSetList());
833        Node root
834                = imageMetadata.getAsTree(TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME);
835        im.setFromTree(TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME, root);
836        return im;
837    }
838
839    public IIOMetadata getStreamMetadata(int imageIndex) throws IIOException {
840        readHeader();
841        TIFFStreamMetadata sm = new TIFFStreamMetadata();
842        Node root = sm.getAsTree(TIFFStreamMetadata.NATIVE_METADATA_FORMAT_NAME);
843        sm.setFromTree(TIFFStreamMetadata.NATIVE_METADATA_FORMAT_NAME, root);
844        return sm;
845    }
846
847    public boolean isRandomAccessEasy(int imageIndex) throws IOException {
848        if (currIndex != -1) {
849            seekToImage(currIndex);
850            return getCompression() == BaselineTIFFTagSet.COMPRESSION_NONE;
851        } else {
852            return false;
853        }
854    }
855
856    // Thumbnails
857    public boolean readSupportsThumbnails() {
858        return false;
859    }
860
861    public boolean hasThumbnails(int imageIndex) {
862        return false;
863    }
864
865    public int getNumThumbnails(int imageIndex) throws IOException {
866        return 0;
867    }
868
869    public ImageReadParam getDefaultReadParam() {
870        return new TIFFImageReadParam();
871    }
872
873    public boolean isImageTiled(int imageIndex) throws IOException {
874        seekToImage(imageIndex);
875
876        TIFFField f
877                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH);
878        return f != null;
879    }
880
881    public int getTileWidth(int imageIndex) throws IOException {
882        seekToImage(imageIndex);
883        return getTileOrStripWidth();
884    }
885
886    public int getTileHeight(int imageIndex) throws IOException {
887        seekToImage(imageIndex);
888        return getTileOrStripHeight();
889    }
890
891    public BufferedImage readTile(int imageIndex, int tileX, int tileY)
892            throws IOException {
893
894        int w = getWidth(imageIndex);
895        int h = getHeight(imageIndex);
896        int tw = getTileWidth(imageIndex);
897        int th = getTileHeight(imageIndex);
898
899        int x = tw * tileX;
900        int y = th * tileY;
901
902        if (tileX < 0 || tileY < 0 || x >= w || y >= h) {
903            throw new IllegalArgumentException("Tile indices are out of bounds!");
904        }
905
906        if (x + tw > w) {
907            tw = w - x;
908        }
909
910        if (y + th > h) {
911            th = h - y;
912        }
913
914        ImageReadParam param = getDefaultReadParam();
915        Rectangle tileRect = new Rectangle(x, y, tw, th);
916        param.setSourceRegion(tileRect);
917
918        return read(imageIndex, param);
919    }
920
921    public boolean canReadRaster() {
922        return false;
923    }
924
925    public Raster readRaster(int imageIndex, ImageReadParam param)
926            throws IOException {
927        throw new UnsupportedOperationException();
928    }
929
930    private int[] sourceBands;
931    private int[] destinationBands;
932
933    private TIFFDecompressor decompressor;
934
935    // floor(num/den)
936    private static int ifloor(int num, int den) {
937        if (num < 0) {
938            num -= den - 1;
939        }
940        return num / den;
941    }
942
943    // ceil(num/den)
944    private static int iceil(int num, int den) {
945        if (num > 0) {
946            num += den - 1;
947        }
948        return num / den;
949    }
950
951    private void prepareRead(int imageIndex, ImageReadParam param)
952            throws IOException {
953        if (stream == null) {
954            throw new IllegalStateException("Input not set!");
955        }
956
957        // A null ImageReadParam means we use the default
958        if (param == null) {
959            param = getDefaultReadParam();
960        }
961
962        this.imageReadParam = param;
963
964        seekToImage(imageIndex);
965
966        this.tileOrStripWidth = getTileOrStripWidth();
967        this.tileOrStripHeight = getTileOrStripHeight();
968        this.planarConfiguration = getPlanarConfiguration();
969
970        this.sourceBands = param.getSourceBands();
971        if (sourceBands == null) {
972            sourceBands = new int[numBands];
973            for (int i = 0; i < numBands; i++) {
974                sourceBands[i] = i;
975            }
976        }
977
978        // Initialize the destination image
979        Iterator<ImageTypeSpecifier> imageTypes = getImageTypes(imageIndex);
980        ImageTypeSpecifier theImageType
981                = ImageUtil.getDestinationType(param, imageTypes);
982
983        int destNumBands = theImageType.getSampleModel().getNumBands();
984
985        this.destinationBands = param.getDestinationBands();
986        if (destinationBands == null) {
987            destinationBands = new int[destNumBands];
988            for (int i = 0; i < destNumBands; i++) {
989                destinationBands[i] = i;
990            }
991        }
992
993        if (sourceBands.length != destinationBands.length) {
994            throw new IllegalArgumentException(
995                    "sourceBands.length != destinationBands.length");
996        }
997
998        for (int i = 0; i < sourceBands.length; i++) {
999            int sb = sourceBands[i];
1000            if (sb < 0 || sb >= numBands) {
1001                throw new IllegalArgumentException(
1002                        "Source band out of range!");
1003            }
1004            int db = destinationBands[i];
1005            if (db < 0 || db >= destNumBands) {
1006                throw new IllegalArgumentException(
1007                        "Destination band out of range!");
1008            }
1009        }
1010    }
1011
1012    public RenderedImage readAsRenderedImage(int imageIndex,
1013            ImageReadParam param)
1014            throws IOException {
1015        prepareRead(imageIndex, param);
1016        return new TIFFRenderedImage(this, imageIndex, imageReadParam,
1017                width, height);
1018    }
1019
1020    private void decodeTile(int ti, int tj, int band) throws IOException {
1021        // Compute the region covered by the strip or tile
1022        Rectangle tileRect = new Rectangle(ti * tileOrStripWidth,
1023                tj * tileOrStripHeight,
1024                tileOrStripWidth,
1025                tileOrStripHeight);
1026
1027        // Clip against the image bounds if the image is not tiled. If it
1028        // is tiled, the tile may legally extend beyond the image bounds.
1029        if (!isImageTiled(currIndex)) {
1030            tileRect
1031                    = tileRect.intersection(new Rectangle(0, 0, width, height));
1032        }
1033
1034        // Return if the intersection is empty.
1035        if (tileRect.width <= 0 || tileRect.height <= 0) {
1036            return;
1037        }
1038
1039        int srcMinX = tileRect.x;
1040        int srcMinY = tileRect.y;
1041        int srcWidth = tileRect.width;
1042        int srcHeight = tileRect.height;
1043
1044        // Determine dest region that can be derived from the
1045        // source region
1046        dstMinX = iceil(srcMinX - sourceXOffset, srcXSubsampling);
1047        int dstMaxX = ifloor(srcMinX + srcWidth - 1 - sourceXOffset,
1048                srcXSubsampling);
1049
1050        dstMinY = iceil(srcMinY - sourceYOffset, srcYSubsampling);
1051        int dstMaxY = ifloor(srcMinY + srcHeight - 1 - sourceYOffset,
1052                srcYSubsampling);
1053
1054        dstWidth = dstMaxX - dstMinX + 1;
1055        dstHeight = dstMaxY - dstMinY + 1;
1056
1057        dstMinX += dstXOffset;
1058        dstMinY += dstYOffset;
1059
1060        // Clip against image bounds
1061        Rectangle dstRect = new Rectangle(dstMinX, dstMinY,
1062                dstWidth, dstHeight);
1063        dstRect
1064                = dstRect.intersection(theImage.getRaster().getBounds());
1065
1066        dstMinX = dstRect.x;
1067        dstMinY = dstRect.y;
1068        dstWidth = dstRect.width;
1069        dstHeight = dstRect.height;
1070
1071        if (dstWidth <= 0 || dstHeight <= 0) {
1072            return;
1073        }
1074
1075        // Backwards map dest region to source to determine
1076        // active source region
1077        int activeSrcMinX = (dstMinX - dstXOffset) * srcXSubsampling
1078                + sourceXOffset;
1079        int sxmax
1080                = (dstMinX + dstWidth - 1 - dstXOffset) * srcXSubsampling
1081                + sourceXOffset;
1082        int activeSrcWidth = sxmax - activeSrcMinX + 1;
1083
1084        int activeSrcMinY = (dstMinY - dstYOffset) * srcYSubsampling
1085                + sourceYOffset;
1086        int symax
1087                = (dstMinY + dstHeight - 1 - dstYOffset) * srcYSubsampling
1088                + sourceYOffset;
1089        int activeSrcHeight = symax - activeSrcMinY + 1;
1090
1091        decompressor.setSrcMinX(srcMinX);
1092        decompressor.setSrcMinY(srcMinY);
1093        decompressor.setSrcWidth(srcWidth);
1094        decompressor.setSrcHeight(srcHeight);
1095
1096        decompressor.setDstMinX(dstMinX);
1097        decompressor.setDstMinY(dstMinY);
1098        decompressor.setDstWidth(dstWidth);
1099        decompressor.setDstHeight(dstHeight);
1100
1101        decompressor.setActiveSrcMinX(activeSrcMinX);
1102        decompressor.setActiveSrcMinY(activeSrcMinY);
1103        decompressor.setActiveSrcWidth(activeSrcWidth);
1104        decompressor.setActiveSrcHeight(activeSrcHeight);
1105
1106        int tileIndex = tj * tilesAcross + ti;
1107
1108        if (planarConfiguration
1109                == BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) {
1110            tileIndex += band * tilesAcross * tilesDown;
1111        }
1112
1113        long offset = getTileOrStripOffset(tileIndex);
1114        long byteCount = getTileOrStripByteCount(tileIndex);
1115
1116        decompressor.setPlanarBand(band);
1117        decompressor.setStream(stream);
1118        decompressor.setOffset(offset);
1119        decompressor.setByteCount((int) byteCount);
1120
1121        decompressor.beginDecoding();
1122
1123        stream.mark();
1124        decompressor.decode();
1125        stream.reset();
1126    }
1127
1128    private void reportProgress() {
1129        // Report image progress/update to listeners after each tile
1130        pixelsRead += dstWidth * dstHeight;
1131        processImageProgress(100.0f * pixelsRead / pixelsToRead);
1132        processImageUpdate(theImage,
1133                dstMinX, dstMinY, dstWidth, dstHeight,
1134                1, 1,
1135                destinationBands);
1136    }
1137
1138    public BufferedImage read(int imageIndex, ImageReadParam param)
1139            throws IOException {
1140        prepareRead(imageIndex, param);
1141        this.theImage = getDestination(param,
1142                getImageTypes(imageIndex),
1143                width, height);
1144
1145        srcXSubsampling = imageReadParam.getSourceXSubsampling();
1146        srcYSubsampling = imageReadParam.getSourceYSubsampling();
1147
1148        Point p = imageReadParam.getDestinationOffset();
1149        dstXOffset = p.x;
1150        dstYOffset = p.y;
1151
1152        // This could probably be made more efficient...
1153        Rectangle srcRegion = new Rectangle(0, 0, 0, 0);
1154        Rectangle destRegion = new Rectangle(0, 0, 0, 0);
1155
1156        computeRegions(imageReadParam, width, height, theImage,
1157                srcRegion, destRegion);
1158
1159        // Initial source pixel, taking source region and source
1160        // subsamplimg offsets into account
1161        sourceXOffset = srcRegion.x;
1162        sourceYOffset = srcRegion.y;
1163
1164        pixelsToRead = destRegion.width * destRegion.height;
1165        pixelsRead = 0;
1166
1167        clearAbortRequest();
1168        processImageStarted(imageIndex);
1169        if (abortRequested()) {
1170            processReadAborted();
1171            return theImage;
1172        }
1173
1174        tilesAcross = (width + tileOrStripWidth - 1) / tileOrStripWidth;
1175        tilesDown = (height + tileOrStripHeight - 1) / tileOrStripHeight;
1176
1177        int compression = getCompression();
1178
1179        // Set the decompressor
1180        if (compression == BaselineTIFFTagSet.COMPRESSION_NONE) {
1181            // Get the fillOrder field.
1182            TIFFField fillOrderField
1183                    = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_FILL_ORDER);
1184
1185            // Set the decompressor based on the fill order.
1186            if (fillOrderField != null && fillOrderField.getAsInt(0) == 2) {
1187                this.decompressor = new TIFFLSBDecompressor();
1188            } else {
1189                this.decompressor = new TIFFNullDecompressor();
1190            }
1191        } else if (compression
1192                == BaselineTIFFTagSet.COMPRESSION_CCITT_T_6) {
1193            this.decompressor = new TIFFFaxDecompressor();
1194        } else if (compression
1195                == BaselineTIFFTagSet.COMPRESSION_CCITT_T_4) {
1196            this.decompressor = new TIFFFaxDecompressor();
1197        } else if (compression
1198                == BaselineTIFFTagSet.COMPRESSION_CCITT_RLE) {
1199            this.decompressor = new TIFFFaxDecompressor();
1200        } else if (compression
1201                == BaselineTIFFTagSet.COMPRESSION_PACKBITS) {
1202            this.decompressor = new TIFFPackBitsDecompressor();
1203        } else if (compression
1204                == BaselineTIFFTagSet.COMPRESSION_LZW) {
1205            TIFFField predictorField
1206                    = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PREDICTOR);
1207            int predictor = ((predictorField == null)
1208                    ? BaselineTIFFTagSet.PREDICTOR_NONE
1209                    : predictorField.getAsInt(0));
1210
1211            TIFFField fillOrderField
1212                    = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_FILL_ORDER);
1213            int fillOrder = ((fillOrderField == null)
1214                    ? BaselineTIFFTagSet.FILL_ORDER_LEFT_TO_RIGHT
1215                    : fillOrderField.getAsInt(0));
1216
1217            this.decompressor = new TIFFLZWDecompressor(predictor, fillOrder);
1218        } else if (compression
1219                == BaselineTIFFTagSet.COMPRESSION_JPEG) {
1220            this.decompressor = new TIFFJPEGDecompressor();
1221        } else if (compression
1222                == BaselineTIFFTagSet.COMPRESSION_ZLIB
1223                || compression
1224                == BaselineTIFFTagSet.COMPRESSION_DEFLATE) {
1225            TIFFField predictorField
1226                    = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PREDICTOR);
1227            int predictor = ((predictorField == null)
1228                    ? BaselineTIFFTagSet.PREDICTOR_NONE
1229                    : predictorField.getAsInt(0));
1230            this.decompressor = new TIFFDeflateDecompressor(predictor);
1231        } else if (compression
1232                == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
1233            TIFFField JPEGProcField
1234                    = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_PROC);
1235            if (JPEGProcField == null) {
1236                processWarningOccurred("JPEGProc field missing; assuming baseline sequential JPEG process.");
1237            } else if (JPEGProcField.getAsInt(0)
1238                    != BaselineTIFFTagSet.JPEG_PROC_BASELINE) {
1239                throw new IIOException("Old-style JPEG supported for baseline sequential JPEG process only!");
1240            }
1241            this.decompressor = new TIFFOldJPEGDecompressor();
1242            //throw new IIOException("Old-style JPEG not supported!");
1243        } else {
1244            throw new IIOException("Unsupported compression type (tag value = "
1245                    + compression + ")!");
1246        }
1247
1248        if (photometricInterpretation
1249                == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR
1250                && compression != BaselineTIFFTagSet.COMPRESSION_JPEG
1251                && compression != BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
1252            boolean convertYCbCrToRGB
1253                    = theImage.getColorModel().getColorSpace().getType()
1254                    == ColorSpace.TYPE_RGB;
1255            TIFFDecompressor wrappedDecompressor
1256                    = this.decompressor instanceof TIFFNullDecompressor
1257                            ? null : this.decompressor;
1258            this.decompressor
1259                    = new TIFFYCbCrDecompressor(wrappedDecompressor,
1260                            convertYCbCrToRGB);
1261        }
1262
1263        TIFFColorConverter colorConverter = null;
1264        if (photometricInterpretation
1265                == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CIELAB
1266                && theImage.getColorModel().getColorSpace().getType()
1267                == ColorSpace.TYPE_RGB) {
1268            colorConverter = new TIFFCIELabColorConverter();
1269        } else if (photometricInterpretation
1270                == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR
1271                && !(this.decompressor instanceof TIFFYCbCrDecompressor)
1272                && compression != BaselineTIFFTagSet.COMPRESSION_JPEG
1273                && compression != BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
1274            colorConverter = new TIFFYCbCrColorConverter(imageMetadata);
1275        }
1276
1277        decompressor.setReader(this);
1278        decompressor.setMetadata(imageMetadata);
1279        decompressor.setImage(theImage);
1280
1281        decompressor.setPhotometricInterpretation(photometricInterpretation);
1282        decompressor.setCompression(compression);
1283        decompressor.setSamplesPerPixel(samplesPerPixel);
1284        decompressor.setBitsPerSample(bitsPerSample);
1285        decompressor.setSampleFormat(sampleFormat);
1286        decompressor.setExtraSamples(extraSamples);
1287        decompressor.setColorMap(colorMap);
1288
1289        decompressor.setColorConverter(colorConverter);
1290
1291        decompressor.setSourceXOffset(sourceXOffset);
1292        decompressor.setSourceYOffset(sourceYOffset);
1293        decompressor.setSubsampleX(srcXSubsampling);
1294        decompressor.setSubsampleY(srcYSubsampling);
1295
1296        decompressor.setDstXOffset(dstXOffset);
1297        decompressor.setDstYOffset(dstYOffset);
1298
1299        decompressor.setSourceBands(sourceBands);
1300        decompressor.setDestinationBands(destinationBands);
1301
1302        // Compute bounds on the tile indices for this source region.
1303        int minTileX
1304                = TIFFImageWriter.XToTileX(srcRegion.x, 0, tileOrStripWidth);
1305        int minTileY
1306                = TIFFImageWriter.YToTileY(srcRegion.y, 0, tileOrStripHeight);
1307        int maxTileX
1308                = TIFFImageWriter.XToTileX(srcRegion.x + srcRegion.width - 1,
1309                        0, tileOrStripWidth);
1310        int maxTileY
1311                = TIFFImageWriter.YToTileY(srcRegion.y + srcRegion.height - 1,
1312                        0, tileOrStripHeight);
1313
1314        if (planarConfiguration
1315                == BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) {
1316
1317            decompressor.setPlanar(true);
1318
1319            int[] sb = new int[1];
1320            int[] db = new int[1];
1321            for (int tj = minTileY; tj <= maxTileY; tj++) {
1322                for (int ti = minTileX; ti <= maxTileX; ti++) {
1323                    for (int band = 0; band < numBands; band++) {
1324                        sb[0] = sourceBands[band];
1325                        decompressor.setSourceBands(sb);
1326                        db[0] = destinationBands[band];
1327                        decompressor.setDestinationBands(db);
1328
1329                        decodeTile(ti, tj, band);
1330                    }
1331
1332                    reportProgress();
1333                    if (abortRequested()) {
1334                        processReadAborted();
1335                        return theImage;
1336                    }
1337                }
1338            }
1339        } else {
1340            for (int tj = minTileY; tj <= maxTileY; tj++) {
1341                for (int ti = minTileX; ti <= maxTileX; ti++) {
1342                    decodeTile(ti, tj, -1);
1343
1344                    reportProgress();
1345                    if (abortRequested()) {
1346                        processReadAborted();
1347                        return theImage;
1348                    }
1349                }
1350            }
1351        }
1352        processImageComplete();
1353        return theImage;
1354    }
1355
1356    public void reset() {
1357        super.reset();
1358        resetLocal();
1359    }
1360
1361    protected void resetLocal() {
1362        stream = null;
1363        gotHeader = false;
1364        imageReadParam = getDefaultReadParam();
1365        streamMetadata = null;
1366        currIndex = -1;
1367        imageMetadata = null;
1368        imageStartPosition = new ArrayList<Long>();
1369        numImages = -1;
1370        imageTypeMap = new HashMap<Integer, List<ImageTypeSpecifier>>();
1371        width = -1;
1372        height = -1;
1373        numBands = -1;
1374        tileOrStripWidth = -1;
1375        tileOrStripHeight = -1;
1376        planarConfiguration = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
1377    }
1378
1379    /**
1380     * Package scope method to allow decompressors, for example, to emit warning
1381     * messages.
1382     */
1383    void forwardWarningMessage(String warning) {
1384        processWarningOccurred(warning);
1385    }
1386}
1387