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