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