001/*
002 * $RCSfile: TIFFImageReader.java,v $
003 *
004 * 
005 * Copyright (c) 2005 Sun Microsystems, Inc. All  Rights Reserved.
006 * 
007 * Redistribution and use in source and binary forms, with or without
008 * modification, are permitted provided that the following conditions
009 * are met: 
010 * 
011 * - Redistribution of source code must retain the above copyright 
012 *   notice, this  list of conditions and the following disclaimer.
013 * 
014 * - Redistribution in binary form must reproduce the above copyright
015 *   notice, this list of conditions and the following disclaimer in 
016 *   the documentation and/or other materials provided with the
017 *   distribution.
018 * 
019 * Neither the name of Sun Microsystems, Inc. or the names of 
020 * contributors may be used to endorse or promote products derived 
021 * from this software without specific prior written permission.
022 * 
023 * This software is provided "AS IS," without a warranty of any 
024 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 
025 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
026 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
027 * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL 
028 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 
029 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
030 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR 
031 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
032 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
033 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
034 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
035 * POSSIBILITY OF SUCH DAMAGES. 
036 * 
037 * You acknowledge that this software is not designed or intended for 
038 * use in the design, construction, operation or maintenance of any 
039 * nuclear facility. 
040 *
041 * $Revision: 1.13 $
042 * $Date: 2007/12/19 20:17:02 $
043 * $State: Exp $
044 */
045package com.github.jaiimageio.impl.plugins.tiff;
046
047import java.awt.Point;
048import java.awt.Rectangle;
049import java.awt.color.ColorSpace;
050import java.awt.color.ICC_ColorSpace;
051import java.awt.color.ICC_Profile;
052import java.awt.image.BufferedImage;
053import java.awt.image.ColorModel;
054import java.awt.image.ComponentColorModel;
055import java.awt.image.ComponentSampleModel;
056import java.awt.image.DataBuffer;
057import java.awt.image.DataBufferByte;
058import java.awt.image.MultiPixelPackedSampleModel;
059import java.awt.image.Raster;
060import java.awt.image.RenderedImage;
061import java.awt.image.SampleModel;
062import java.awt.image.SinglePixelPackedSampleModel;
063import java.awt.image.WritableRaster;
064import java.io.ByteArrayInputStream;
065import java.io.IOException;
066import java.io.InputStream;
067import java.nio.ByteOrder;
068import java.util.ArrayList;
069import java.util.Arrays;
070import java.util.HashMap;
071import java.util.Iterator;
072import java.util.List;
073
074import javax.imageio.IIOException;
075import javax.imageio.ImageIO;
076import javax.imageio.ImageReader;
077import javax.imageio.ImageReadParam;
078import javax.imageio.ImageTypeSpecifier;
079import javax.imageio.metadata.IIOMetadata;
080import javax.imageio.spi.ImageReaderSpi;
081import javax.imageio.stream.ImageInputStream;
082import javax.imageio.stream.MemoryCacheImageInputStream;
083
084import org.w3c.dom.Node;
085
086import com.github.jaiimageio.impl.common.ImageUtil;
087import com.github.jaiimageio.impl.common.PackageUtil;
088import com.github.jaiimageio.plugins.tiff.BaselineTIFFTagSet;
089import com.github.jaiimageio.plugins.tiff.TIFFColorConverter;
090import com.github.jaiimageio.plugins.tiff.TIFFDecompressor;
091import com.github.jaiimageio.plugins.tiff.TIFFField;
092import com.github.jaiimageio.plugins.tiff.TIFFImageReadParam;
093import com.github.jaiimageio.plugins.tiff.TIFFTag;
094
095public class TIFFImageReader extends ImageReader {
096
097    private static final boolean DEBUG = false; // XXX 'false' for release!!!
098
099    // The current ImageInputStream source.
100    ImageInputStream stream = null;
101
102    // True if the file header has been read.
103    boolean gotHeader = false;
104
105    ImageReadParam imageReadParam = getDefaultReadParam();
106
107    // Stream metadata, or null.
108    TIFFStreamMetadata streamMetadata = null;
109
110    // The current image index.
111    int currIndex = -1;
112
113    // Metadata for image at 'currIndex', or null.
114    TIFFImageMetadata imageMetadata = null;
115    
116    // A <code>List</code> of <code>Long</code>s indicating the stream
117    // positions of the start of the IFD for each image.  Entries
118    // are added as needed.
119    List imageStartPosition = new ArrayList();
120
121    // The number of images in the stream, if known, otherwise -1.
122    int numImages = -1;
123
124    // The ImageTypeSpecifiers of the images in the stream.
125    // Contains a map of Integers to Lists.
126    HashMap imageTypeMap = new HashMap();
127
128    BufferedImage theImage = null;
129
130    int width = -1;
131    int height = -1;
132    int numBands = -1;
133    int tileOrStripWidth = -1, tileOrStripHeight = -1;
134
135    int planarConfiguration = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
136
137    int rowsDone = 0;
138
139    int compression;
140    int photometricInterpretation;
141    int samplesPerPixel;
142    int[] sampleFormat;
143    int[] bitsPerSample;
144    int[] extraSamples;
145    char[] colorMap;
146
147    int sourceXOffset;
148    int sourceYOffset;
149    int srcXSubsampling;
150    int srcYSubsampling;
151
152    int dstWidth;
153    int dstHeight;
154    int dstMinX;
155    int dstMinY;
156    int dstXOffset;
157    int dstYOffset;
158
159    int tilesAcross;
160    int tilesDown;
161
162    int pixelsRead;
163    int pixelsToRead;
164
165    public TIFFImageReader(ImageReaderSpi originatingProvider) {
166        super(originatingProvider);
167    }
168
169    public void setInput(Object input,
170                         boolean seekForwardOnly,
171                         boolean ignoreMetadata) {
172        super.setInput(input, seekForwardOnly, ignoreMetadata);
173
174        // Clear all local values based on the previous stream contents.
175        resetLocal();
176
177        if (input != null) {
178            if (!(input instanceof ImageInputStream)) {
179                throw new IllegalArgumentException
180                    ("input not an ImageInputStream!"); 
181            }
182            this.stream = (ImageInputStream)input;
183        } else {
184            this.stream = null;
185        }
186    }
187
188    // Do not seek to the beginning of the stream so as to allow users to
189    // point us at an IFD within some other file format
190    private void readHeader() throws IIOException {
191        if (gotHeader) {
192            return;
193        }
194        if (stream == null) {
195            throw new IllegalStateException("Input not set!");
196        }
197
198        // Create an object to store the stream metadata
199        this.streamMetadata = new TIFFStreamMetadata();
200        
201        try {
202            int byteOrder = stream.readUnsignedShort();
203            if (byteOrder == 0x4d4d) {
204                streamMetadata.byteOrder = ByteOrder.BIG_ENDIAN;
205                stream.setByteOrder(ByteOrder.BIG_ENDIAN);
206            } else if (byteOrder == 0x4949) {
207                streamMetadata.byteOrder = ByteOrder.LITTLE_ENDIAN;
208                stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
209            } else {
210                processWarningOccurred(
211                           "Bad byte order in header, assuming little-endian");
212                streamMetadata.byteOrder = ByteOrder.LITTLE_ENDIAN;
213                stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
214            }
215            
216            int magic = stream.readUnsignedShort();
217            if (magic != 42) {
218                processWarningOccurred(
219                                     "Bad magic number in header, continuing");
220            }
221            
222            // Seek to start of first IFD
223            long offset = stream.readUnsignedInt();
224            imageStartPosition.add(new Long(offset));
225            stream.seek(offset);
226        } catch (IOException e) {
227            throw new IIOException("I/O error reading header!", e);
228        }
229
230        gotHeader = true;
231    }
232
233    private int locateImage(int imageIndex) throws IIOException {
234        readHeader();
235
236        try {
237            // Find closest known index
238            int index = Math.min(imageIndex, imageStartPosition.size() - 1);
239
240            // Seek to that position
241            Long l = (Long)imageStartPosition.get(index);
242            stream.seek(l.longValue());
243
244            // Skip IFDs until at desired index or last image found
245            while (index < imageIndex) {
246                int count = stream.readUnsignedShort();
247                stream.skipBytes(12*count);
248
249                long offset = stream.readUnsignedInt();
250                if (offset == 0) {
251                    return index;
252                }
253                
254                imageStartPosition.add(new Long(offset));
255                stream.seek(offset);
256                ++index;
257            }
258        } catch (IOException e) {
259            throw new IIOException("Couldn't seek!", e);
260        }
261
262        if (currIndex != imageIndex) {
263            imageMetadata = null;
264        }
265        currIndex = imageIndex;
266        return imageIndex;
267    }
268
269    public int getNumImages(boolean allowSearch) throws IOException {
270        if (stream == null) {
271            throw new IllegalStateException("Input not set!");
272        }
273        if (seekForwardOnly && allowSearch) {
274            throw new IllegalStateException
275                ("seekForwardOnly and allowSearch can't both be true!");
276        }
277
278        if (numImages > 0) {
279            return numImages;
280        }
281        if (allowSearch) {
282            this.numImages = locateImage(Integer.MAX_VALUE) + 1;
283        }
284        return numImages;
285    }
286
287    public IIOMetadata getStreamMetadata() throws IIOException {
288        readHeader();
289        return streamMetadata;
290    }
291
292    // Throw an IndexOutOfBoundsException if index < minIndex,
293    // and bump minIndex if required.
294    private void checkIndex(int imageIndex) {
295        if (imageIndex < minIndex) {
296            throw new IndexOutOfBoundsException("imageIndex < minIndex!");
297        }
298        if (seekForwardOnly) {
299            minIndex = imageIndex;
300        }
301    }
302
303    // Verify that imageIndex is in bounds, find the image IFD, read the
304    // image metadata, initialize instance variables from the metadata.
305    private void seekToImage(int imageIndex) throws IIOException {
306        checkIndex(imageIndex);
307
308        int index = locateImage(imageIndex);
309        if (index != imageIndex) {
310            throw new IndexOutOfBoundsException("imageIndex out of bounds!");
311        }
312
313        readMetadata();
314
315        initializeFromMetadata();
316    }
317
318    // Stream must be positioned at start of IFD for 'currIndex'
319    private void readMetadata() throws IIOException {
320        if (stream == null) {
321            throw new IllegalStateException("Input not set!");
322        }
323
324        if (imageMetadata != null) {
325            return;
326        }
327        try {
328            // Create an object to store the image metadata
329            List tagSets;
330            if (imageReadParam instanceof TIFFImageReadParam) {
331                tagSets =
332                    ((TIFFImageReadParam)imageReadParam).getAllowedTagSets();
333            } else {
334                tagSets = new ArrayList(1);
335                tagSets.add(BaselineTIFFTagSet.getInstance());
336            }
337
338            this.imageMetadata = new TIFFImageMetadata(tagSets);
339            imageMetadata.initializeFromStream(stream, ignoreMetadata);
340        } catch (IIOException iioe) {
341            throw iioe;
342        } catch (IOException ioe) {
343            throw new IIOException("I/O error reading image metadata!", ioe);
344        }
345    }
346
347    private int getWidth() {
348        return this.width;
349    }
350
351    private int getHeight() {
352        return this.height;
353    }
354
355    private int getNumBands() {
356        return this.numBands;
357    }
358
359    // Returns tile width if image is tiled, else image width
360    private int getTileOrStripWidth() {
361        TIFFField f =
362            imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH);
363        return (f == null) ? getWidth() : f.getAsInt(0);
364    }
365
366    // Returns tile height if image is tiled, else strip height
367    private int getTileOrStripHeight() {
368        TIFFField f =
369            imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_LENGTH);
370        if (f != null) {
371            return f.getAsInt(0);
372        }
373
374        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP);
375        // Default for ROWS_PER_STRIP is 2^32 - 1, i.e., infinity
376        int h = (f == null) ? -1 : f.getAsInt(0);
377        return (h == -1) ? getHeight() : h;
378    }
379
380    private int getPlanarConfiguration() {
381        TIFFField f =
382        imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION);
383        if (f != null) {
384            int planarConfigurationValue = f.getAsInt(0);
385            if(planarConfigurationValue ==
386               BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) {
387                // Some writers (e.g. Kofax standard Multi-Page TIFF
388                // Storage Filter v2.01.000; cf. bug 4929147) do not
389                // correctly set the value of this field. Attempt to
390                // ascertain whether the value is correctly Planar.
391                if(getCompression() ==
392                   BaselineTIFFTagSet.COMPRESSION_OLD_JPEG &&
393                   imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT) !=
394                   null) {
395                    // JPEG interchange format cannot have
396                    // PlanarConfiguration value Chunky so reset.
397                    processWarningOccurred("PlanarConfiguration \"Planar\" value inconsistent with JPEGInterchangeFormat; resetting to \"Chunky\".");
398                    planarConfigurationValue =
399                        BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
400                } else {
401                    TIFFField offsetField =
402                        imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS);
403                    if (offsetField == null) {
404                        // Tiles
405                        offsetField =
406                            imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS);
407                        int tw = getTileOrStripWidth();
408                        int th = getTileOrStripHeight();
409                        int tAcross = (getWidth() + tw - 1)/tw;
410                        int tDown = (getHeight() + th - 1)/th;
411                        int tilesPerImage = tAcross*tDown;
412                        long[] offsetArray = offsetField.getAsLongs();
413                        if(offsetArray != null &&
414                           offsetArray.length == tilesPerImage) {
415                            // Length of offsets array is
416                            // TilesPerImage for Chunky and
417                            // SamplesPerPixel*TilesPerImage for Planar.
418                            processWarningOccurred("PlanarConfiguration \"Planar\" value inconsistent with TileOffsets field value count; resetting to \"Chunky\".");
419                            planarConfigurationValue =
420                                BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
421                        }
422                    } else {
423                        // Strips
424                        int rowsPerStrip = getTileOrStripHeight();
425                        int stripsPerImage =
426                            (getHeight() + rowsPerStrip - 1)/rowsPerStrip;
427                        long[] offsetArray = offsetField.getAsLongs();
428                        if(offsetArray != null &&
429                           offsetArray.length == stripsPerImage) {
430                            // Length of offsets array is
431                            // StripsPerImage for Chunky and
432                            // SamplesPerPixel*StripsPerImage for Planar.
433                            processWarningOccurred("PlanarConfiguration \"Planar\" value inconsistent with StripOffsets field value count; resetting to \"Chunky\".");
434                            planarConfigurationValue =
435                                BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
436                        }
437                    }
438                }
439            }
440            return planarConfigurationValue;
441        }
442
443        return BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
444    }
445
446    private long getTileOrStripOffset(int tileIndex) throws IIOException {
447        TIFFField f =
448            imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS);
449        if (f == null) {
450            f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS);
451        }
452        if (f == null) {
453            f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT);
454        }
455
456        if(f == null) {
457            throw new IIOException
458                ("Missing required strip or tile offsets field.");
459        }
460
461        return f.getAsLong(tileIndex);
462    }
463
464    private long getTileOrStripByteCount(int tileIndex) throws IOException {
465        TIFFField f =
466           imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS);
467        if (f == null) {
468            f =
469          imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS);
470        }
471        if (f == null) {
472            f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
473        }
474
475        long tileOrStripByteCount;
476        if(f != null) {
477            tileOrStripByteCount = f.getAsLong(tileIndex);
478        } else {
479            processWarningOccurred("TIFF directory contains neither StripByteCounts nor TileByteCounts field: attempting to calculate from strip or tile width and height.");
480
481            // Initialize to number of bytes per strip or tile assuming
482            // no compression.
483            int bitsPerPixel = bitsPerSample[0];
484            for(int i = 1; i < samplesPerPixel; i++) {
485                bitsPerPixel += bitsPerSample[i];
486            }
487            int bytesPerRow = (getTileOrStripWidth()*bitsPerPixel + 7)/8;
488            tileOrStripByteCount = bytesPerRow*getTileOrStripHeight();
489
490            // Clamp to end of stream if possible.
491            long streamLength = stream.length();
492            if(streamLength != -1) {
493                tileOrStripByteCount =
494                    Math.min(tileOrStripByteCount,
495                             streamLength - getTileOrStripOffset(tileIndex));
496            } else {
497                processWarningOccurred("Stream length is unknown: cannot clamp estimated strip or tile byte count to EOF.");
498            }
499        }
500
501        return tileOrStripByteCount;
502    }
503
504    private int getCompression() {
505        TIFFField f =
506            imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION);
507        if (f == null) {
508            return BaselineTIFFTagSet.COMPRESSION_NONE;
509        } else {
510            return f.getAsInt(0);
511        }
512    }
513
514    public int getWidth(int imageIndex) throws IOException {
515        seekToImage(imageIndex);
516        return getWidth();
517    }
518
519    public int getHeight(int imageIndex) throws IOException {
520        seekToImage(imageIndex);
521        return getHeight();
522    }
523
524    /**
525     * Initializes these instance variables from the image metadata:
526     * <pre>
527     * compression
528     * width
529     * height
530     * samplesPerPixel
531     * numBands
532     * colorMap
533     * photometricInterpretation
534     * sampleFormat
535     * bitsPerSample
536     * extraSamples
537     * tileOrStripWidth
538     * tileOrStripHeight
539     * </pre>
540     */
541    private void initializeFromMetadata() {
542        TIFFField f;
543
544        // Compression
545        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION);
546        if (f == null) {
547            processWarningOccurred
548                ("Compression field is missing; assuming no compression");
549            compression = BaselineTIFFTagSet.COMPRESSION_NONE;
550        } else {
551            compression = f.getAsInt(0);
552        }
553
554        // Whether key dimensional information is absent.
555        boolean isMissingDimension = false;
556
557        // ImageWidth -> width
558        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH);
559        if (f != null) {
560            this.width = f.getAsInt(0);
561        } else {
562            processWarningOccurred("ImageWidth field is missing.");
563            isMissingDimension = true;
564        }
565
566        // ImageLength -> height
567        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH);
568        if (f != null) {
569            this.height = f.getAsInt(0);
570        } else {
571            processWarningOccurred("ImageLength field is missing.");
572            isMissingDimension = true;
573        }
574
575        // SamplesPerPixel
576        f =
577          imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL);
578        if (f != null) {
579            samplesPerPixel = f.getAsInt(0);
580        } else {
581            samplesPerPixel = 1;
582            isMissingDimension = true;
583        }
584
585        // If any dimension is missing and there is a JPEG stream available
586        // get the information from it.
587        int defaultBitDepth = 1;
588        if(isMissingDimension &&
589           (f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT)) != null) {
590            Iterator iter = ImageIO.getImageReadersByFormatName("JPEG");
591            if(iter != null && iter.hasNext()) {
592                ImageReader jreader = (ImageReader)iter.next();
593                try {
594                    stream.mark();
595                    stream.seek(f.getAsLong(0));
596                    jreader.setInput(stream);
597                    if(imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH) == null) {
598                        this.width = jreader.getWidth(0);
599                    }
600                    if(imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH) == null) {
601                        this.height = jreader.getHeight(0);
602                    }
603                    ImageTypeSpecifier imageType = jreader.getRawImageType(0);
604                    if(imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL) == null) {
605                        this.samplesPerPixel =
606                            imageType.getSampleModel().getNumBands();
607                    }
608                    stream.reset();
609                    defaultBitDepth =
610                        imageType.getColorModel().getComponentSize(0);
611                } catch(IOException e) {
612                    // Ignore it and proceed: an error will occur later.
613                }
614                jreader.dispose();
615            }
616        }
617
618        if (samplesPerPixel < 1) {
619            processWarningOccurred("Samples per pixel < 1!");
620        }
621
622        // SamplesPerPixel -> numBands
623        numBands = samplesPerPixel;
624
625        // ColorMap
626        this.colorMap = null;
627        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_COLOR_MAP);
628        if (f != null) {
629            // Grab color map
630            colorMap = f.getAsChars();
631        }
632        
633        // PhotometricInterpretation
634        f =
635        imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION);
636        if (f == null) {
637            if (compression == BaselineTIFFTagSet.COMPRESSION_CCITT_RLE ||
638                compression == BaselineTIFFTagSet.COMPRESSION_CCITT_T_4 ||
639                compression == BaselineTIFFTagSet.COMPRESSION_CCITT_T_6) {
640                processWarningOccurred
641                    ("PhotometricInterpretation field is missing; "+
642                     "assuming WhiteIsZero");
643                photometricInterpretation =
644                   BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO;
645            } else if(this.colorMap != null) {
646                photometricInterpretation =
647                    BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR;
648            } else if(samplesPerPixel == 3 || samplesPerPixel == 4) {
649                photometricInterpretation =
650                    BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB;
651            } else {
652                processWarningOccurred
653                    ("PhotometricInterpretation field is missing; "+
654                     "assuming BlackIsZero");
655                photometricInterpretation =
656                   BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO;
657            }
658        } else {
659            photometricInterpretation = f.getAsInt(0);
660        }
661
662        // SampleFormat
663        boolean replicateFirst = false;
664        int first = -1;
665
666        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLE_FORMAT);
667        sampleFormat = new int[samplesPerPixel];
668        replicateFirst = false;
669        if (f == null) {
670            replicateFirst = true;
671            first = BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED;
672        } else if (f.getCount() != samplesPerPixel) {
673            replicateFirst = true;
674            first = f.getAsInt(0);
675        }
676
677        for (int i = 0; i < samplesPerPixel; i++) {
678            sampleFormat[i] = replicateFirst ? first : f.getAsInt(i);
679            if (sampleFormat[i] !=
680                  BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER &&
681                sampleFormat[i] !=
682                  BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER &&
683                sampleFormat[i] !=
684                  BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT &&
685                sampleFormat[i] !=
686                  BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED) {
687                processWarningOccurred(
688          "Illegal value for SAMPLE_FORMAT, assuming SAMPLE_FORMAT_UNDEFINED");
689                sampleFormat[i] = BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED;
690            }
691        }
692
693        // BitsPerSample
694        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
695        this.bitsPerSample = new int[samplesPerPixel];
696        replicateFirst = false;
697        if (f == null) {
698            replicateFirst = true;
699            first = defaultBitDepth;
700        } else if (f.getCount() != samplesPerPixel) {
701            replicateFirst = true;
702            first = f.getAsInt(0);
703        }
704        
705        for (int i = 0; i < samplesPerPixel; i++) {
706            // Replicate initial value if not enough values provided
707            bitsPerSample[i] = replicateFirst ? first : f.getAsInt(i);
708
709            if (DEBUG) {
710                System.out.println("bitsPerSample[" + i + "] = "
711                                   + bitsPerSample[i]);
712            }
713        }
714
715        // ExtraSamples
716        this.extraSamples = null;
717        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES);
718        if (f != null) {
719            extraSamples = f.getAsInts();
720        }
721
722//         System.out.println("colorMap = " + colorMap);
723//         if (colorMap != null) {
724//             for (int i = 0; i < colorMap.length; i++) {
725//              System.out.println("colorMap[" + i + "] = " + (int)(colorMap[i]));
726//             }
727//         }
728
729    }
730
731    public Iterator getImageTypes(int imageIndex) throws IIOException {
732        List l; // List of ImageTypeSpecifiers
733
734        Integer imageIndexInteger = new Integer(imageIndex);
735        if(imageTypeMap.containsKey(imageIndexInteger)) {
736            // Return the cached ITS List.
737            l = (List)imageTypeMap.get(imageIndexInteger);
738        } else {
739            // Create a new ITS List.
740            l = new ArrayList(1);
741
742            // Create the ITS and cache if for later use so that this method
743            // always returns an Iterator containing the same ITS objects.
744            seekToImage(imageIndex);
745            ImageTypeSpecifier itsRaw = 
746                TIFFDecompressor.getRawImageTypeSpecifier
747                    (photometricInterpretation,
748                     compression,
749                     samplesPerPixel,
750                     bitsPerSample,
751                     sampleFormat,
752                     extraSamples,
753                     colorMap);
754
755            // Check for an ICCProfile field.
756            TIFFField iccProfileField =
757                imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_ICC_PROFILE);
758
759            // If an ICCProfile field is present change the ImageTypeSpecifier
760            // to use it if the data layout is component type.
761            if(iccProfileField != null &&
762               itsRaw.getColorModel() instanceof ComponentColorModel) {
763                // Create a ColorSpace from the profile.
764                byte[] iccProfileValue = iccProfileField.getAsBytes();
765                ICC_Profile iccProfile =
766                    ICC_Profile.getInstance(iccProfileValue);
767                ICC_ColorSpace iccColorSpace =
768                    new ICC_ColorSpace(iccProfile);
769
770                // Get the raw sample and color information.
771                ColorModel cmRaw = itsRaw.getColorModel();
772                ColorSpace csRaw = cmRaw.getColorSpace();
773                SampleModel smRaw = itsRaw.getSampleModel();
774
775                // Get the number of samples per pixel and the number
776                // of color components.
777                int numBands = smRaw.getNumBands();
778                int numComponents = iccColorSpace.getNumComponents();
779
780                // Replace the ColorModel with the ICC ColorModel if the
781                // numbers of samples and color components are amenable.
782                if(numBands == numComponents ||
783                   numBands == numComponents + 1) {
784                    // Set alpha flags.
785                    boolean hasAlpha = numComponents != numBands;
786                    boolean isAlphaPre =
787                        hasAlpha && cmRaw.isAlphaPremultiplied();
788
789                    // Create a ColorModel of the same class and with
790                    // the same transfer type.
791                    ColorModel iccColorModel =
792                        new ComponentColorModel(iccColorSpace,
793                                                cmRaw.getComponentSize(),
794                                                hasAlpha,
795                                                isAlphaPre,
796                                                cmRaw.getTransparency(),
797                                                cmRaw.getTransferType());
798
799                    // Prepend the ICC profile-based ITS to the List. The
800                    // ColorModel and SampleModel are guaranteed to be
801                    // compatible as the old and new ColorModels are both
802                    // ComponentColorModels with the same transfer type
803                    // and the same number of components.
804                    l.add(new ImageTypeSpecifier(iccColorModel, smRaw));
805
806                    // Append the raw ITS to the List if and only if its
807                    // ColorSpace has the same type and number of components
808                    // as the ICC ColorSpace.
809                    if(csRaw.getType() == iccColorSpace.getType() &&
810                       csRaw.getNumComponents() ==
811                       iccColorSpace.getNumComponents()) {
812                        l.add(itsRaw);
813                    }
814                } else { // ICCProfile not compatible with SampleModel.
815                    // Append the raw ITS to the List.
816                    l.add(itsRaw);
817                }
818            } else { // No ICCProfile field or raw ColorModel not component.
819                // Append the raw ITS to the List.
820                l.add(itsRaw);
821            }
822
823            // Cache the ITS List.
824            imageTypeMap.put(imageIndexInteger, l);
825        }
826
827        return l.iterator();
828    }
829
830    public IIOMetadata getImageMetadata(int imageIndex) throws IIOException {
831        seekToImage(imageIndex);
832        TIFFImageMetadata im =
833            new TIFFImageMetadata(imageMetadata.getRootIFD().getTagSetList());
834        Node root =
835            imageMetadata.getAsTree(TIFFImageMetadata.nativeMetadataFormatName);
836        im.setFromTree(TIFFImageMetadata.nativeMetadataFormatName, root);
837        return im;
838    }
839
840    public IIOMetadata getStreamMetadata(int imageIndex) throws IIOException {
841        readHeader();
842        TIFFStreamMetadata sm = new TIFFStreamMetadata();
843        Node root = sm.getAsTree(TIFFStreamMetadata.nativeMetadataFormatName);
844        sm.setFromTree(TIFFStreamMetadata.nativeMetadataFormatName, root);
845        return sm;
846    }
847
848    public boolean isRandomAccessEasy(int imageIndex) throws IOException {
849        if(currIndex != -1) {
850            seekToImage(currIndex);
851            return getCompression() == BaselineTIFFTagSet.COMPRESSION_NONE;
852        } else {
853            return false;
854        }
855    }
856
857    // Thumbnails
858
859    public boolean readSupportsThumbnails() {
860        return false;
861    }
862
863    public boolean hasThumbnails(int imageIndex) {
864        return false;
865    }
866
867    public int getNumThumbnails(int imageIndex) throws IOException {
868        return 0;
869    }
870
871    public ImageReadParam getDefaultReadParam() {
872        return new TIFFImageReadParam();
873    }
874
875    public boolean isImageTiled(int imageIndex) throws IOException {
876        seekToImage(imageIndex);
877
878        TIFFField f =
879            imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH);
880        return f != null;
881    }
882
883    public int getTileWidth(int imageIndex) throws IOException {
884        seekToImage(imageIndex);
885        return getTileOrStripWidth();
886    }
887
888    public int getTileHeight(int imageIndex) throws IOException {
889        seekToImage(imageIndex);
890        return getTileOrStripHeight();
891    }
892
893    public BufferedImage readTile(int imageIndex, int tileX, int tileY)
894        throws IOException {
895
896        int w = getWidth(imageIndex);
897        int h = getHeight(imageIndex);
898        int tw = getTileWidth(imageIndex);
899        int th = getTileHeight(imageIndex);
900
901        int x = tw*tileX;
902        int y = th*tileY;
903
904        if(tileX < 0 || tileY < 0 || x >= w || y >= h) {
905            throw new IllegalArgumentException
906                ("Tile indices are out of bounds!");
907        }
908
909        if (x + tw > w) {
910            tw = w - x;
911        }
912
913        if (y + th > h) {
914            th = h - y;
915        }
916
917        ImageReadParam param = getDefaultReadParam();
918        Rectangle tileRect = new Rectangle(x, y, tw, th);
919        param.setSourceRegion(tileRect);
920
921        return read(imageIndex, param);
922    }
923
924    public boolean canReadRaster() {
925        // Enable this?
926        return false;
927    }
928
929    public Raster readRaster(int imageIndex, ImageReadParam param)
930        throws IOException {
931        // Enable this?
932        throw new UnsupportedOperationException();
933    }
934
935//     public BufferedImage readTileRaster(int imageIndex,
936//                                         int tileX, int tileY)
937//         throws IOException {
938//     }
939
940    private int[] sourceBands;
941    private int[] destinationBands;
942
943    private TIFFDecompressor decompressor;
944
945    // floor(num/den)
946    private static int ifloor(int num, int den) {
947        if (num < 0) {
948            num -= den - 1;
949        }
950        return num/den;
951    }
952
953    // ceil(num/den)
954    private static int iceil(int num, int den) {
955        if (num > 0) {
956            num += den - 1;
957        }
958        return num/den;
959    }
960
961    private void prepareRead(int imageIndex, ImageReadParam param)
962        throws IOException {
963        if (stream == null) {
964            throw new IllegalStateException("Input not set!");
965        }
966
967        // A null ImageReadParam means we use the default
968        if (param == null) {
969            param = getDefaultReadParam();
970        }
971
972        this.imageReadParam = param;
973
974        seekToImage(imageIndex);
975
976        this.tileOrStripWidth = getTileOrStripWidth();
977        this.tileOrStripHeight = getTileOrStripHeight();
978        this.planarConfiguration = getPlanarConfiguration();
979
980        this.sourceBands = param.getSourceBands();
981        if (sourceBands == null) {
982            sourceBands = new int[numBands];
983            for (int i = 0; i < numBands; i++) {
984                sourceBands[i] = i;
985            }
986        }
987
988        // Initialize the destination image
989        Iterator imageTypes = getImageTypes(imageIndex);
990        ImageTypeSpecifier theImageType =
991            ImageUtil.getDestinationType(param, imageTypes);
992
993        int destNumBands = theImageType.getSampleModel().getNumBands();
994
995        this.destinationBands = param.getDestinationBands();
996        if (destinationBands == null) {
997            destinationBands = new int[destNumBands];
998            for (int i = 0; i < destNumBands; i++) {
999                destinationBands[i] = i;
1000            }
1001        }
1002
1003        if (sourceBands.length != destinationBands.length) {
1004            throw new IllegalArgumentException(
1005                              "sourceBands.length != destinationBands.length");
1006        }
1007
1008        for (int i = 0; i < sourceBands.length; i++) {
1009            int sb = sourceBands[i];
1010            if (sb < 0 || sb >= numBands) {
1011                throw new IllegalArgumentException(
1012                                                  "Source band out of range!");
1013            }
1014            int db = destinationBands[i];
1015            if (db < 0 || db >= destNumBands) {
1016                throw new IllegalArgumentException(
1017                                             "Destination band out of range!");
1018            }
1019        }
1020    }
1021
1022    public RenderedImage readAsRenderedImage(int imageIndex,
1023                                             ImageReadParam param)
1024        throws IOException {
1025        prepareRead(imageIndex, param);
1026        return new TIFFRenderedImage(this, imageIndex, imageReadParam,
1027                                     width, height);
1028    }
1029
1030    private void decodeTile(int ti, int tj, int band) throws IOException {
1031        if(DEBUG) {
1032            System.out.println("decodeTile("+ti+","+tj+","+band+")");
1033        }
1034
1035        // Compute the region covered by the strip or tile
1036        Rectangle tileRect = new Rectangle(ti*tileOrStripWidth,
1037                                           tj*tileOrStripHeight,
1038                                           tileOrStripWidth,
1039                                           tileOrStripHeight);
1040
1041        // Clip against the image bounds if the image is not tiled. If it
1042        // is tiled, the tile may legally extend beyond the image bounds.
1043        if(!isImageTiled(currIndex)) {
1044            tileRect =
1045                tileRect.intersection(new Rectangle(0, 0, width, height));
1046        }
1047
1048        // Return if the intersection is empty.
1049        if(tileRect.width <= 0 || tileRect.height <= 0) {
1050            return;
1051        }
1052        
1053        int srcMinX = tileRect.x;
1054        int srcMinY = tileRect.y;
1055        int srcWidth = tileRect.width;
1056        int srcHeight = tileRect.height;
1057
1058        // Determine dest region that can be derived from the
1059        // source region
1060        
1061        dstMinX = iceil(srcMinX - sourceXOffset, srcXSubsampling);
1062        int dstMaxX = ifloor(srcMinX + srcWidth - 1 - sourceXOffset,
1063                         srcXSubsampling);
1064        
1065        dstMinY = iceil(srcMinY - sourceYOffset, srcYSubsampling);
1066        int dstMaxY = ifloor(srcMinY + srcHeight - 1 - sourceYOffset,
1067                             srcYSubsampling);
1068        
1069        dstWidth = dstMaxX - dstMinX + 1;
1070        dstHeight = dstMaxY - dstMinY + 1;
1071        
1072        dstMinX += dstXOffset;
1073        dstMinY += dstYOffset;
1074        
1075        // Clip against image bounds
1076        
1077        Rectangle dstRect = new Rectangle(dstMinX, dstMinY,
1078                                          dstWidth, dstHeight);
1079        dstRect =
1080            dstRect.intersection(theImage.getRaster().getBounds());
1081        
1082        dstMinX = dstRect.x;
1083        dstMinY = dstRect.y;
1084        dstWidth = dstRect.width;
1085        dstHeight = dstRect.height;
1086        
1087        if (dstWidth <= 0 || dstHeight <= 0) {
1088            return;
1089        }
1090        
1091        // Backwards map dest region to source to determine
1092        // active source region
1093        
1094        int activeSrcMinX = (dstMinX - dstXOffset)*srcXSubsampling +
1095            sourceXOffset;
1096        int sxmax = 
1097            (dstMinX + dstWidth - 1 - dstXOffset)*srcXSubsampling +
1098            sourceXOffset;
1099        int activeSrcWidth = sxmax - activeSrcMinX + 1;
1100        
1101        int activeSrcMinY = (dstMinY - dstYOffset)*srcYSubsampling +
1102            sourceYOffset;
1103        int symax =
1104            (dstMinY + dstHeight - 1 - dstYOffset)*srcYSubsampling +
1105            sourceYOffset;
1106        int activeSrcHeight = symax - activeSrcMinY + 1;
1107        
1108        decompressor.setSrcMinX(srcMinX);
1109        decompressor.setSrcMinY(srcMinY);
1110        decompressor.setSrcWidth(srcWidth);
1111        decompressor.setSrcHeight(srcHeight);
1112        
1113        decompressor.setDstMinX(dstMinX);
1114        decompressor.setDstMinY(dstMinY);
1115        decompressor.setDstWidth(dstWidth);
1116        decompressor.setDstHeight(dstHeight);
1117        
1118        decompressor.setActiveSrcMinX(activeSrcMinX);
1119        decompressor.setActiveSrcMinY(activeSrcMinY);
1120        decompressor.setActiveSrcWidth(activeSrcWidth);
1121        decompressor.setActiveSrcHeight(activeSrcHeight);
1122
1123        int tileIndex = tj*tilesAcross + ti;
1124
1125        if (planarConfiguration ==
1126            BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) {
1127            tileIndex += band*tilesAcross*tilesDown;
1128        }
1129        
1130        long offset = getTileOrStripOffset(tileIndex);
1131        long byteCount = getTileOrStripByteCount(tileIndex);
1132
1133        //
1134        // Attempt to handle truncated streams, i.e., where reading the
1135        // compressed strip or tile would result in an EOFException. The
1136        // number of bytes to read is clamped to the number available
1137        // from the stream starting at the indicated position in the hope
1138        // that the decompressor will handle it.
1139        //
1140        long streamLength = stream.length();
1141        if(streamLength > 0 && offset + byteCount > streamLength) {
1142            processWarningOccurred("Attempting to process truncated stream.");
1143            if(Math.max(byteCount = streamLength - offset, 0) == 0) {
1144                processWarningOccurred("No bytes in strip/tile: skipping.");
1145                return;
1146            }
1147        }
1148
1149        decompressor.setStream(stream);
1150        decompressor.setOffset(offset);
1151        decompressor.setByteCount((int)byteCount);
1152        
1153        decompressor.beginDecoding();
1154
1155        stream.mark();
1156        decompressor.decode();
1157        stream.reset();
1158    }
1159
1160    private void reportProgress() {
1161        // Report image progress/update to listeners after each tile
1162        pixelsRead += dstWidth*dstHeight;
1163        processImageProgress(100.0f*pixelsRead/pixelsToRead);
1164        processImageUpdate(theImage,
1165                           dstMinX, dstMinY, dstWidth, dstHeight,
1166                           1, 1,
1167                           destinationBands);
1168    }
1169
1170    public BufferedImage read(int imageIndex, ImageReadParam param)
1171        throws IOException {
1172        prepareRead(imageIndex, param);
1173        this.theImage = getDestination(param,
1174                                       getImageTypes(imageIndex),
1175                                       width, height);
1176
1177        srcXSubsampling = imageReadParam.getSourceXSubsampling();
1178        srcYSubsampling = imageReadParam.getSourceYSubsampling();
1179
1180        Point p = imageReadParam.getDestinationOffset();
1181        dstXOffset = p.x;
1182        dstYOffset = p.y;
1183
1184        // This could probably be made more efficient...
1185        Rectangle srcRegion = new Rectangle(0, 0, 0, 0);
1186        Rectangle destRegion = new Rectangle(0, 0, 0, 0);
1187
1188        computeRegions(imageReadParam, width, height, theImage,
1189                       srcRegion, destRegion);
1190
1191        // Initial source pixel, taking source region and source
1192        // subsamplimg offsets into account
1193        sourceXOffset = srcRegion.x;
1194        sourceYOffset = srcRegion.y;
1195
1196        pixelsToRead = destRegion.width*destRegion.height;
1197        pixelsRead = 0;
1198
1199        processImageStarted(imageIndex);
1200        processImageProgress(0.0f);
1201
1202        tilesAcross = (width + tileOrStripWidth - 1)/tileOrStripWidth;
1203        tilesDown = (height + tileOrStripHeight - 1)/tileOrStripHeight;
1204
1205        int compression = getCompression();
1206
1207        // Attempt to get decompressor and color converted from the read param
1208        
1209        TIFFColorConverter colorConverter = null;
1210        if (imageReadParam instanceof TIFFImageReadParam) {
1211            TIFFImageReadParam tparam =
1212                (TIFFImageReadParam)imageReadParam;
1213            this.decompressor = tparam.getTIFFDecompressor();
1214            colorConverter = tparam.getColorConverter();
1215        }
1216
1217        // If we didn't find one, use a standard decompressor
1218        if (this.decompressor == null) {
1219            if (compression ==
1220                BaselineTIFFTagSet.COMPRESSION_NONE) {
1221                // Get the fillOrder field.
1222                TIFFField fillOrderField =
1223                    imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_FILL_ORDER);
1224
1225                // Set the decompressor based on the fill order.
1226                if(fillOrderField != null && fillOrderField.getAsInt(0) == 2) {
1227                    this.decompressor = new TIFFLSBDecompressor();
1228                } else {
1229                    this.decompressor = new TIFFNullDecompressor();
1230                }
1231            } else if (compression ==
1232                       BaselineTIFFTagSet.COMPRESSION_CCITT_T_6) {
1233
1234             
1235                // Fall back to the Java decompressor.
1236                if (this.decompressor == null) {
1237                    if(DEBUG) {
1238                        System.out.println("Using Java T.6 decompressor");
1239                    }
1240                    this.decompressor = new TIFFFaxDecompressor();
1241                }
1242            } else if (compression ==
1243                       BaselineTIFFTagSet.COMPRESSION_CCITT_T_4) {
1244
1245                   // Fall back to the Java decompressor.
1246                if (this.decompressor == null) {
1247                    if(DEBUG) {
1248                        System.out.println("Using Java T.4 decompressor");
1249                    }
1250                    this.decompressor = new TIFFFaxDecompressor();
1251                }
1252            } else if (compression ==
1253                       BaselineTIFFTagSet.COMPRESSION_CCITT_RLE) {
1254                this.decompressor = new TIFFFaxDecompressor();
1255            } else if (compression ==
1256                       BaselineTIFFTagSet.COMPRESSION_PACKBITS) {
1257                if(DEBUG) {
1258                    System.out.println("Using TIFFPackBitsDecompressor");
1259                }
1260                this.decompressor = new TIFFPackBitsDecompressor();
1261            } else if (compression ==
1262                       BaselineTIFFTagSet.COMPRESSION_LZW) {
1263                if(DEBUG) {
1264                    System.out.println("Using TIFFLZWDecompressor");
1265                }
1266                TIFFField predictorField =
1267                    imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PREDICTOR);
1268                int predictor = ((predictorField == null) ?
1269                                 BaselineTIFFTagSet.PREDICTOR_NONE :
1270                                 predictorField.getAsInt(0));
1271                this.decompressor = new TIFFLZWDecompressor(predictor);
1272            } else if (compression ==
1273                       BaselineTIFFTagSet.COMPRESSION_JPEG) {
1274                this.decompressor = new TIFFJPEGDecompressor();
1275            } else if (compression ==
1276                       BaselineTIFFTagSet.COMPRESSION_ZLIB ||
1277                       compression ==
1278                       BaselineTIFFTagSet.COMPRESSION_DEFLATE) {
1279                TIFFField predictorField =
1280                    imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PREDICTOR);
1281                int predictor = ((predictorField == null) ?
1282                                 BaselineTIFFTagSet.PREDICTOR_NONE :
1283                                 predictorField.getAsInt(0));
1284                this.decompressor = new TIFFDeflateDecompressor(predictor);
1285            } else if (compression ==
1286                       BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
1287                TIFFField JPEGProcField =
1288                    imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_PROC);
1289                if(JPEGProcField == null) {
1290                    processWarningOccurred
1291                        ("JPEGProc field missing; assuming baseline sequential JPEG process.");
1292                } else if(JPEGProcField.getAsInt(0) !=
1293                   BaselineTIFFTagSet.JPEG_PROC_BASELINE) {
1294                    throw new IIOException
1295                        ("Old-style JPEG supported for baseline sequential JPEG process only!");
1296                }
1297                this.decompressor = new TIFFOldJPEGDecompressor();
1298                //throw new IIOException("Old-style JPEG not supported!");
1299            } else {
1300                throw new IIOException
1301                    ("Unsupported compression type (tag number = "+
1302                     compression+")!");
1303            }
1304
1305            if (photometricInterpretation ==
1306                BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR &&
1307                compression != BaselineTIFFTagSet.COMPRESSION_JPEG &&
1308                compression != BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
1309                boolean convertYCbCrToRGB =
1310                    theImage.getColorModel().getColorSpace().getType() ==
1311                    ColorSpace.TYPE_RGB;
1312                TIFFDecompressor wrappedDecompressor =
1313                    this.decompressor instanceof TIFFNullDecompressor ?
1314                    null : this.decompressor;
1315                this.decompressor =
1316                    new TIFFYCbCrDecompressor(wrappedDecompressor,
1317                                              convertYCbCrToRGB);
1318            }
1319        }
1320
1321        if(DEBUG) {
1322            System.out.println("\nDecompressor class = "+
1323                               decompressor.getClass().getName()+"\n");
1324        }
1325
1326        if (colorConverter == null) {
1327            if (photometricInterpretation ==
1328                BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CIELAB &&
1329                theImage.getColorModel().getColorSpace().getType() ==
1330                ColorSpace.TYPE_RGB) {
1331                colorConverter = new TIFFCIELabColorConverter();
1332             } else if (photometricInterpretation ==
1333                        BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR &&
1334                        !(this.decompressor instanceof TIFFYCbCrDecompressor) &&
1335                        compression != BaselineTIFFTagSet.COMPRESSION_JPEG &&
1336                        compression != BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
1337                 colorConverter = new TIFFYCbCrColorConverter(imageMetadata);
1338            }
1339        }
1340        
1341        decompressor.setReader(this);
1342        decompressor.setMetadata(imageMetadata);
1343        decompressor.setImage(theImage);
1344
1345        decompressor.setPhotometricInterpretation(photometricInterpretation);
1346        decompressor.setCompression(compression);
1347        decompressor.setSamplesPerPixel(samplesPerPixel);
1348        decompressor.setBitsPerSample(bitsPerSample);
1349        decompressor.setSampleFormat(sampleFormat);
1350        decompressor.setExtraSamples(extraSamples);
1351        decompressor.setColorMap(colorMap);
1352
1353        decompressor.setColorConverter(colorConverter);
1354
1355        decompressor.setSourceXOffset(sourceXOffset);
1356        decompressor.setSourceYOffset(sourceYOffset);
1357        decompressor.setSubsampleX(srcXSubsampling);
1358        decompressor.setSubsampleY(srcYSubsampling);
1359
1360        decompressor.setDstXOffset(dstXOffset);
1361        decompressor.setDstYOffset(dstYOffset);
1362
1363        decompressor.setSourceBands(sourceBands);
1364        decompressor.setDestinationBands(destinationBands);
1365
1366        // Compute bounds on the tile indices for this source region.
1367        int minTileX =
1368            TIFFImageWriter.XToTileX(srcRegion.x, 0, tileOrStripWidth);
1369        int minTileY =
1370            TIFFImageWriter.YToTileY(srcRegion.y, 0, tileOrStripHeight);
1371        int maxTileX =
1372            TIFFImageWriter.XToTileX(srcRegion.x + srcRegion.width - 1,
1373                                     0, tileOrStripWidth);
1374        int maxTileY =
1375            TIFFImageWriter.YToTileY(srcRegion.y + srcRegion.height - 1,
1376                                     0, tileOrStripHeight);
1377
1378        boolean isAbortRequested = false;
1379        if (planarConfiguration ==
1380            BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) {
1381            
1382            decompressor.setPlanar(true);
1383            
1384            int[] sb = new int[1];
1385            int[] db = new int[1];
1386            for (int tj = minTileY; tj <= maxTileY; tj++) {
1387                for (int ti = minTileX; ti <= maxTileX; ti++) {
1388                    for (int band = 0; band < numBands; band++) {
1389                        sb[0] = sourceBands[band];
1390                        decompressor.setSourceBands(sb);
1391                        db[0] = destinationBands[band];
1392                        decompressor.setDestinationBands(db);
1393                        //XXX decompressor.beginDecoding();
1394
1395                        // The method abortRequested() is synchronized
1396                        // so check it only once per loop just before
1397                        // doing any actual decoding.
1398                        if(abortRequested()) {
1399                            isAbortRequested = true;
1400                            break;
1401                        }
1402
1403                        decodeTile(ti, tj, band);
1404                    }
1405
1406                    if(isAbortRequested) break;
1407
1408                    reportProgress();
1409                }
1410
1411                if(isAbortRequested) break;
1412            }
1413        } else {
1414            //XXX decompressor.beginDecoding();
1415
1416            for (int tj = minTileY; tj <= maxTileY; tj++) {
1417                for (int ti = minTileX; ti <= maxTileX; ti++) {
1418                    // The method abortRequested() is synchronized
1419                    // so check it only once per loop just before
1420                    // doing any actual decoding.
1421                    if(abortRequested()) {
1422                        isAbortRequested = true;
1423                        break;
1424                    }
1425
1426                    decodeTile(ti, tj, -1);
1427
1428                    reportProgress();
1429                }
1430
1431                if(isAbortRequested) break;
1432            }
1433        }
1434
1435        if (isAbortRequested) {
1436            processReadAborted();
1437        } else {
1438            processImageComplete();
1439        }
1440
1441        return theImage;
1442    }
1443
1444    public void reset() {
1445        super.reset();
1446        resetLocal();
1447    }
1448
1449    protected void resetLocal() {
1450        stream = null;
1451        gotHeader = false;
1452        imageReadParam = getDefaultReadParam();
1453        streamMetadata = null;
1454        currIndex = -1;
1455        imageMetadata = null;
1456        imageStartPosition = new ArrayList();
1457        numImages = -1;
1458        imageTypeMap = new HashMap();
1459        width = -1;
1460        height = -1;
1461        numBands = -1;
1462        tileOrStripWidth = -1;
1463        tileOrStripHeight = -1;
1464        planarConfiguration = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
1465        rowsDone = 0;
1466    }
1467
1468    /**
1469     * Package scope method to allow decompressors, for example, to
1470     * emit warning messages.
1471     */
1472    void forwardWarningMessage(String warning) {
1473        processWarningOccurred(warning);
1474    }
1475    
1476    protected static BufferedImage getDestination(ImageReadParam param,
1477                                                  Iterator imageTypes,
1478                                                  int width, int height)
1479            throws IIOException {
1480        if (imageTypes == null || !imageTypes.hasNext()) {
1481            throw new IllegalArgumentException("imageTypes null or empty!");
1482        }        
1483        
1484        BufferedImage dest = null;
1485        ImageTypeSpecifier imageType = null;
1486
1487        // If param is non-null, use it
1488        if (param != null) {
1489            // Try to get the image itself
1490            dest = param.getDestination();
1491            if (dest != null) {
1492                return dest;
1493            }
1494        
1495            // No image, get the image type
1496            imageType = param.getDestinationType();
1497        }
1498
1499        // No info from param, use fallback image type
1500        if (imageType == null) {
1501            Object o = imageTypes.next();
1502            if (!(o instanceof ImageTypeSpecifier)) {
1503                throw new IllegalArgumentException
1504                    ("Non-ImageTypeSpecifier retrieved from imageTypes!");
1505            }
1506            imageType = (ImageTypeSpecifier)o;
1507        } else {
1508            boolean foundIt = false;
1509            while (imageTypes.hasNext()) {
1510                ImageTypeSpecifier type =
1511                    (ImageTypeSpecifier)imageTypes.next();
1512                if (type.equals(imageType)) {
1513                    foundIt = true;
1514                    break;
1515                }
1516            }
1517
1518            if (!foundIt) {
1519                throw new IIOException
1520                    ("Destination type from ImageReadParam does not match!");
1521            }
1522        }
1523
1524        Rectangle srcRegion = new Rectangle(0,0,0,0);
1525        Rectangle destRegion = new Rectangle(0,0,0,0);
1526        computeRegions(param,
1527                       width,
1528                       height,
1529                       null,
1530                       srcRegion,
1531                       destRegion);
1532        
1533        int destWidth = destRegion.x + destRegion.width;
1534        int destHeight = destRegion.y + destRegion.height;
1535        // Create a new image based on the type specifier
1536        
1537        if ((long)destWidth*destHeight > Integer.MAX_VALUE) {
1538            throw new IllegalArgumentException
1539                ("width*height > Integer.MAX_VALUE!");
1540        }
1541        
1542        return imageType.createBufferedImage(destWidth, destHeight);
1543    }    
1544}