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