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  */
25 package com.sun.imageio.plugins.tiff;
26 
27 import java.awt.Point;
28 import java.awt.Rectangle;
29 import java.awt.color.ColorSpace;
30 import java.awt.color.ICC_ColorSpace;
31 import java.awt.color.ICC_Profile;
32 import java.awt.image.BufferedImage;
33 import java.awt.image.ColorModel;
34 import java.awt.image.ComponentColorModel;
35 import java.awt.image.Raster;
36 import java.awt.image.RenderedImage;
37 import java.awt.image.SampleModel;
38 import java.io.EOFException;
39 import java.io.IOException;
40 import java.nio.ByteOrder;
41 import java.util.ArrayList;
42 import java.util.HashMap;
43 import java.util.Iterator;
44 import java.util.List;
45 import javax.imageio.IIOException;
46 import javax.imageio.ImageIO;
47 import javax.imageio.ImageReader;
48 import javax.imageio.ImageReadParam;
49 import javax.imageio.ImageTypeSpecifier;
50 import javax.imageio.metadata.IIOMetadata;
51 import javax.imageio.spi.ImageReaderSpi;
52 import javax.imageio.stream.ImageInputStream;
53 import org.w3c.dom.Node;
54 import com.sun.imageio.plugins.common.ImageUtil;
55 import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
56 import javax.imageio.plugins.tiff.TIFFField;
57 import javax.imageio.plugins.tiff.TIFFImageReadParam;
58 import javax.imageio.plugins.tiff.TIFFTagSet;
59 
60 public 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 
TIFFImageReader(ImageReaderSpi originatingProvider)135     public TIFFImageReader(ImageReaderSpi originatingProvider) {
136         super(originatingProvider);
137     }
138 
setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata)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
readHeader()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 
locateImage(int imageIndex)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 
getNumImages(boolean allowSearch)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 
getStreamMetadata()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.
checkIndex(int imageIndex)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.
seekToImage(int imageIndex)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'
readMetadata()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 
getWidth()328     private int getWidth() {
329         return this.width;
330     }
331 
getHeight()332     private int getHeight() {
333         return this.height;
334     }
335 
336     // Returns tile width if image is tiled, else image width
getTileOrStripWidth()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
getTileOrStripHeight()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 
getPlanarConfiguration()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 
getTileOrStripOffset(int tileIndex)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 
getTileOrStripByteCount(int tileIndex)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 
getCompression()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 
getWidth(int imageIndex)490     public int getWidth(int imageIndex) throws IOException {
491         seekToImage(imageIndex);
492         return getWidth();
493     }
494 
getHeight(int imageIndex)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      */
initializeFromMetadata()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 
getImageTypes(int imageIndex)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 
getImageMetadata(int imageIndex)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 
getStreamMetadata(int imageIndex)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 
isRandomAccessEasy(int imageIndex)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
readSupportsThumbnails()857     public boolean readSupportsThumbnails() {
858         return false;
859     }
860 
hasThumbnails(int imageIndex)861     public boolean hasThumbnails(int imageIndex) {
862         return false;
863     }
864 
getNumThumbnails(int imageIndex)865     public int getNumThumbnails(int imageIndex) throws IOException {
866         return 0;
867     }
868 
getDefaultReadParam()869     public ImageReadParam getDefaultReadParam() {
870         return new TIFFImageReadParam();
871     }
872 
isImageTiled(int imageIndex)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 
getTileWidth(int imageIndex)881     public int getTileWidth(int imageIndex) throws IOException {
882         seekToImage(imageIndex);
883         return getTileOrStripWidth();
884     }
885 
getTileHeight(int imageIndex)886     public int getTileHeight(int imageIndex) throws IOException {
887         seekToImage(imageIndex);
888         return getTileOrStripHeight();
889     }
890 
readTile(int imageIndex, int tileX, int tileY)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 
canReadRaster()921     public boolean canReadRaster() {
922         return false;
923     }
924 
readRaster(int imageIndex, ImageReadParam param)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)
ifloor(int num, int 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)
iceil(int num, int 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 
prepareRead(int imageIndex, ImageReadParam param)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 
readAsRenderedImage(int imageIndex, ImageReadParam param)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 
decodeTile(int ti, int tj, int band)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 
reportProgress()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 
read(int imageIndex, ImageReadParam param)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 
reset()1356     public void reset() {
1357         super.reset();
1358         resetLocal();
1359     }
1360 
resetLocal()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      */
forwardWarningMessage(String warning)1383     void forwardWarningMessage(String warning) {
1384         processWarningOccurred(warning);
1385     }
1386 }
1387