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