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