001/* 002 * $RCSfile: TIFFImageReader.java,v $ 003 * 004 * 005 * Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved. 006 * 007 * Redistribution and use in source and binary forms, with or without 008 * modification, are permitted provided that the following conditions 009 * are met: 010 * 011 * - Redistribution of source code must retain the above copyright 012 * notice, this list of conditions and the following disclaimer. 013 * 014 * - Redistribution in binary form must reproduce the above copyright 015 * notice, this list of conditions and the following disclaimer in 016 * the documentation and/or other materials provided with the 017 * distribution. 018 * 019 * Neither the name of Sun Microsystems, Inc. or the names of 020 * contributors may be used to endorse or promote products derived 021 * from this software without specific prior written permission. 022 * 023 * This software is provided "AS IS," without a warranty of any 024 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 025 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 026 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY 027 * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL 028 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 029 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS 030 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR 031 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, 032 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND 033 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR 034 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE 035 * POSSIBILITY OF SUCH DAMAGES. 036 * 037 * You acknowledge that this software is not designed or intended for 038 * use in the design, construction, operation or maintenance of any 039 * nuclear facility. 040 * 041 * $Revision: 1.13 $ 042 * $Date: 2007/12/19 20:17:02 $ 043 * $State: Exp $ 044 */ 045package com.github.jaiimageio.impl.plugins.tiff; 046 047import java.awt.Point; 048import java.awt.Rectangle; 049import java.awt.color.ColorSpace; 050import java.awt.color.ICC_ColorSpace; 051import java.awt.color.ICC_Profile; 052import java.awt.image.BufferedImage; 053import java.awt.image.ColorModel; 054import java.awt.image.ComponentColorModel; 055import java.awt.image.ComponentSampleModel; 056import java.awt.image.DataBuffer; 057import java.awt.image.DataBufferByte; 058import java.awt.image.MultiPixelPackedSampleModel; 059import java.awt.image.Raster; 060import java.awt.image.RenderedImage; 061import java.awt.image.SampleModel; 062import java.awt.image.SinglePixelPackedSampleModel; 063import java.awt.image.WritableRaster; 064import java.io.ByteArrayInputStream; 065import java.io.IOException; 066import java.io.InputStream; 067import java.nio.ByteOrder; 068import java.util.ArrayList; 069import java.util.Arrays; 070import java.util.HashMap; 071import java.util.Iterator; 072import java.util.List; 073 074import javax.imageio.IIOException; 075import javax.imageio.ImageIO; 076import javax.imageio.ImageReader; 077import javax.imageio.ImageReadParam; 078import javax.imageio.ImageTypeSpecifier; 079import javax.imageio.metadata.IIOMetadata; 080import javax.imageio.spi.ImageReaderSpi; 081import javax.imageio.stream.ImageInputStream; 082import javax.imageio.stream.MemoryCacheImageInputStream; 083 084import org.w3c.dom.Node; 085 086import com.github.jaiimageio.impl.common.ImageUtil; 087import com.github.jaiimageio.impl.common.PackageUtil; 088import com.github.jaiimageio.plugins.tiff.BaselineTIFFTagSet; 089import com.github.jaiimageio.plugins.tiff.TIFFColorConverter; 090import com.github.jaiimageio.plugins.tiff.TIFFDecompressor; 091import com.github.jaiimageio.plugins.tiff.TIFFField; 092import com.github.jaiimageio.plugins.tiff.TIFFImageReadParam; 093import com.github.jaiimageio.plugins.tiff.TIFFTag; 094 095public class TIFFImageReader extends ImageReader { 096 097 private static final boolean DEBUG = false; // XXX 'false' for release!!! 098 099 // The current ImageInputStream source. 100 ImageInputStream stream = null; 101 102 // True if the file header has been read. 103 boolean gotHeader = false; 104 105 ImageReadParam imageReadParam = getDefaultReadParam(); 106 107 // Stream metadata, or null. 108 TIFFStreamMetadata streamMetadata = null; 109 110 // The current image index. 111 int currIndex = -1; 112 113 // Metadata for image at 'currIndex', or null. 114 TIFFImageMetadata imageMetadata = null; 115 116 // A <code>List</code> of <code>Long</code>s indicating the stream 117 // positions of the start of the IFD for each image. Entries 118 // are added as needed. 119 List imageStartPosition = new ArrayList(); 120 121 // The number of images in the stream, if known, otherwise -1. 122 int numImages = -1; 123 124 // The ImageTypeSpecifiers of the images in the stream. 125 // Contains a map of Integers to Lists. 126 HashMap imageTypeMap = new HashMap(); 127 128 BufferedImage theImage = null; 129 130 int width = -1; 131 int height = -1; 132 int numBands = -1; 133 int tileOrStripWidth = -1, tileOrStripHeight = -1; 134 135 int planarConfiguration = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY; 136 137 int rowsDone = 0; 138 139 int compression; 140 int photometricInterpretation; 141 int samplesPerPixel; 142 int[] sampleFormat; 143 int[] bitsPerSample; 144 int[] extraSamples; 145 char[] colorMap; 146 147 int sourceXOffset; 148 int sourceYOffset; 149 int srcXSubsampling; 150 int srcYSubsampling; 151 152 int dstWidth; 153 int dstHeight; 154 int dstMinX; 155 int dstMinY; 156 int dstXOffset; 157 int dstYOffset; 158 159 int tilesAcross; 160 int tilesDown; 161 162 int pixelsRead; 163 int pixelsToRead; 164 165 public TIFFImageReader(ImageReaderSpi originatingProvider) { 166 super(originatingProvider); 167 } 168 169 public void setInput(Object input, 170 boolean seekForwardOnly, 171 boolean ignoreMetadata) { 172 super.setInput(input, seekForwardOnly, ignoreMetadata); 173 174 // Clear all local values based on the previous stream contents. 175 resetLocal(); 176 177 if (input != null) { 178 if (!(input instanceof ImageInputStream)) { 179 throw new IllegalArgumentException 180 ("input not an ImageInputStream!"); 181 } 182 this.stream = (ImageInputStream)input; 183 } else { 184 this.stream = null; 185 } 186 } 187 188 // Do not seek to the beginning of the stream so as to allow users to 189 // point us at an IFD within some other file format 190 private void readHeader() throws IIOException { 191 if (gotHeader) { 192 return; 193 } 194 if (stream == null) { 195 throw new IllegalStateException("Input not set!"); 196 } 197 198 // Create an object to store the stream metadata 199 this.streamMetadata = new TIFFStreamMetadata(); 200 201 try { 202 int byteOrder = stream.readUnsignedShort(); 203 if (byteOrder == 0x4d4d) { 204 streamMetadata.byteOrder = ByteOrder.BIG_ENDIAN; 205 stream.setByteOrder(ByteOrder.BIG_ENDIAN); 206 } else if (byteOrder == 0x4949) { 207 streamMetadata.byteOrder = ByteOrder.LITTLE_ENDIAN; 208 stream.setByteOrder(ByteOrder.LITTLE_ENDIAN); 209 } else { 210 processWarningOccurred( 211 "Bad byte order in header, assuming little-endian"); 212 streamMetadata.byteOrder = ByteOrder.LITTLE_ENDIAN; 213 stream.setByteOrder(ByteOrder.LITTLE_ENDIAN); 214 } 215 216 int magic = stream.readUnsignedShort(); 217 if (magic != 42) { 218 processWarningOccurred( 219 "Bad magic number in header, continuing"); 220 } 221 222 // Seek to start of first IFD 223 long offset = stream.readUnsignedInt(); 224 imageStartPosition.add(new Long(offset)); 225 stream.seek(offset); 226 } catch (IOException e) { 227 throw new IIOException("I/O error reading header!", e); 228 } 229 230 gotHeader = true; 231 } 232 233 private int locateImage(int imageIndex) throws IIOException { 234 readHeader(); 235 236 try { 237 // Find closest known index 238 int index = Math.min(imageIndex, imageStartPosition.size() - 1); 239 240 // Seek to that position 241 Long l = (Long)imageStartPosition.get(index); 242 stream.seek(l.longValue()); 243 244 // Skip IFDs until at desired index or last image found 245 while (index < imageIndex) { 246 int count = stream.readUnsignedShort(); 247 stream.skipBytes(12*count); 248 249 long offset = stream.readUnsignedInt(); 250 if (offset == 0) { 251 return index; 252 } 253 254 imageStartPosition.add(new Long(offset)); 255 stream.seek(offset); 256 ++index; 257 } 258 } catch (IOException e) { 259 throw new IIOException("Couldn't seek!", e); 260 } 261 262 if (currIndex != imageIndex) { 263 imageMetadata = null; 264 } 265 currIndex = imageIndex; 266 return imageIndex; 267 } 268 269 public int getNumImages(boolean allowSearch) throws IOException { 270 if (stream == null) { 271 throw new IllegalStateException("Input not set!"); 272 } 273 if (seekForwardOnly && allowSearch) { 274 throw new IllegalStateException 275 ("seekForwardOnly and allowSearch can't both be true!"); 276 } 277 278 if (numImages > 0) { 279 return numImages; 280 } 281 if (allowSearch) { 282 this.numImages = locateImage(Integer.MAX_VALUE) + 1; 283 } 284 return numImages; 285 } 286 287 public IIOMetadata getStreamMetadata() throws IIOException { 288 readHeader(); 289 return streamMetadata; 290 } 291 292 // Throw an IndexOutOfBoundsException if index < minIndex, 293 // and bump minIndex if required. 294 private void checkIndex(int imageIndex) { 295 if (imageIndex < minIndex) { 296 throw new IndexOutOfBoundsException("imageIndex < minIndex!"); 297 } 298 if (seekForwardOnly) { 299 minIndex = imageIndex; 300 } 301 } 302 303 // Verify that imageIndex is in bounds, find the image IFD, read the 304 // image metadata, initialize instance variables from the metadata. 305 private void seekToImage(int imageIndex) throws IIOException { 306 checkIndex(imageIndex); 307 308 int index = locateImage(imageIndex); 309 if (index != imageIndex) { 310 throw new IndexOutOfBoundsException("imageIndex out of bounds!"); 311 } 312 313 readMetadata(); 314 315 initializeFromMetadata(); 316 } 317 318 // Stream must be positioned at start of IFD for 'currIndex' 319 private void readMetadata() throws IIOException { 320 if (stream == null) { 321 throw new IllegalStateException("Input not set!"); 322 } 323 324 if (imageMetadata != null) { 325 return; 326 } 327 try { 328 // Create an object to store the image metadata 329 List tagSets; 330 if (imageReadParam instanceof TIFFImageReadParam) { 331 tagSets = 332 ((TIFFImageReadParam)imageReadParam).getAllowedTagSets(); 333 } else { 334 tagSets = new ArrayList(1); 335 tagSets.add(BaselineTIFFTagSet.getInstance()); 336 } 337 338 this.imageMetadata = new TIFFImageMetadata(tagSets); 339 imageMetadata.initializeFromStream(stream, ignoreMetadata); 340 } catch (IIOException iioe) { 341 throw iioe; 342 } catch (IOException ioe) { 343 throw new IIOException("I/O error reading image metadata!", ioe); 344 } 345 } 346 347 private int getWidth() { 348 return this.width; 349 } 350 351 private int getHeight() { 352 return this.height; 353 } 354 355 private int getNumBands() { 356 return this.numBands; 357 } 358 359 // Returns tile width if image is tiled, else image width 360 private int getTileOrStripWidth() { 361 TIFFField f = 362 imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH); 363 return (f == null) ? getWidth() : f.getAsInt(0); 364 } 365 366 // Returns tile height if image is tiled, else strip height 367 private int getTileOrStripHeight() { 368 TIFFField f = 369 imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_LENGTH); 370 if (f != null) { 371 return f.getAsInt(0); 372 } 373 374 f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP); 375 // Default for ROWS_PER_STRIP is 2^32 - 1, i.e., infinity 376 int h = (f == null) ? -1 : f.getAsInt(0); 377 return (h == -1) ? getHeight() : h; 378 } 379 380 private int getPlanarConfiguration() { 381 TIFFField f = 382 imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION); 383 if (f != null) { 384 int planarConfigurationValue = f.getAsInt(0); 385 if(planarConfigurationValue == 386 BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) { 387 // Some writers (e.g. Kofax standard Multi-Page TIFF 388 // Storage Filter v2.01.000; cf. bug 4929147) do not 389 // correctly set the value of this field. Attempt to 390 // ascertain whether the value is correctly Planar. 391 if(getCompression() == 392 BaselineTIFFTagSet.COMPRESSION_OLD_JPEG && 393 imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT) != 394 null) { 395 // JPEG interchange format cannot have 396 // PlanarConfiguration value Chunky so reset. 397 processWarningOccurred("PlanarConfiguration \"Planar\" value inconsistent with JPEGInterchangeFormat; resetting to \"Chunky\"."); 398 planarConfigurationValue = 399 BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY; 400 } else { 401 TIFFField offsetField = 402 imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS); 403 if (offsetField == null) { 404 // Tiles 405 offsetField = 406 imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS); 407 int tw = getTileOrStripWidth(); 408 int th = getTileOrStripHeight(); 409 int tAcross = (getWidth() + tw - 1)/tw; 410 int tDown = (getHeight() + th - 1)/th; 411 int tilesPerImage = tAcross*tDown; 412 long[] offsetArray = offsetField.getAsLongs(); 413 if(offsetArray != null && 414 offsetArray.length == tilesPerImage) { 415 // Length of offsets array is 416 // TilesPerImage for Chunky and 417 // SamplesPerPixel*TilesPerImage for Planar. 418 processWarningOccurred("PlanarConfiguration \"Planar\" value inconsistent with TileOffsets field value count; resetting to \"Chunky\"."); 419 planarConfigurationValue = 420 BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY; 421 } 422 } else { 423 // Strips 424 int rowsPerStrip = getTileOrStripHeight(); 425 int stripsPerImage = 426 (getHeight() + rowsPerStrip - 1)/rowsPerStrip; 427 long[] offsetArray = offsetField.getAsLongs(); 428 if(offsetArray != null && 429 offsetArray.length == stripsPerImage) { 430 // Length of offsets array is 431 // StripsPerImage for Chunky and 432 // SamplesPerPixel*StripsPerImage for Planar. 433 processWarningOccurred("PlanarConfiguration \"Planar\" value inconsistent with StripOffsets field value count; resetting to \"Chunky\"."); 434 planarConfigurationValue = 435 BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY; 436 } 437 } 438 } 439 } 440 return planarConfigurationValue; 441 } 442 443 return BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY; 444 } 445 446 private long getTileOrStripOffset(int tileIndex) throws IIOException { 447 TIFFField f = 448 imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS); 449 if (f == null) { 450 f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS); 451 } 452 if (f == null) { 453 f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT); 454 } 455 456 if(f == null) { 457 throw new IIOException 458 ("Missing required strip or tile offsets field."); 459 } 460 461 return f.getAsLong(tileIndex); 462 } 463 464 private long getTileOrStripByteCount(int tileIndex) throws IOException { 465 TIFFField f = 466 imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS); 467 if (f == null) { 468 f = 469 imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS); 470 } 471 if (f == null) { 472 f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH); 473 } 474 475 long tileOrStripByteCount; 476 if(f != null) { 477 tileOrStripByteCount = f.getAsLong(tileIndex); 478 } else { 479 processWarningOccurred("TIFF directory contains neither StripByteCounts nor TileByteCounts field: attempting to calculate from strip or tile width and height."); 480 481 // Initialize to number of bytes per strip or tile assuming 482 // no compression. 483 int bitsPerPixel = bitsPerSample[0]; 484 for(int i = 1; i < samplesPerPixel; i++) { 485 bitsPerPixel += bitsPerSample[i]; 486 } 487 int bytesPerRow = (getTileOrStripWidth()*bitsPerPixel + 7)/8; 488 tileOrStripByteCount = bytesPerRow*getTileOrStripHeight(); 489 490 // Clamp to end of stream if possible. 491 long streamLength = stream.length(); 492 if(streamLength != -1) { 493 tileOrStripByteCount = 494 Math.min(tileOrStripByteCount, 495 streamLength - getTileOrStripOffset(tileIndex)); 496 } else { 497 processWarningOccurred("Stream length is unknown: cannot clamp estimated strip or tile byte count to EOF."); 498 } 499 } 500 501 return tileOrStripByteCount; 502 } 503 504 private int getCompression() { 505 TIFFField f = 506 imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION); 507 if (f == null) { 508 return BaselineTIFFTagSet.COMPRESSION_NONE; 509 } else { 510 return f.getAsInt(0); 511 } 512 } 513 514 public int getWidth(int imageIndex) throws IOException { 515 seekToImage(imageIndex); 516 return getWidth(); 517 } 518 519 public int getHeight(int imageIndex) throws IOException { 520 seekToImage(imageIndex); 521 return getHeight(); 522 } 523 524 /** 525 * Initializes these instance variables from the image metadata: 526 * <pre> 527 * compression 528 * width 529 * height 530 * samplesPerPixel 531 * numBands 532 * colorMap 533 * photometricInterpretation 534 * sampleFormat 535 * bitsPerSample 536 * extraSamples 537 * tileOrStripWidth 538 * tileOrStripHeight 539 * </pre> 540 */ 541 private void initializeFromMetadata() { 542 TIFFField f; 543 544 // Compression 545 f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION); 546 if (f == null) { 547 processWarningOccurred 548 ("Compression field is missing; assuming no compression"); 549 compression = BaselineTIFFTagSet.COMPRESSION_NONE; 550 } else { 551 compression = f.getAsInt(0); 552 } 553 554 // Whether key dimensional information is absent. 555 boolean isMissingDimension = false; 556 557 // ImageWidth -> width 558 f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH); 559 if (f != null) { 560 this.width = f.getAsInt(0); 561 } else { 562 processWarningOccurred("ImageWidth field is missing."); 563 isMissingDimension = true; 564 } 565 566 // ImageLength -> height 567 f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH); 568 if (f != null) { 569 this.height = f.getAsInt(0); 570 } else { 571 processWarningOccurred("ImageLength field is missing."); 572 isMissingDimension = true; 573 } 574 575 // SamplesPerPixel 576 f = 577 imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL); 578 if (f != null) { 579 samplesPerPixel = f.getAsInt(0); 580 } else { 581 samplesPerPixel = 1; 582 isMissingDimension = true; 583 } 584 585 // If any dimension is missing and there is a JPEG stream available 586 // get the information from it. 587 int defaultBitDepth = 1; 588 if(isMissingDimension && 589 (f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT)) != null) { 590 Iterator iter = ImageIO.getImageReadersByFormatName("JPEG"); 591 if(iter != null && iter.hasNext()) { 592 ImageReader jreader = (ImageReader)iter.next(); 593 try { 594 stream.mark(); 595 stream.seek(f.getAsLong(0)); 596 jreader.setInput(stream); 597 if(imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH) == null) { 598 this.width = jreader.getWidth(0); 599 } 600 if(imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH) == null) { 601 this.height = jreader.getHeight(0); 602 } 603 ImageTypeSpecifier imageType = jreader.getRawImageType(0); 604 if(imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL) == null) { 605 this.samplesPerPixel = 606 imageType.getSampleModel().getNumBands(); 607 } 608 stream.reset(); 609 defaultBitDepth = 610 imageType.getColorModel().getComponentSize(0); 611 } catch(IOException e) { 612 // Ignore it and proceed: an error will occur later. 613 } 614 jreader.dispose(); 615 } 616 } 617 618 if (samplesPerPixel < 1) { 619 processWarningOccurred("Samples per pixel < 1!"); 620 } 621 622 // SamplesPerPixel -> numBands 623 numBands = samplesPerPixel; 624 625 // ColorMap 626 this.colorMap = null; 627 f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_COLOR_MAP); 628 if (f != null) { 629 // Grab color map 630 colorMap = f.getAsChars(); 631 } 632 633 // PhotometricInterpretation 634 f = 635 imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION); 636 if (f == null) { 637 if (compression == BaselineTIFFTagSet.COMPRESSION_CCITT_RLE || 638 compression == BaselineTIFFTagSet.COMPRESSION_CCITT_T_4 || 639 compression == BaselineTIFFTagSet.COMPRESSION_CCITT_T_6) { 640 processWarningOccurred 641 ("PhotometricInterpretation field is missing; "+ 642 "assuming WhiteIsZero"); 643 photometricInterpretation = 644 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO; 645 } else if(this.colorMap != null) { 646 photometricInterpretation = 647 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR; 648 } else if(samplesPerPixel == 3 || samplesPerPixel == 4) { 649 photometricInterpretation = 650 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB; 651 } else { 652 processWarningOccurred 653 ("PhotometricInterpretation field is missing; "+ 654 "assuming BlackIsZero"); 655 photometricInterpretation = 656 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO; 657 } 658 } else { 659 photometricInterpretation = f.getAsInt(0); 660 } 661 662 // SampleFormat 663 boolean replicateFirst = false; 664 int first = -1; 665 666 f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLE_FORMAT); 667 sampleFormat = new int[samplesPerPixel]; 668 replicateFirst = false; 669 if (f == null) { 670 replicateFirst = true; 671 first = BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED; 672 } else if (f.getCount() != samplesPerPixel) { 673 replicateFirst = true; 674 first = f.getAsInt(0); 675 } 676 677 for (int i = 0; i < samplesPerPixel; i++) { 678 sampleFormat[i] = replicateFirst ? first : f.getAsInt(i); 679 if (sampleFormat[i] != 680 BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER && 681 sampleFormat[i] != 682 BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER && 683 sampleFormat[i] != 684 BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT && 685 sampleFormat[i] != 686 BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED) { 687 processWarningOccurred( 688 "Illegal value for SAMPLE_FORMAT, assuming SAMPLE_FORMAT_UNDEFINED"); 689 sampleFormat[i] = BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED; 690 } 691 } 692 693 // BitsPerSample 694 f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE); 695 this.bitsPerSample = new int[samplesPerPixel]; 696 replicateFirst = false; 697 if (f == null) { 698 replicateFirst = true; 699 first = defaultBitDepth; 700 } else if (f.getCount() != samplesPerPixel) { 701 replicateFirst = true; 702 first = f.getAsInt(0); 703 } 704 705 for (int i = 0; i < samplesPerPixel; i++) { 706 // Replicate initial value if not enough values provided 707 bitsPerSample[i] = replicateFirst ? first : f.getAsInt(i); 708 709 if (DEBUG) { 710 System.out.println("bitsPerSample[" + i + "] = " 711 + bitsPerSample[i]); 712 } 713 } 714 715 // ExtraSamples 716 this.extraSamples = null; 717 f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES); 718 if (f != null) { 719 extraSamples = f.getAsInts(); 720 } 721 722// System.out.println("colorMap = " + colorMap); 723// if (colorMap != null) { 724// for (int i = 0; i < colorMap.length; i++) { 725// System.out.println("colorMap[" + i + "] = " + (int)(colorMap[i])); 726// } 727// } 728 729 } 730 731 public Iterator getImageTypes(int imageIndex) throws IIOException { 732 List l; // List of ImageTypeSpecifiers 733 734 Integer imageIndexInteger = new Integer(imageIndex); 735 if(imageTypeMap.containsKey(imageIndexInteger)) { 736 // Return the cached ITS List. 737 l = (List)imageTypeMap.get(imageIndexInteger); 738 } else { 739 // Create a new ITS List. 740 l = new ArrayList(1); 741 742 // Create the ITS and cache if for later use so that this method 743 // always returns an Iterator containing the same ITS objects. 744 seekToImage(imageIndex); 745 ImageTypeSpecifier itsRaw = 746 TIFFDecompressor.getRawImageTypeSpecifier 747 (photometricInterpretation, 748 compression, 749 samplesPerPixel, 750 bitsPerSample, 751 sampleFormat, 752 extraSamples, 753 colorMap); 754 755 // Check for an ICCProfile field. 756 TIFFField iccProfileField = 757 imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_ICC_PROFILE); 758 759 // If an ICCProfile field is present change the ImageTypeSpecifier 760 // to use it if the data layout is component type. 761 if(iccProfileField != null && 762 itsRaw.getColorModel() instanceof ComponentColorModel) { 763 // Create a ColorSpace from the profile. 764 byte[] iccProfileValue = iccProfileField.getAsBytes(); 765 ICC_Profile iccProfile = 766 ICC_Profile.getInstance(iccProfileValue); 767 ICC_ColorSpace iccColorSpace = 768 new ICC_ColorSpace(iccProfile); 769 770 // Get the raw sample and color information. 771 ColorModel cmRaw = itsRaw.getColorModel(); 772 ColorSpace csRaw = cmRaw.getColorSpace(); 773 SampleModel smRaw = itsRaw.getSampleModel(); 774 775 // Get the number of samples per pixel and the number 776 // of color components. 777 int numBands = smRaw.getNumBands(); 778 int numComponents = iccColorSpace.getNumComponents(); 779 780 // Replace the ColorModel with the ICC ColorModel if the 781 // numbers of samples and color components are amenable. 782 if(numBands == numComponents || 783 numBands == numComponents + 1) { 784 // Set alpha flags. 785 boolean hasAlpha = numComponents != numBands; 786 boolean isAlphaPre = 787 hasAlpha && cmRaw.isAlphaPremultiplied(); 788 789 // Create a ColorModel of the same class and with 790 // the same transfer type. 791 ColorModel iccColorModel = 792 new ComponentColorModel(iccColorSpace, 793 cmRaw.getComponentSize(), 794 hasAlpha, 795 isAlphaPre, 796 cmRaw.getTransparency(), 797 cmRaw.getTransferType()); 798 799 // Prepend the ICC profile-based ITS to the List. The 800 // ColorModel and SampleModel are guaranteed to be 801 // compatible as the old and new ColorModels are both 802 // ComponentColorModels with the same transfer type 803 // and the same number of components. 804 l.add(new ImageTypeSpecifier(iccColorModel, smRaw)); 805 806 // Append the raw ITS to the List if and only if its 807 // ColorSpace has the same type and number of components 808 // as the ICC ColorSpace. 809 if(csRaw.getType() == iccColorSpace.getType() && 810 csRaw.getNumComponents() == 811 iccColorSpace.getNumComponents()) { 812 l.add(itsRaw); 813 } 814 } else { // ICCProfile not compatible with SampleModel. 815 // Append the raw ITS to the List. 816 l.add(itsRaw); 817 } 818 } else { // No ICCProfile field or raw ColorModel not component. 819 // Append the raw ITS to the List. 820 l.add(itsRaw); 821 } 822 823 // Cache the ITS List. 824 imageTypeMap.put(imageIndexInteger, l); 825 } 826 827 return l.iterator(); 828 } 829 830 public IIOMetadata getImageMetadata(int imageIndex) throws IIOException { 831 seekToImage(imageIndex); 832 TIFFImageMetadata im = 833 new TIFFImageMetadata(imageMetadata.getRootIFD().getTagSetList()); 834 Node root = 835 imageMetadata.getAsTree(TIFFImageMetadata.nativeMetadataFormatName); 836 im.setFromTree(TIFFImageMetadata.nativeMetadataFormatName, root); 837 return im; 838 } 839 840 public IIOMetadata getStreamMetadata(int imageIndex) throws IIOException { 841 readHeader(); 842 TIFFStreamMetadata sm = new TIFFStreamMetadata(); 843 Node root = sm.getAsTree(TIFFStreamMetadata.nativeMetadataFormatName); 844 sm.setFromTree(TIFFStreamMetadata.nativeMetadataFormatName, root); 845 return sm; 846 } 847 848 public boolean isRandomAccessEasy(int imageIndex) throws IOException { 849 if(currIndex != -1) { 850 seekToImage(currIndex); 851 return getCompression() == BaselineTIFFTagSet.COMPRESSION_NONE; 852 } else { 853 return false; 854 } 855 } 856 857 // Thumbnails 858 859 public boolean readSupportsThumbnails() { 860 return false; 861 } 862 863 public boolean hasThumbnails(int imageIndex) { 864 return false; 865 } 866 867 public int getNumThumbnails(int imageIndex) throws IOException { 868 return 0; 869 } 870 871 public ImageReadParam getDefaultReadParam() { 872 return new TIFFImageReadParam(); 873 } 874 875 public boolean isImageTiled(int imageIndex) throws IOException { 876 seekToImage(imageIndex); 877 878 TIFFField f = 879 imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH); 880 return f != null; 881 } 882 883 public int getTileWidth(int imageIndex) throws IOException { 884 seekToImage(imageIndex); 885 return getTileOrStripWidth(); 886 } 887 888 public int getTileHeight(int imageIndex) throws IOException { 889 seekToImage(imageIndex); 890 return getTileOrStripHeight(); 891 } 892 893 public BufferedImage readTile(int imageIndex, int tileX, int tileY) 894 throws IOException { 895 896 int w = getWidth(imageIndex); 897 int h = getHeight(imageIndex); 898 int tw = getTileWidth(imageIndex); 899 int th = getTileHeight(imageIndex); 900 901 int x = tw*tileX; 902 int y = th*tileY; 903 904 if(tileX < 0 || tileY < 0 || x >= w || y >= h) { 905 throw new IllegalArgumentException 906 ("Tile indices are out of bounds!"); 907 } 908 909 if (x + tw > w) { 910 tw = w - x; 911 } 912 913 if (y + th > h) { 914 th = h - y; 915 } 916 917 ImageReadParam param = getDefaultReadParam(); 918 Rectangle tileRect = new Rectangle(x, y, tw, th); 919 param.setSourceRegion(tileRect); 920 921 return read(imageIndex, param); 922 } 923 924 public boolean canReadRaster() { 925 // Enable this? 926 return false; 927 } 928 929 public Raster readRaster(int imageIndex, ImageReadParam param) 930 throws IOException { 931 // Enable this? 932 throw new UnsupportedOperationException(); 933 } 934 935// public BufferedImage readTileRaster(int imageIndex, 936// int tileX, int tileY) 937// throws IOException { 938// } 939 940 private int[] sourceBands; 941 private int[] destinationBands; 942 943 private TIFFDecompressor decompressor; 944 945 // floor(num/den) 946 private static int ifloor(int num, int den) { 947 if (num < 0) { 948 num -= den - 1; 949 } 950 return num/den; 951 } 952 953 // ceil(num/den) 954 private static int iceil(int num, int den) { 955 if (num > 0) { 956 num += den - 1; 957 } 958 return num/den; 959 } 960 961 private void prepareRead(int imageIndex, ImageReadParam param) 962 throws IOException { 963 if (stream == null) { 964 throw new IllegalStateException("Input not set!"); 965 } 966 967 // A null ImageReadParam means we use the default 968 if (param == null) { 969 param = getDefaultReadParam(); 970 } 971 972 this.imageReadParam = param; 973 974 seekToImage(imageIndex); 975 976 this.tileOrStripWidth = getTileOrStripWidth(); 977 this.tileOrStripHeight = getTileOrStripHeight(); 978 this.planarConfiguration = getPlanarConfiguration(); 979 980 this.sourceBands = param.getSourceBands(); 981 if (sourceBands == null) { 982 sourceBands = new int[numBands]; 983 for (int i = 0; i < numBands; i++) { 984 sourceBands[i] = i; 985 } 986 } 987 988 // Initialize the destination image 989 Iterator imageTypes = getImageTypes(imageIndex); 990 ImageTypeSpecifier theImageType = 991 ImageUtil.getDestinationType(param, imageTypes); 992 993 int destNumBands = theImageType.getSampleModel().getNumBands(); 994 995 this.destinationBands = param.getDestinationBands(); 996 if (destinationBands == null) { 997 destinationBands = new int[destNumBands]; 998 for (int i = 0; i < destNumBands; i++) { 999 destinationBands[i] = i; 1000 } 1001 } 1002 1003 if (sourceBands.length != destinationBands.length) { 1004 throw new IllegalArgumentException( 1005 "sourceBands.length != destinationBands.length"); 1006 } 1007 1008 for (int i = 0; i < sourceBands.length; i++) { 1009 int sb = sourceBands[i]; 1010 if (sb < 0 || sb >= numBands) { 1011 throw new IllegalArgumentException( 1012 "Source band out of range!"); 1013 } 1014 int db = destinationBands[i]; 1015 if (db < 0 || db >= destNumBands) { 1016 throw new IllegalArgumentException( 1017 "Destination band out of range!"); 1018 } 1019 } 1020 } 1021 1022 public RenderedImage readAsRenderedImage(int imageIndex, 1023 ImageReadParam param) 1024 throws IOException { 1025 prepareRead(imageIndex, param); 1026 return new TIFFRenderedImage(this, imageIndex, imageReadParam, 1027 width, height); 1028 } 1029 1030 private void decodeTile(int ti, int tj, int band) throws IOException { 1031 if(DEBUG) { 1032 System.out.println("decodeTile("+ti+","+tj+","+band+")"); 1033 } 1034 1035 // Compute the region covered by the strip or tile 1036 Rectangle tileRect = new Rectangle(ti*tileOrStripWidth, 1037 tj*tileOrStripHeight, 1038 tileOrStripWidth, 1039 tileOrStripHeight); 1040 1041 // Clip against the image bounds if the image is not tiled. If it 1042 // is tiled, the tile may legally extend beyond the image bounds. 1043 if(!isImageTiled(currIndex)) { 1044 tileRect = 1045 tileRect.intersection(new Rectangle(0, 0, width, height)); 1046 } 1047 1048 // Return if the intersection is empty. 1049 if(tileRect.width <= 0 || tileRect.height <= 0) { 1050 return; 1051 } 1052 1053 int srcMinX = tileRect.x; 1054 int srcMinY = tileRect.y; 1055 int srcWidth = tileRect.width; 1056 int srcHeight = tileRect.height; 1057 1058 // Determine dest region that can be derived from the 1059 // source region 1060 1061 dstMinX = iceil(srcMinX - sourceXOffset, srcXSubsampling); 1062 int dstMaxX = ifloor(srcMinX + srcWidth - 1 - sourceXOffset, 1063 srcXSubsampling); 1064 1065 dstMinY = iceil(srcMinY - sourceYOffset, srcYSubsampling); 1066 int dstMaxY = ifloor(srcMinY + srcHeight - 1 - sourceYOffset, 1067 srcYSubsampling); 1068 1069 dstWidth = dstMaxX - dstMinX + 1; 1070 dstHeight = dstMaxY - dstMinY + 1; 1071 1072 dstMinX += dstXOffset; 1073 dstMinY += dstYOffset; 1074 1075 // Clip against image bounds 1076 1077 Rectangle dstRect = new Rectangle(dstMinX, dstMinY, 1078 dstWidth, dstHeight); 1079 dstRect = 1080 dstRect.intersection(theImage.getRaster().getBounds()); 1081 1082 dstMinX = dstRect.x; 1083 dstMinY = dstRect.y; 1084 dstWidth = dstRect.width; 1085 dstHeight = dstRect.height; 1086 1087 if (dstWidth <= 0 || dstHeight <= 0) { 1088 return; 1089 } 1090 1091 // Backwards map dest region to source to determine 1092 // active source region 1093 1094 int activeSrcMinX = (dstMinX - dstXOffset)*srcXSubsampling + 1095 sourceXOffset; 1096 int sxmax = 1097 (dstMinX + dstWidth - 1 - dstXOffset)*srcXSubsampling + 1098 sourceXOffset; 1099 int activeSrcWidth = sxmax - activeSrcMinX + 1; 1100 1101 int activeSrcMinY = (dstMinY - dstYOffset)*srcYSubsampling + 1102 sourceYOffset; 1103 int symax = 1104 (dstMinY + dstHeight - 1 - dstYOffset)*srcYSubsampling + 1105 sourceYOffset; 1106 int activeSrcHeight = symax - activeSrcMinY + 1; 1107 1108 decompressor.setSrcMinX(srcMinX); 1109 decompressor.setSrcMinY(srcMinY); 1110 decompressor.setSrcWidth(srcWidth); 1111 decompressor.setSrcHeight(srcHeight); 1112 1113 decompressor.setDstMinX(dstMinX); 1114 decompressor.setDstMinY(dstMinY); 1115 decompressor.setDstWidth(dstWidth); 1116 decompressor.setDstHeight(dstHeight); 1117 1118 decompressor.setActiveSrcMinX(activeSrcMinX); 1119 decompressor.setActiveSrcMinY(activeSrcMinY); 1120 decompressor.setActiveSrcWidth(activeSrcWidth); 1121 decompressor.setActiveSrcHeight(activeSrcHeight); 1122 1123 int tileIndex = tj*tilesAcross + ti; 1124 1125 if (planarConfiguration == 1126 BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) { 1127 tileIndex += band*tilesAcross*tilesDown; 1128 } 1129 1130 long offset = getTileOrStripOffset(tileIndex); 1131 long byteCount = getTileOrStripByteCount(tileIndex); 1132 1133 // 1134 // Attempt to handle truncated streams, i.e., where reading the 1135 // compressed strip or tile would result in an EOFException. The 1136 // number of bytes to read is clamped to the number available 1137 // from the stream starting at the indicated position in the hope 1138 // that the decompressor will handle it. 1139 // 1140 long streamLength = stream.length(); 1141 if(streamLength > 0 && offset + byteCount > streamLength) { 1142 processWarningOccurred("Attempting to process truncated stream."); 1143 if(Math.max(byteCount = streamLength - offset, 0) == 0) { 1144 processWarningOccurred("No bytes in strip/tile: skipping."); 1145 return; 1146 } 1147 } 1148 1149 decompressor.setStream(stream); 1150 decompressor.setOffset(offset); 1151 decompressor.setByteCount((int)byteCount); 1152 1153 decompressor.beginDecoding(); 1154 1155 stream.mark(); 1156 decompressor.decode(); 1157 stream.reset(); 1158 } 1159 1160 private void reportProgress() { 1161 // Report image progress/update to listeners after each tile 1162 pixelsRead += dstWidth*dstHeight; 1163 processImageProgress(100.0f*pixelsRead/pixelsToRead); 1164 processImageUpdate(theImage, 1165 dstMinX, dstMinY, dstWidth, dstHeight, 1166 1, 1, 1167 destinationBands); 1168 } 1169 1170 public BufferedImage read(int imageIndex, ImageReadParam param) 1171 throws IOException { 1172 prepareRead(imageIndex, param); 1173 this.theImage = getDestination(param, 1174 getImageTypes(imageIndex), 1175 width, height); 1176 1177 srcXSubsampling = imageReadParam.getSourceXSubsampling(); 1178 srcYSubsampling = imageReadParam.getSourceYSubsampling(); 1179 1180 Point p = imageReadParam.getDestinationOffset(); 1181 dstXOffset = p.x; 1182 dstYOffset = p.y; 1183 1184 // This could probably be made more efficient... 1185 Rectangle srcRegion = new Rectangle(0, 0, 0, 0); 1186 Rectangle destRegion = new Rectangle(0, 0, 0, 0); 1187 1188 computeRegions(imageReadParam, width, height, theImage, 1189 srcRegion, destRegion); 1190 1191 // Initial source pixel, taking source region and source 1192 // subsamplimg offsets into account 1193 sourceXOffset = srcRegion.x; 1194 sourceYOffset = srcRegion.y; 1195 1196 pixelsToRead = destRegion.width*destRegion.height; 1197 pixelsRead = 0; 1198 1199 processImageStarted(imageIndex); 1200 processImageProgress(0.0f); 1201 1202 tilesAcross = (width + tileOrStripWidth - 1)/tileOrStripWidth; 1203 tilesDown = (height + tileOrStripHeight - 1)/tileOrStripHeight; 1204 1205 int compression = getCompression(); 1206 1207 // Attempt to get decompressor and color converted from the read param 1208 1209 TIFFColorConverter colorConverter = null; 1210 if (imageReadParam instanceof TIFFImageReadParam) { 1211 TIFFImageReadParam tparam = 1212 (TIFFImageReadParam)imageReadParam; 1213 this.decompressor = tparam.getTIFFDecompressor(); 1214 colorConverter = tparam.getColorConverter(); 1215 } 1216 1217 // If we didn't find one, use a standard decompressor 1218 if (this.decompressor == null) { 1219 if (compression == 1220 BaselineTIFFTagSet.COMPRESSION_NONE) { 1221 // Get the fillOrder field. 1222 TIFFField fillOrderField = 1223 imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_FILL_ORDER); 1224 1225 // Set the decompressor based on the fill order. 1226 if(fillOrderField != null && fillOrderField.getAsInt(0) == 2) { 1227 this.decompressor = new TIFFLSBDecompressor(); 1228 } else { 1229 this.decompressor = new TIFFNullDecompressor(); 1230 } 1231 } else if (compression == 1232 BaselineTIFFTagSet.COMPRESSION_CCITT_T_6) { 1233 1234 1235 // Fall back to the Java decompressor. 1236 if (this.decompressor == null) { 1237 if(DEBUG) { 1238 System.out.println("Using Java T.6 decompressor"); 1239 } 1240 this.decompressor = new TIFFFaxDecompressor(); 1241 } 1242 } else if (compression == 1243 BaselineTIFFTagSet.COMPRESSION_CCITT_T_4) { 1244 1245 // Fall back to the Java decompressor. 1246 if (this.decompressor == null) { 1247 if(DEBUG) { 1248 System.out.println("Using Java T.4 decompressor"); 1249 } 1250 this.decompressor = new TIFFFaxDecompressor(); 1251 } 1252 } else if (compression == 1253 BaselineTIFFTagSet.COMPRESSION_CCITT_RLE) { 1254 this.decompressor = new TIFFFaxDecompressor(); 1255 } else if (compression == 1256 BaselineTIFFTagSet.COMPRESSION_PACKBITS) { 1257 if(DEBUG) { 1258 System.out.println("Using TIFFPackBitsDecompressor"); 1259 } 1260 this.decompressor = new TIFFPackBitsDecompressor(); 1261 } else if (compression == 1262 BaselineTIFFTagSet.COMPRESSION_LZW) { 1263 if(DEBUG) { 1264 System.out.println("Using TIFFLZWDecompressor"); 1265 } 1266 TIFFField predictorField = 1267 imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PREDICTOR); 1268 int predictor = ((predictorField == null) ? 1269 BaselineTIFFTagSet.PREDICTOR_NONE : 1270 predictorField.getAsInt(0)); 1271 this.decompressor = new TIFFLZWDecompressor(predictor); 1272 } else if (compression == 1273 BaselineTIFFTagSet.COMPRESSION_JPEG) { 1274 this.decompressor = new TIFFJPEGDecompressor(); 1275 } else if (compression == 1276 BaselineTIFFTagSet.COMPRESSION_ZLIB || 1277 compression == 1278 BaselineTIFFTagSet.COMPRESSION_DEFLATE) { 1279 TIFFField predictorField = 1280 imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PREDICTOR); 1281 int predictor = ((predictorField == null) ? 1282 BaselineTIFFTagSet.PREDICTOR_NONE : 1283 predictorField.getAsInt(0)); 1284 this.decompressor = new TIFFDeflateDecompressor(predictor); 1285 } else if (compression == 1286 BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) { 1287 TIFFField JPEGProcField = 1288 imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_PROC); 1289 if(JPEGProcField == null) { 1290 processWarningOccurred 1291 ("JPEGProc field missing; assuming baseline sequential JPEG process."); 1292 } else if(JPEGProcField.getAsInt(0) != 1293 BaselineTIFFTagSet.JPEG_PROC_BASELINE) { 1294 throw new IIOException 1295 ("Old-style JPEG supported for baseline sequential JPEG process only!"); 1296 } 1297 this.decompressor = new TIFFOldJPEGDecompressor(); 1298 //throw new IIOException("Old-style JPEG not supported!"); 1299 } else { 1300 throw new IIOException 1301 ("Unsupported compression type (tag number = "+ 1302 compression+")!"); 1303 } 1304 1305 if (photometricInterpretation == 1306 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR && 1307 compression != BaselineTIFFTagSet.COMPRESSION_JPEG && 1308 compression != BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) { 1309 boolean convertYCbCrToRGB = 1310 theImage.getColorModel().getColorSpace().getType() == 1311 ColorSpace.TYPE_RGB; 1312 TIFFDecompressor wrappedDecompressor = 1313 this.decompressor instanceof TIFFNullDecompressor ? 1314 null : this.decompressor; 1315 this.decompressor = 1316 new TIFFYCbCrDecompressor(wrappedDecompressor, 1317 convertYCbCrToRGB); 1318 } 1319 } 1320 1321 if(DEBUG) { 1322 System.out.println("\nDecompressor class = "+ 1323 decompressor.getClass().getName()+"\n"); 1324 } 1325 1326 if (colorConverter == null) { 1327 if (photometricInterpretation == 1328 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CIELAB && 1329 theImage.getColorModel().getColorSpace().getType() == 1330 ColorSpace.TYPE_RGB) { 1331 colorConverter = new TIFFCIELabColorConverter(); 1332 } else if (photometricInterpretation == 1333 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR && 1334 !(this.decompressor instanceof TIFFYCbCrDecompressor) && 1335 compression != BaselineTIFFTagSet.COMPRESSION_JPEG && 1336 compression != BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) { 1337 colorConverter = new TIFFYCbCrColorConverter(imageMetadata); 1338 } 1339 } 1340 1341 decompressor.setReader(this); 1342 decompressor.setMetadata(imageMetadata); 1343 decompressor.setImage(theImage); 1344 1345 decompressor.setPhotometricInterpretation(photometricInterpretation); 1346 decompressor.setCompression(compression); 1347 decompressor.setSamplesPerPixel(samplesPerPixel); 1348 decompressor.setBitsPerSample(bitsPerSample); 1349 decompressor.setSampleFormat(sampleFormat); 1350 decompressor.setExtraSamples(extraSamples); 1351 decompressor.setColorMap(colorMap); 1352 1353 decompressor.setColorConverter(colorConverter); 1354 1355 decompressor.setSourceXOffset(sourceXOffset); 1356 decompressor.setSourceYOffset(sourceYOffset); 1357 decompressor.setSubsampleX(srcXSubsampling); 1358 decompressor.setSubsampleY(srcYSubsampling); 1359 1360 decompressor.setDstXOffset(dstXOffset); 1361 decompressor.setDstYOffset(dstYOffset); 1362 1363 decompressor.setSourceBands(sourceBands); 1364 decompressor.setDestinationBands(destinationBands); 1365 1366 // Compute bounds on the tile indices for this source region. 1367 int minTileX = 1368 TIFFImageWriter.XToTileX(srcRegion.x, 0, tileOrStripWidth); 1369 int minTileY = 1370 TIFFImageWriter.YToTileY(srcRegion.y, 0, tileOrStripHeight); 1371 int maxTileX = 1372 TIFFImageWriter.XToTileX(srcRegion.x + srcRegion.width - 1, 1373 0, tileOrStripWidth); 1374 int maxTileY = 1375 TIFFImageWriter.YToTileY(srcRegion.y + srcRegion.height - 1, 1376 0, tileOrStripHeight); 1377 1378 boolean isAbortRequested = false; 1379 if (planarConfiguration == 1380 BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) { 1381 1382 decompressor.setPlanar(true); 1383 1384 int[] sb = new int[1]; 1385 int[] db = new int[1]; 1386 for (int tj = minTileY; tj <= maxTileY; tj++) { 1387 for (int ti = minTileX; ti <= maxTileX; ti++) { 1388 for (int band = 0; band < numBands; band++) { 1389 sb[0] = sourceBands[band]; 1390 decompressor.setSourceBands(sb); 1391 db[0] = destinationBands[band]; 1392 decompressor.setDestinationBands(db); 1393 //XXX decompressor.beginDecoding(); 1394 1395 // The method abortRequested() is synchronized 1396 // so check it only once per loop just before 1397 // doing any actual decoding. 1398 if(abortRequested()) { 1399 isAbortRequested = true; 1400 break; 1401 } 1402 1403 decodeTile(ti, tj, band); 1404 } 1405 1406 if(isAbortRequested) break; 1407 1408 reportProgress(); 1409 } 1410 1411 if(isAbortRequested) break; 1412 } 1413 } else { 1414 //XXX decompressor.beginDecoding(); 1415 1416 for (int tj = minTileY; tj <= maxTileY; tj++) { 1417 for (int ti = minTileX; ti <= maxTileX; ti++) { 1418 // The method abortRequested() is synchronized 1419 // so check it only once per loop just before 1420 // doing any actual decoding. 1421 if(abortRequested()) { 1422 isAbortRequested = true; 1423 break; 1424 } 1425 1426 decodeTile(ti, tj, -1); 1427 1428 reportProgress(); 1429 } 1430 1431 if(isAbortRequested) break; 1432 } 1433 } 1434 1435 if (isAbortRequested) { 1436 processReadAborted(); 1437 } else { 1438 processImageComplete(); 1439 } 1440 1441 return theImage; 1442 } 1443 1444 public void reset() { 1445 super.reset(); 1446 resetLocal(); 1447 } 1448 1449 protected void resetLocal() { 1450 stream = null; 1451 gotHeader = false; 1452 imageReadParam = getDefaultReadParam(); 1453 streamMetadata = null; 1454 currIndex = -1; 1455 imageMetadata = null; 1456 imageStartPosition = new ArrayList(); 1457 numImages = -1; 1458 imageTypeMap = new HashMap(); 1459 width = -1; 1460 height = -1; 1461 numBands = -1; 1462 tileOrStripWidth = -1; 1463 tileOrStripHeight = -1; 1464 planarConfiguration = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY; 1465 rowsDone = 0; 1466 } 1467 1468 /** 1469 * Package scope method to allow decompressors, for example, to 1470 * emit warning messages. 1471 */ 1472 void forwardWarningMessage(String warning) { 1473 processWarningOccurred(warning); 1474 } 1475 1476 protected static BufferedImage getDestination(ImageReadParam param, 1477 Iterator imageTypes, 1478 int width, int height) 1479 throws IIOException { 1480 if (imageTypes == null || !imageTypes.hasNext()) { 1481 throw new IllegalArgumentException("imageTypes null or empty!"); 1482 } 1483 1484 BufferedImage dest = null; 1485 ImageTypeSpecifier imageType = null; 1486 1487 // If param is non-null, use it 1488 if (param != null) { 1489 // Try to get the image itself 1490 dest = param.getDestination(); 1491 if (dest != null) { 1492 return dest; 1493 } 1494 1495 // No image, get the image type 1496 imageType = param.getDestinationType(); 1497 } 1498 1499 // No info from param, use fallback image type 1500 if (imageType == null) { 1501 Object o = imageTypes.next(); 1502 if (!(o instanceof ImageTypeSpecifier)) { 1503 throw new IllegalArgumentException 1504 ("Non-ImageTypeSpecifier retrieved from imageTypes!"); 1505 } 1506 imageType = (ImageTypeSpecifier)o; 1507 } else { 1508 boolean foundIt = false; 1509 while (imageTypes.hasNext()) { 1510 ImageTypeSpecifier type = 1511 (ImageTypeSpecifier)imageTypes.next(); 1512 if (type.equals(imageType)) { 1513 foundIt = true; 1514 break; 1515 } 1516 } 1517 1518 if (!foundIt) { 1519 throw new IIOException 1520 ("Destination type from ImageReadParam does not match!"); 1521 } 1522 } 1523 1524 Rectangle srcRegion = new Rectangle(0,0,0,0); 1525 Rectangle destRegion = new Rectangle(0,0,0,0); 1526 computeRegions(param, 1527 width, 1528 height, 1529 null, 1530 srcRegion, 1531 destRegion); 1532 1533 int destWidth = destRegion.x + destRegion.width; 1534 int destHeight = destRegion.y + destRegion.height; 1535 // Create a new image based on the type specifier 1536 1537 if ((long)destWidth*destHeight > Integer.MAX_VALUE) { 1538 throw new IllegalArgumentException 1539 ("width*height > Integer.MAX_VALUE!"); 1540 } 1541 1542 return imageType.createBufferedImage(destWidth, destHeight); 1543 } 1544}