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