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