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