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 }