| /* |
| * Copyright (c) 2005, 2017, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| package com.sun.imageio.plugins.tiff; |
| |
| import java.awt.Point; |
| import java.awt.Rectangle; |
| import java.awt.color.ColorSpace; |
| import java.awt.color.ICC_ColorSpace; |
| import java.awt.color.ICC_Profile; |
| import java.awt.image.BufferedImage; |
| import java.awt.image.ColorModel; |
| import java.awt.image.ComponentColorModel; |
| import java.awt.image.Raster; |
| import java.awt.image.RenderedImage; |
| import java.awt.image.SampleModel; |
| import java.io.EOFException; |
| import java.io.IOException; |
| import java.nio.ByteOrder; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import javax.imageio.IIOException; |
| import javax.imageio.ImageIO; |
| import javax.imageio.ImageReader; |
| import javax.imageio.ImageReadParam; |
| import javax.imageio.ImageTypeSpecifier; |
| import javax.imageio.metadata.IIOMetadata; |
| import javax.imageio.spi.ImageReaderSpi; |
| import javax.imageio.stream.ImageInputStream; |
| import org.w3c.dom.Node; |
| import com.sun.imageio.plugins.common.ImageUtil; |
| import javax.imageio.plugins.tiff.BaselineTIFFTagSet; |
| import javax.imageio.plugins.tiff.TIFFField; |
| import javax.imageio.plugins.tiff.TIFFImageReadParam; |
| import javax.imageio.plugins.tiff.TIFFTagSet; |
| |
| public class TIFFImageReader extends ImageReader { |
| |
| // A somewhat arbitrary upper bound on SamplesPerPixel. Hyperspectral |
| // images as of this writing appear to be under 300 bands so this should |
| // account for those cases should they arise. |
| private static final int SAMPLES_PER_PIXEL_MAX = 1024; |
| |
| // In baseline TIFF the largest data types are 64-bit long and double. |
| private static final int BITS_PER_SAMPLE_MAX = 64; |
| |
| // The current ImageInputStream source. |
| private ImageInputStream stream = null; |
| |
| // True if the file header has been read. |
| private boolean gotHeader = false; |
| |
| private ImageReadParam imageReadParam = getDefaultReadParam(); |
| |
| // Stream metadata, or null. |
| private TIFFStreamMetadata streamMetadata = null; |
| |
| // The current image index. |
| private int currIndex = -1; |
| |
| // Metadata for image at 'currIndex', or null. |
| private TIFFImageMetadata imageMetadata = null; |
| |
| // A {@code List} of {@code Long}s indicating the stream |
| // positions of the start of the IFD for each image. Entries |
| // are added as needed. |
| private List<Long> imageStartPosition = new ArrayList<Long>(); |
| |
| // The number of images in the stream, if known, otherwise -1. |
| private int numImages = -1; |
| |
| // The ImageTypeSpecifiers of the images in the stream. |
| // Contains a map of Integers to Lists. |
| private HashMap<Integer, List<ImageTypeSpecifier>> imageTypeMap |
| = new HashMap<Integer, List<ImageTypeSpecifier>>(); |
| |
| private BufferedImage theImage = null; |
| |
| private int width = -1; |
| private int height = -1; |
| private int numBands = -1; |
| private int tileOrStripWidth = -1, tileOrStripHeight = -1; |
| |
| private int planarConfiguration = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY; |
| |
| private int compression; |
| private int photometricInterpretation; |
| private int samplesPerPixel; |
| private int[] sampleFormat; |
| private int[] bitsPerSample; |
| private int[] extraSamples; |
| private char[] colorMap; |
| |
| private int sourceXOffset; |
| private int sourceYOffset; |
| private int srcXSubsampling; |
| private int srcYSubsampling; |
| |
| private int dstWidth; |
| private int dstHeight; |
| private int dstMinX; |
| private int dstMinY; |
| private int dstXOffset; |
| private int dstYOffset; |
| |
| private int tilesAcross; |
| private int tilesDown; |
| |
| private int pixelsRead; |
| private int pixelsToRead; |
| |
| public TIFFImageReader(ImageReaderSpi originatingProvider) { |
| super(originatingProvider); |
| } |
| |
| public void setInput(Object input, |
| boolean seekForwardOnly, |
| boolean ignoreMetadata) { |
| super.setInput(input, seekForwardOnly, ignoreMetadata); |
| |
| // Clear all local values based on the previous stream contents. |
| resetLocal(); |
| |
| if (input != null) { |
| if (!(input instanceof ImageInputStream)) { |
| throw new IllegalArgumentException("input not an ImageInputStream!"); |
| } |
| this.stream = (ImageInputStream) input; |
| } else { |
| this.stream = null; |
| } |
| } |
| |
| // Do not seek to the beginning of the stream so as to allow users to |
| // point us at an IFD within some other file format |
| private void readHeader() throws IIOException { |
| if (gotHeader) { |
| return; |
| } |
| if (stream == null) { |
| throw new IllegalStateException("Input not set!"); |
| } |
| |
| // Create an object to store the stream metadata |
| this.streamMetadata = new TIFFStreamMetadata(); |
| |
| try { |
| int byteOrder = stream.readUnsignedShort(); |
| if (byteOrder == 0x4d4d) { |
| streamMetadata.byteOrder = ByteOrder.BIG_ENDIAN; |
| stream.setByteOrder(ByteOrder.BIG_ENDIAN); |
| } else if (byteOrder == 0x4949) { |
| streamMetadata.byteOrder = ByteOrder.LITTLE_ENDIAN; |
| stream.setByteOrder(ByteOrder.LITTLE_ENDIAN); |
| } else { |
| processWarningOccurred( |
| "Bad byte order in header, assuming little-endian"); |
| streamMetadata.byteOrder = ByteOrder.LITTLE_ENDIAN; |
| stream.setByteOrder(ByteOrder.LITTLE_ENDIAN); |
| } |
| |
| int magic = stream.readUnsignedShort(); |
| if (magic != 42) { |
| processWarningOccurred( |
| "Bad magic number in header, continuing"); |
| } |
| |
| // Seek to start of first IFD |
| long offset = stream.readUnsignedInt(); |
| stream.seek(offset); |
| imageStartPosition.add(Long.valueOf(offset)); |
| } catch (IOException e) { |
| throw new IIOException("I/O error reading header!", e); |
| } |
| |
| gotHeader = true; |
| } |
| |
| private int locateImage(int imageIndex) throws IIOException { |
| readHeader(); |
| |
| // Find closest known index |
| int index = Math.min(imageIndex, imageStartPosition.size() - 1); |
| |
| try { |
| // Seek to that position |
| Long l = imageStartPosition.get(index); |
| stream.seek(l.longValue()); |
| |
| // Skip IFDs until at desired index or last image found |
| while (index < imageIndex) { |
| int count = stream.readUnsignedShort(); |
| // If zero-entry IFD, decrement the index and exit the loop |
| if (count == 0) { |
| imageIndex = index > 0 ? index - 1 : 0; |
| break; |
| } |
| stream.skipBytes(12 * count); |
| |
| long offset = stream.readUnsignedInt(); |
| if (offset == 0) { |
| return index; |
| } |
| |
| stream.seek(offset); |
| imageStartPosition.add(Long.valueOf(offset)); |
| ++index; |
| } |
| } catch (EOFException eofe) { |
| forwardWarningMessage("Ignored " + eofe); |
| |
| // Ran off the end of stream: decrement index |
| imageIndex = index > 0 ? index - 1 : 0; |
| } catch (IOException ioe) { |
| throw new IIOException("Couldn't seek!", ioe); |
| } |
| |
| if (currIndex != imageIndex) { |
| imageMetadata = null; |
| } |
| currIndex = imageIndex; |
| return imageIndex; |
| } |
| |
| public int getNumImages(boolean allowSearch) throws IOException { |
| if (stream == null) { |
| throw new IllegalStateException("Input not set!"); |
| } |
| if (seekForwardOnly && allowSearch) { |
| throw new IllegalStateException("seekForwardOnly and allowSearch can't both be true!"); |
| } |
| |
| if (numImages > 0) { |
| return numImages; |
| } |
| if (allowSearch) { |
| this.numImages = locateImage(Integer.MAX_VALUE) + 1; |
| } |
| return numImages; |
| } |
| |
| public IIOMetadata getStreamMetadata() throws IIOException { |
| readHeader(); |
| return streamMetadata; |
| } |
| |
| // Throw an IndexOutOfBoundsException if index < minIndex, |
| // and bump minIndex if required. |
| private void checkIndex(int imageIndex) { |
| if (imageIndex < minIndex) { |
| throw new IndexOutOfBoundsException("imageIndex < minIndex!"); |
| } |
| if (seekForwardOnly) { |
| minIndex = imageIndex; |
| } |
| } |
| |
| // Verify that imageIndex is in bounds, find the image IFD, read the |
| // image metadata, initialize instance variables from the metadata. |
| private void seekToImage(int imageIndex) throws IIOException { |
| checkIndex(imageIndex); |
| |
| int index = locateImage(imageIndex); |
| if (index != imageIndex) { |
| throw new IndexOutOfBoundsException("imageIndex out of bounds!"); |
| } |
| |
| readMetadata(); |
| |
| initializeFromMetadata(); |
| } |
| |
| // Stream must be positioned at start of IFD for 'currIndex' |
| private void readMetadata() throws IIOException { |
| if (stream == null) { |
| throw new IllegalStateException("Input not set!"); |
| } |
| |
| if (imageMetadata != null) { |
| return; |
| } |
| try { |
| // Create an object to store the image metadata |
| List<TIFFTagSet> tagSets; |
| boolean readUnknownTags = false; |
| if (imageReadParam instanceof TIFFImageReadParam) { |
| TIFFImageReadParam tp = (TIFFImageReadParam)imageReadParam; |
| tagSets = tp.getAllowedTagSets(); |
| readUnknownTags = tp.getReadUnknownTags(); |
| } else { |
| tagSets = new ArrayList<TIFFTagSet>(1); |
| tagSets.add(BaselineTIFFTagSet.getInstance()); |
| } |
| |
| this.imageMetadata = new TIFFImageMetadata(tagSets); |
| imageMetadata.initializeFromStream(stream, ignoreMetadata, |
| readUnknownTags); |
| } catch (IIOException iioe) { |
| throw iioe; |
| } catch (IOException ioe) { |
| throw new IIOException("I/O error reading image metadata!", ioe); |
| } |
| } |
| |
| private int getWidth() { |
| return this.width; |
| } |
| |
| private int getHeight() { |
| return this.height; |
| } |
| |
| // Returns tile width if image is tiled, else image width |
| private int getTileOrStripWidth() { |
| TIFFField f |
| = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH); |
| return (f == null) ? getWidth() : f.getAsInt(0); |
| } |
| |
| // Returns tile height if image is tiled, else strip height |
| private int getTileOrStripHeight() { |
| TIFFField f |
| = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_LENGTH); |
| if (f != null) { |
| return f.getAsInt(0); |
| } |
| |
| f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP); |
| // Default for ROWS_PER_STRIP is 2^32 - 1, i.e., infinity |
| int h = (f == null) ? -1 : f.getAsInt(0); |
| return (h == -1) ? getHeight() : h; |
| } |
| |
| private int getPlanarConfiguration() { |
| TIFFField f |
| = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION); |
| if (f != null) { |
| int planarConfigurationValue = f.getAsInt(0); |
| if (planarConfigurationValue |
| == BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) { |
| // Some writers (e.g. Kofax standard Multi-Page TIFF |
| // Storage Filter v2.01.000; cf. bug 4929147) do not |
| // correctly set the value of this field. Attempt to |
| // ascertain whether the value is correctly Planar. |
| if (getCompression() |
| == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG |
| && imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT) |
| != null) { |
| // JPEG interchange format cannot have |
| // PlanarConfiguration value Chunky so reset. |
| processWarningOccurred("PlanarConfiguration \"Planar\" value inconsistent with JPEGInterchangeFormat; resetting to \"Chunky\"."); |
| planarConfigurationValue |
| = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY; |
| } else { |
| TIFFField offsetField |
| = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS); |
| if (offsetField == null) { |
| // Tiles |
| offsetField |
| = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS); |
| int tw = getTileOrStripWidth(); |
| int th = getTileOrStripHeight(); |
| int tAcross = (getWidth() + tw - 1) / tw; |
| int tDown = (getHeight() + th - 1) / th; |
| int tilesPerImage = tAcross * tDown; |
| long[] offsetArray = offsetField.getAsLongs(); |
| if (offsetArray != null |
| && offsetArray.length == tilesPerImage) { |
| // Length of offsets array is |
| // TilesPerImage for Chunky and |
| // SamplesPerPixel*TilesPerImage for Planar. |
| processWarningOccurred("PlanarConfiguration \"Planar\" value inconsistent with TileOffsets field value count; resetting to \"Chunky\"."); |
| planarConfigurationValue |
| = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY; |
| } |
| } else { |
| // Strips |
| int rowsPerStrip = getTileOrStripHeight(); |
| int stripsPerImage |
| = (getHeight() + rowsPerStrip - 1) / rowsPerStrip; |
| long[] offsetArray = offsetField.getAsLongs(); |
| if (offsetArray != null |
| && offsetArray.length == stripsPerImage) { |
| // Length of offsets array is |
| // StripsPerImage for Chunky and |
| // SamplesPerPixel*StripsPerImage for Planar. |
| processWarningOccurred("PlanarConfiguration \"Planar\" value inconsistent with StripOffsets field value count; resetting to \"Chunky\"."); |
| planarConfigurationValue |
| = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY; |
| } |
| } |
| } |
| } |
| return planarConfigurationValue; |
| } |
| |
| return BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY; |
| } |
| |
| private long getTileOrStripOffset(int tileIndex) throws IIOException { |
| TIFFField f |
| = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS); |
| if (f == null) { |
| f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS); |
| } |
| if (f == null) { |
| f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT); |
| } |
| |
| if (f == null) { |
| throw new IIOException("Missing required strip or tile offsets field."); |
| } |
| |
| return f.getAsLong(tileIndex); |
| } |
| |
| private long getTileOrStripByteCount(int tileIndex) throws IOException { |
| TIFFField f |
| = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS); |
| if (f == null) { |
| f |
| = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS); |
| } |
| if (f == null) { |
| f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH); |
| } |
| |
| long tileOrStripByteCount; |
| if (f != null) { |
| tileOrStripByteCount = f.getAsLong(tileIndex); |
| } else { |
| processWarningOccurred("TIFF directory contains neither StripByteCounts nor TileByteCounts field: attempting to calculate from strip or tile width and height."); |
| |
| // Initialize to number of bytes per strip or tile assuming |
| // no compression. |
| int bitsPerPixel = bitsPerSample[0]; |
| for (int i = 1; i < samplesPerPixel; i++) { |
| bitsPerPixel += bitsPerSample[i]; |
| } |
| int bytesPerRow = (getTileOrStripWidth() * bitsPerPixel + 7) / 8; |
| tileOrStripByteCount = bytesPerRow * getTileOrStripHeight(); |
| |
| // Clamp to end of stream if possible. |
| long streamLength = stream.length(); |
| if (streamLength != -1) { |
| tileOrStripByteCount |
| = Math.min(tileOrStripByteCount, |
| streamLength - getTileOrStripOffset(tileIndex)); |
| } else { |
| processWarningOccurred("Stream length is unknown: cannot clamp estimated strip or tile byte count to EOF."); |
| } |
| } |
| |
| return tileOrStripByteCount; |
| } |
| |
| private int getCompression() { |
| TIFFField f |
| = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION); |
| if (f == null) { |
| return BaselineTIFFTagSet.COMPRESSION_NONE; |
| } else { |
| return f.getAsInt(0); |
| } |
| } |
| |
| public int getWidth(int imageIndex) throws IOException { |
| seekToImage(imageIndex); |
| return getWidth(); |
| } |
| |
| public int getHeight(int imageIndex) throws IOException { |
| seekToImage(imageIndex); |
| return getHeight(); |
| } |
| |
| /** |
| * Initializes these instance variables from the image metadata: |
| * <pre> |
| * compression |
| * width |
| * height |
| * samplesPerPixel |
| * numBands |
| * colorMap |
| * photometricInterpretation |
| * sampleFormat |
| * bitsPerSample |
| * extraSamples |
| * tileOrStripWidth |
| * tileOrStripHeight |
| * </pre> |
| */ |
| private void initializeFromMetadata() throws IIOException { |
| TIFFField f; |
| |
| // Compression |
| f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION); |
| if (f == null) { |
| processWarningOccurred("Compression field is missing; assuming no compression"); |
| compression = BaselineTIFFTagSet.COMPRESSION_NONE; |
| } else { |
| compression = f.getAsInt(0); |
| } |
| |
| // Whether key dimensional information is absent. |
| boolean isMissingDimension = false; |
| |
| // ImageWidth -> width |
| f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH); |
| if (f != null) { |
| this.width = f.getAsInt(0); |
| } else { |
| processWarningOccurred("ImageWidth field is missing."); |
| isMissingDimension = true; |
| } |
| |
| // ImageLength -> height |
| f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH); |
| if (f != null) { |
| this.height = f.getAsInt(0); |
| } else { |
| processWarningOccurred("ImageLength field is missing."); |
| isMissingDimension = true; |
| } |
| |
| // SamplesPerPixel |
| f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL); |
| if (f != null) { |
| samplesPerPixel = f.getAsInt(0); |
| } else { |
| samplesPerPixel = 1; |
| isMissingDimension = true; |
| } |
| |
| // If any dimension is missing and there is a JPEG stream available |
| // get the information from it. |
| int defaultBitDepth = 1; |
| if (isMissingDimension |
| && (f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT)) != null) { |
| Iterator<ImageReader> iter = ImageIO.getImageReadersByFormatName("JPEG"); |
| if (iter != null && iter.hasNext()) { |
| ImageReader jreader = iter.next(); |
| try { |
| stream.mark(); |
| stream.seek(f.getAsLong(0)); |
| jreader.setInput(stream); |
| if (imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH) == null) { |
| this.width = jreader.getWidth(0); |
| } |
| if (imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH) == null) { |
| this.height = jreader.getHeight(0); |
| } |
| ImageTypeSpecifier imageType = jreader.getRawImageType(0); |
| if (imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL) == null) { |
| this.samplesPerPixel = |
| imageType != null ? |
| imageType.getSampleModel().getNumBands() : 3; |
| } |
| stream.reset(); |
| defaultBitDepth = |
| imageType != null ? |
| imageType.getColorModel().getComponentSize(0) : 8; |
| } catch (IOException e) { |
| // Ignore it and proceed: an error will occur later. |
| } |
| jreader.dispose(); |
| } |
| } |
| |
| if (samplesPerPixel < 1) { |
| throw new IIOException("Samples per pixel < 1!"); |
| } else if (samplesPerPixel > SAMPLES_PER_PIXEL_MAX) { |
| throw new IIOException |
| ("Samples per pixel (" + samplesPerPixel |
| + ") greater than allowed maximum (" |
| + SAMPLES_PER_PIXEL_MAX + ")"); |
| } |
| |
| // SamplesPerPixel -> numBands |
| numBands = samplesPerPixel; |
| |
| // ColorMap |
| this.colorMap = null; |
| f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_COLOR_MAP); |
| if (f != null) { |
| // Grab color map |
| colorMap = f.getAsChars(); |
| } |
| |
| // PhotometricInterpretation |
| f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION); |
| if (f == null) { |
| if (compression == BaselineTIFFTagSet.COMPRESSION_CCITT_RLE |
| || compression == BaselineTIFFTagSet.COMPRESSION_CCITT_T_4 |
| || compression == BaselineTIFFTagSet.COMPRESSION_CCITT_T_6) { |
| processWarningOccurred("PhotometricInterpretation field is missing; " |
| + "assuming WhiteIsZero"); |
| photometricInterpretation |
| = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO; |
| } else if (this.colorMap != null) { |
| photometricInterpretation |
| = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR; |
| } else if (samplesPerPixel == 3 || samplesPerPixel == 4) { |
| photometricInterpretation |
| = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB; |
| } else { |
| processWarningOccurred("PhotometricInterpretation field is missing; " |
| + "assuming BlackIsZero"); |
| photometricInterpretation |
| = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO; |
| } |
| } else { |
| photometricInterpretation = f.getAsInt(0); |
| } |
| |
| // SampleFormat |
| boolean replicateFirst = false; |
| int first = -1; |
| |
| f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLE_FORMAT); |
| sampleFormat = new int[samplesPerPixel]; |
| replicateFirst = false; |
| if (f == null) { |
| replicateFirst = true; |
| first = BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED; |
| } else if (f.getCount() != samplesPerPixel) { |
| replicateFirst = true; |
| first = f.getAsInt(0); |
| } |
| |
| for (int i = 0; i < samplesPerPixel; i++) { |
| sampleFormat[i] = replicateFirst ? first : f.getAsInt(i); |
| if (sampleFormat[i] |
| != BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER |
| && sampleFormat[i] |
| != BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER |
| && sampleFormat[i] |
| != BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT |
| && sampleFormat[i] |
| != BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED) { |
| processWarningOccurred( |
| "Illegal value for SAMPLE_FORMAT, assuming SAMPLE_FORMAT_UNDEFINED"); |
| sampleFormat[i] = BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED; |
| } |
| } |
| |
| // BitsPerSample |
| f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE); |
| this.bitsPerSample = new int[samplesPerPixel]; |
| replicateFirst = false; |
| if (f == null) { |
| replicateFirst = true; |
| first = defaultBitDepth; |
| } else if (f.getCount() != samplesPerPixel) { |
| replicateFirst = true; |
| first = f.getAsInt(0); |
| } |
| |
| for (int i = 0; i < samplesPerPixel; i++) { |
| // Replicate initial value if not enough values provided |
| bitsPerSample[i] = replicateFirst ? first : f.getAsInt(i); |
| if (bitsPerSample[i] > BITS_PER_SAMPLE_MAX) { |
| throw new IIOException |
| ("Bits per sample (" + bitsPerSample[i] |
| + ") greater than allowed maximum (" |
| + BITS_PER_SAMPLE_MAX + ")"); |
| } |
| } |
| |
| // ExtraSamples |
| this.extraSamples = null; |
| f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES); |
| if (f != null) { |
| extraSamples = f.getAsInts(); |
| } |
| } |
| |
| public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IIOException { |
| List<ImageTypeSpecifier> l; // List of ImageTypeSpecifiers |
| |
| Integer imageIndexInteger = Integer.valueOf(imageIndex); |
| if (imageTypeMap.containsKey(imageIndexInteger)) { |
| // Return the cached ITS List. |
| l = imageTypeMap.get(imageIndexInteger); |
| } else { |
| // Create a new ITS List. |
| l = new ArrayList<ImageTypeSpecifier>(1); |
| |
| // Create the ITS and cache if for later use so that this method |
| // always returns an Iterator containing the same ITS objects. |
| seekToImage(imageIndex); |
| ImageTypeSpecifier itsRaw |
| = TIFFDecompressor.getRawImageTypeSpecifier(photometricInterpretation, |
| compression, |
| samplesPerPixel, |
| bitsPerSample, |
| sampleFormat, |
| extraSamples, |
| colorMap); |
| |
| // Check for an ICCProfile field. |
| TIFFField iccProfileField |
| = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_ICC_PROFILE); |
| |
| // If an ICCProfile field is present change the ImageTypeSpecifier |
| // to use it if the data layout is component type. |
| if (iccProfileField != null |
| && itsRaw.getColorModel() instanceof ComponentColorModel) { |
| // Get the raw sample and color information. |
| ColorModel cmRaw = itsRaw.getColorModel(); |
| ColorSpace csRaw = cmRaw.getColorSpace(); |
| SampleModel smRaw = itsRaw.getSampleModel(); |
| |
| ColorSpace iccColorSpace = null; |
| try { |
| // Create a ColorSpace from the profile. |
| byte[] iccProfileValue = iccProfileField.getAsBytes(); |
| ICC_Profile iccProfile |
| = ICC_Profile.getInstance(iccProfileValue); |
| iccColorSpace = new ICC_ColorSpace(iccProfile); |
| |
| // Workaround for JDK-8145241: test a conversion and fall |
| // back to a standard ColorSpace if it fails. This |
| // workaround could be removed if JDK-8145241 is fixed. |
| float[] rgb = |
| iccColorSpace.toRGB(new float[] {1.0F, 1.0F, 1.0F}); |
| } catch (Exception iccProfileException) { |
| processWarningOccurred("Superseding bad ICC profile: " |
| + iccProfileException.getMessage()); |
| |
| if (iccColorSpace != null) { |
| switch (iccColorSpace.getType()) { |
| case ColorSpace.TYPE_GRAY: |
| iccColorSpace = |
| ColorSpace.getInstance(ColorSpace.CS_GRAY); |
| break; |
| case ColorSpace.TYPE_RGB: |
| iccColorSpace = |
| ColorSpace.getInstance(ColorSpace.CS_sRGB); |
| break; |
| default: |
| iccColorSpace = csRaw; |
| break; |
| } |
| } else { |
| iccColorSpace = csRaw; |
| } |
| } |
| |
| // Get the number of samples per pixel and the number |
| // of color components. |
| int numBands = smRaw.getNumBands(); |
| int numComponents = iccColorSpace.getNumComponents(); |
| |
| // Replace the ColorModel with the ICC ColorModel if the |
| // numbers of samples and color components are amenable. |
| if (numBands == numComponents |
| || numBands == numComponents + 1) { |
| // Set alpha flags. |
| boolean hasAlpha = numComponents != numBands; |
| boolean isAlphaPre |
| = hasAlpha && cmRaw.isAlphaPremultiplied(); |
| |
| // Create a ColorModel of the same class and with |
| // the same transfer type. |
| ColorModel iccColorModel |
| = new ComponentColorModel(iccColorSpace, |
| cmRaw.getComponentSize(), |
| hasAlpha, |
| isAlphaPre, |
| cmRaw.getTransparency(), |
| cmRaw.getTransferType()); |
| |
| // Prepend the ICC profile-based ITS to the List. The |
| // ColorModel and SampleModel are guaranteed to be |
| // compatible as the old and new ColorModels are both |
| // ComponentColorModels with the same transfer type |
| // and the same number of components. |
| l.add(new ImageTypeSpecifier(iccColorModel, smRaw)); |
| |
| // Append the raw ITS to the List if and only if its |
| // ColorSpace has the same type and number of components |
| // as the ICC ColorSpace. |
| if (csRaw.getType() == iccColorSpace.getType() |
| && csRaw.getNumComponents() |
| == iccColorSpace.getNumComponents()) { |
| l.add(itsRaw); |
| } |
| } else { // ICCProfile not compatible with SampleModel. |
| // Append the raw ITS to the List. |
| l.add(itsRaw); |
| } |
| } else { // No ICCProfile field or raw ColorModel not component. |
| // Append the raw ITS to the List. |
| l.add(itsRaw); |
| } |
| |
| // Cache the ITS List. |
| imageTypeMap.put(imageIndexInteger, l); |
| } |
| |
| return l.iterator(); |
| } |
| |
| public IIOMetadata getImageMetadata(int imageIndex) throws IIOException { |
| seekToImage(imageIndex); |
| TIFFImageMetadata im |
| = new TIFFImageMetadata(imageMetadata.getRootIFD().getTagSetList()); |
| Node root |
| = imageMetadata.getAsTree(TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME); |
| im.setFromTree(TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME, root); |
| return im; |
| } |
| |
| public IIOMetadata getStreamMetadata(int imageIndex) throws IIOException { |
| readHeader(); |
| TIFFStreamMetadata sm = new TIFFStreamMetadata(); |
| Node root = sm.getAsTree(TIFFStreamMetadata.NATIVE_METADATA_FORMAT_NAME); |
| sm.setFromTree(TIFFStreamMetadata.NATIVE_METADATA_FORMAT_NAME, root); |
| return sm; |
| } |
| |
| public boolean isRandomAccessEasy(int imageIndex) throws IOException { |
| if (currIndex != -1) { |
| seekToImage(currIndex); |
| return getCompression() == BaselineTIFFTagSet.COMPRESSION_NONE; |
| } else { |
| return false; |
| } |
| } |
| |
| // Thumbnails |
| public boolean readSupportsThumbnails() { |
| return false; |
| } |
| |
| public boolean hasThumbnails(int imageIndex) { |
| return false; |
| } |
| |
| public int getNumThumbnails(int imageIndex) throws IOException { |
| return 0; |
| } |
| |
| public ImageReadParam getDefaultReadParam() { |
| return new TIFFImageReadParam(); |
| } |
| |
| public boolean isImageTiled(int imageIndex) throws IOException { |
| seekToImage(imageIndex); |
| |
| TIFFField f |
| = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH); |
| return f != null; |
| } |
| |
| public int getTileWidth(int imageIndex) throws IOException { |
| seekToImage(imageIndex); |
| return getTileOrStripWidth(); |
| } |
| |
| public int getTileHeight(int imageIndex) throws IOException { |
| seekToImage(imageIndex); |
| return getTileOrStripHeight(); |
| } |
| |
| public BufferedImage readTile(int imageIndex, int tileX, int tileY) |
| throws IOException { |
| |
| int w = getWidth(imageIndex); |
| int h = getHeight(imageIndex); |
| int tw = getTileWidth(imageIndex); |
| int th = getTileHeight(imageIndex); |
| |
| int x = tw * tileX; |
| int y = th * tileY; |
| |
| if (tileX < 0 || tileY < 0 || x >= w || y >= h) { |
| throw new IllegalArgumentException("Tile indices are out of bounds!"); |
| } |
| |
| if (x + tw > w) { |
| tw = w - x; |
| } |
| |
| if (y + th > h) { |
| th = h - y; |
| } |
| |
| ImageReadParam param = getDefaultReadParam(); |
| Rectangle tileRect = new Rectangle(x, y, tw, th); |
| param.setSourceRegion(tileRect); |
| |
| return read(imageIndex, param); |
| } |
| |
| public boolean canReadRaster() { |
| return false; |
| } |
| |
| public Raster readRaster(int imageIndex, ImageReadParam param) |
| throws IOException { |
| throw new UnsupportedOperationException(); |
| } |
| |
| private int[] sourceBands; |
| private int[] destinationBands; |
| |
| private TIFFDecompressor decompressor; |
| |
| // floor(num/den) |
| private static int ifloor(int num, int den) { |
| if (num < 0) { |
| num -= den - 1; |
| } |
| return num / den; |
| } |
| |
| // ceil(num/den) |
| private static int iceil(int num, int den) { |
| if (num > 0) { |
| num += den - 1; |
| } |
| return num / den; |
| } |
| |
| private void prepareRead(int imageIndex, ImageReadParam param) |
| throws IOException { |
| if (stream == null) { |
| throw new IllegalStateException("Input not set!"); |
| } |
| |
| // A null ImageReadParam means we use the default |
| if (param == null) { |
| param = getDefaultReadParam(); |
| } |
| |
| this.imageReadParam = param; |
| |
| seekToImage(imageIndex); |
| |
| this.tileOrStripWidth = getTileOrStripWidth(); |
| this.tileOrStripHeight = getTileOrStripHeight(); |
| this.planarConfiguration = getPlanarConfiguration(); |
| |
| this.sourceBands = param.getSourceBands(); |
| if (sourceBands == null) { |
| sourceBands = new int[numBands]; |
| for (int i = 0; i < numBands; i++) { |
| sourceBands[i] = i; |
| } |
| } |
| |
| // Initialize the destination image |
| Iterator<ImageTypeSpecifier> imageTypes = getImageTypes(imageIndex); |
| ImageTypeSpecifier theImageType |
| = ImageUtil.getDestinationType(param, imageTypes); |
| |
| int destNumBands = theImageType.getSampleModel().getNumBands(); |
| |
| this.destinationBands = param.getDestinationBands(); |
| if (destinationBands == null) { |
| destinationBands = new int[destNumBands]; |
| for (int i = 0; i < destNumBands; i++) { |
| destinationBands[i] = i; |
| } |
| } |
| |
| if (sourceBands.length != destinationBands.length) { |
| throw new IllegalArgumentException( |
| "sourceBands.length != destinationBands.length"); |
| } |
| |
| for (int i = 0; i < sourceBands.length; i++) { |
| int sb = sourceBands[i]; |
| if (sb < 0 || sb >= numBands) { |
| throw new IllegalArgumentException( |
| "Source band out of range!"); |
| } |
| int db = destinationBands[i]; |
| if (db < 0 || db >= destNumBands) { |
| throw new IllegalArgumentException( |
| "Destination band out of range!"); |
| } |
| } |
| } |
| |
| public RenderedImage readAsRenderedImage(int imageIndex, |
| ImageReadParam param) |
| throws IOException { |
| prepareRead(imageIndex, param); |
| return new TIFFRenderedImage(this, imageIndex, imageReadParam, |
| width, height); |
| } |
| |
| private void decodeTile(int ti, int tj, int band) throws IOException { |
| // Compute the region covered by the strip or tile |
| Rectangle tileRect = new Rectangle(ti * tileOrStripWidth, |
| tj * tileOrStripHeight, |
| tileOrStripWidth, |
| tileOrStripHeight); |
| |
| // Clip against the image bounds if the image is not tiled. If it |
| // is tiled, the tile may legally extend beyond the image bounds. |
| if (!isImageTiled(currIndex)) { |
| tileRect |
| = tileRect.intersection(new Rectangle(0, 0, width, height)); |
| } |
| |
| // Return if the intersection is empty. |
| if (tileRect.width <= 0 || tileRect.height <= 0) { |
| return; |
| } |
| |
| int srcMinX = tileRect.x; |
| int srcMinY = tileRect.y; |
| int srcWidth = tileRect.width; |
| int srcHeight = tileRect.height; |
| |
| // Determine dest region that can be derived from the |
| // source region |
| dstMinX = iceil(srcMinX - sourceXOffset, srcXSubsampling); |
| int dstMaxX = ifloor(srcMinX + srcWidth - 1 - sourceXOffset, |
| srcXSubsampling); |
| |
| dstMinY = iceil(srcMinY - sourceYOffset, srcYSubsampling); |
| int dstMaxY = ifloor(srcMinY + srcHeight - 1 - sourceYOffset, |
| srcYSubsampling); |
| |
| dstWidth = dstMaxX - dstMinX + 1; |
| dstHeight = dstMaxY - dstMinY + 1; |
| |
| dstMinX += dstXOffset; |
| dstMinY += dstYOffset; |
| |
| // Clip against image bounds |
| Rectangle dstRect = new Rectangle(dstMinX, dstMinY, |
| dstWidth, dstHeight); |
| dstRect |
| = dstRect.intersection(theImage.getRaster().getBounds()); |
| |
| dstMinX = dstRect.x; |
| dstMinY = dstRect.y; |
| dstWidth = dstRect.width; |
| dstHeight = dstRect.height; |
| |
| if (dstWidth <= 0 || dstHeight <= 0) { |
| return; |
| } |
| |
| // Backwards map dest region to source to determine |
| // active source region |
| int activeSrcMinX = (dstMinX - dstXOffset) * srcXSubsampling |
| + sourceXOffset; |
| int sxmax |
| = (dstMinX + dstWidth - 1 - dstXOffset) * srcXSubsampling |
| + sourceXOffset; |
| int activeSrcWidth = sxmax - activeSrcMinX + 1; |
| |
| int activeSrcMinY = (dstMinY - dstYOffset) * srcYSubsampling |
| + sourceYOffset; |
| int symax |
| = (dstMinY + dstHeight - 1 - dstYOffset) * srcYSubsampling |
| + sourceYOffset; |
| int activeSrcHeight = symax - activeSrcMinY + 1; |
| |
| decompressor.setSrcMinX(srcMinX); |
| decompressor.setSrcMinY(srcMinY); |
| decompressor.setSrcWidth(srcWidth); |
| decompressor.setSrcHeight(srcHeight); |
| |
| decompressor.setDstMinX(dstMinX); |
| decompressor.setDstMinY(dstMinY); |
| decompressor.setDstWidth(dstWidth); |
| decompressor.setDstHeight(dstHeight); |
| |
| decompressor.setActiveSrcMinX(activeSrcMinX); |
| decompressor.setActiveSrcMinY(activeSrcMinY); |
| decompressor.setActiveSrcWidth(activeSrcWidth); |
| decompressor.setActiveSrcHeight(activeSrcHeight); |
| |
| int tileIndex = tj * tilesAcross + ti; |
| |
| if (planarConfiguration |
| == BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) { |
| tileIndex += band * tilesAcross * tilesDown; |
| } |
| |
| long offset = getTileOrStripOffset(tileIndex); |
| long byteCount = getTileOrStripByteCount(tileIndex); |
| |
| decompressor.setPlanarBand(band); |
| decompressor.setStream(stream); |
| decompressor.setOffset(offset); |
| decompressor.setByteCount((int) byteCount); |
| |
| decompressor.beginDecoding(); |
| |
| stream.mark(); |
| decompressor.decode(); |
| stream.reset(); |
| } |
| |
| private void reportProgress() { |
| // Report image progress/update to listeners after each tile |
| pixelsRead += dstWidth * dstHeight; |
| processImageProgress(100.0f * pixelsRead / pixelsToRead); |
| processImageUpdate(theImage, |
| dstMinX, dstMinY, dstWidth, dstHeight, |
| 1, 1, |
| destinationBands); |
| } |
| |
| public BufferedImage read(int imageIndex, ImageReadParam param) |
| throws IOException { |
| prepareRead(imageIndex, param); |
| this.theImage = getDestination(param, |
| getImageTypes(imageIndex), |
| width, height); |
| |
| srcXSubsampling = imageReadParam.getSourceXSubsampling(); |
| srcYSubsampling = imageReadParam.getSourceYSubsampling(); |
| |
| Point p = imageReadParam.getDestinationOffset(); |
| dstXOffset = p.x; |
| dstYOffset = p.y; |
| |
| // This could probably be made more efficient... |
| Rectangle srcRegion = new Rectangle(0, 0, 0, 0); |
| Rectangle destRegion = new Rectangle(0, 0, 0, 0); |
| |
| computeRegions(imageReadParam, width, height, theImage, |
| srcRegion, destRegion); |
| |
| // Initial source pixel, taking source region and source |
| // subsamplimg offsets into account |
| sourceXOffset = srcRegion.x; |
| sourceYOffset = srcRegion.y; |
| |
| pixelsToRead = destRegion.width * destRegion.height; |
| pixelsRead = 0; |
| |
| clearAbortRequest(); |
| processImageStarted(imageIndex); |
| if (abortRequested()) { |
| processReadAborted(); |
| return theImage; |
| } |
| |
| tilesAcross = (width + tileOrStripWidth - 1) / tileOrStripWidth; |
| tilesDown = (height + tileOrStripHeight - 1) / tileOrStripHeight; |
| |
| int compression = getCompression(); |
| |
| // Set the decompressor |
| if (compression == BaselineTIFFTagSet.COMPRESSION_NONE) { |
| // Get the fillOrder field. |
| TIFFField fillOrderField |
| = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_FILL_ORDER); |
| |
| // Set the decompressor based on the fill order. |
| if (fillOrderField != null && fillOrderField.getAsInt(0) == 2) { |
| this.decompressor = new TIFFLSBDecompressor(); |
| } else { |
| this.decompressor = new TIFFNullDecompressor(); |
| } |
| } else if (compression |
| == BaselineTIFFTagSet.COMPRESSION_CCITT_T_6) { |
| this.decompressor = new TIFFFaxDecompressor(); |
| } else if (compression |
| == BaselineTIFFTagSet.COMPRESSION_CCITT_T_4) { |
| this.decompressor = new TIFFFaxDecompressor(); |
| } else if (compression |
| == BaselineTIFFTagSet.COMPRESSION_CCITT_RLE) { |
| this.decompressor = new TIFFFaxDecompressor(); |
| } else if (compression |
| == BaselineTIFFTagSet.COMPRESSION_PACKBITS) { |
| this.decompressor = new TIFFPackBitsDecompressor(); |
| } else if (compression |
| == BaselineTIFFTagSet.COMPRESSION_LZW) { |
| TIFFField predictorField |
| = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PREDICTOR); |
| int predictor = ((predictorField == null) |
| ? BaselineTIFFTagSet.PREDICTOR_NONE |
| : predictorField.getAsInt(0)); |
| |
| TIFFField fillOrderField |
| = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_FILL_ORDER); |
| int fillOrder = ((fillOrderField == null) |
| ? BaselineTIFFTagSet.FILL_ORDER_LEFT_TO_RIGHT |
| : fillOrderField.getAsInt(0)); |
| |
| this.decompressor = new TIFFLZWDecompressor(predictor, fillOrder); |
| } else if (compression |
| == BaselineTIFFTagSet.COMPRESSION_JPEG) { |
| this.decompressor = new TIFFJPEGDecompressor(); |
| } else if (compression |
| == BaselineTIFFTagSet.COMPRESSION_ZLIB |
| || compression |
| == BaselineTIFFTagSet.COMPRESSION_DEFLATE) { |
| TIFFField predictorField |
| = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PREDICTOR); |
| int predictor = ((predictorField == null) |
| ? BaselineTIFFTagSet.PREDICTOR_NONE |
| : predictorField.getAsInt(0)); |
| this.decompressor = new TIFFDeflateDecompressor(predictor); |
| } else if (compression |
| == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) { |
| TIFFField JPEGProcField |
| = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_PROC); |
| if (JPEGProcField == null) { |
| processWarningOccurred("JPEGProc field missing; assuming baseline sequential JPEG process."); |
| } else if (JPEGProcField.getAsInt(0) |
| != BaselineTIFFTagSet.JPEG_PROC_BASELINE) { |
| throw new IIOException("Old-style JPEG supported for baseline sequential JPEG process only!"); |
| } |
| this.decompressor = new TIFFOldJPEGDecompressor(); |
| //throw new IIOException("Old-style JPEG not supported!"); |
| } else { |
| throw new IIOException("Unsupported compression type (tag value = " |
| + compression + ")!"); |
| } |
| |
| if (photometricInterpretation |
| == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR |
| && compression != BaselineTIFFTagSet.COMPRESSION_JPEG |
| && compression != BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) { |
| boolean convertYCbCrToRGB |
| = theImage.getColorModel().getColorSpace().getType() |
| == ColorSpace.TYPE_RGB; |
| TIFFDecompressor wrappedDecompressor |
| = this.decompressor instanceof TIFFNullDecompressor |
| ? null : this.decompressor; |
| this.decompressor |
| = new TIFFYCbCrDecompressor(wrappedDecompressor, |
| convertYCbCrToRGB); |
| } |
| |
| TIFFColorConverter colorConverter = null; |
| if (photometricInterpretation |
| == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CIELAB |
| && theImage.getColorModel().getColorSpace().getType() |
| == ColorSpace.TYPE_RGB) { |
| colorConverter = new TIFFCIELabColorConverter(); |
| } else if (photometricInterpretation |
| == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR |
| && !(this.decompressor instanceof TIFFYCbCrDecompressor) |
| && compression != BaselineTIFFTagSet.COMPRESSION_JPEG |
| && compression != BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) { |
| colorConverter = new TIFFYCbCrColorConverter(imageMetadata); |
| } |
| |
| decompressor.setReader(this); |
| decompressor.setMetadata(imageMetadata); |
| decompressor.setImage(theImage); |
| |
| decompressor.setPhotometricInterpretation(photometricInterpretation); |
| decompressor.setCompression(compression); |
| decompressor.setSamplesPerPixel(samplesPerPixel); |
| decompressor.setBitsPerSample(bitsPerSample); |
| decompressor.setSampleFormat(sampleFormat); |
| decompressor.setExtraSamples(extraSamples); |
| decompressor.setColorMap(colorMap); |
| |
| decompressor.setColorConverter(colorConverter); |
| |
| decompressor.setSourceXOffset(sourceXOffset); |
| decompressor.setSourceYOffset(sourceYOffset); |
| decompressor.setSubsampleX(srcXSubsampling); |
| decompressor.setSubsampleY(srcYSubsampling); |
| |
| decompressor.setDstXOffset(dstXOffset); |
| decompressor.setDstYOffset(dstYOffset); |
| |
| decompressor.setSourceBands(sourceBands); |
| decompressor.setDestinationBands(destinationBands); |
| |
| // Compute bounds on the tile indices for this source region. |
| int minTileX |
| = TIFFImageWriter.XToTileX(srcRegion.x, 0, tileOrStripWidth); |
| int minTileY |
| = TIFFImageWriter.YToTileY(srcRegion.y, 0, tileOrStripHeight); |
| int maxTileX |
| = TIFFImageWriter.XToTileX(srcRegion.x + srcRegion.width - 1, |
| 0, tileOrStripWidth); |
| int maxTileY |
| = TIFFImageWriter.YToTileY(srcRegion.y + srcRegion.height - 1, |
| 0, tileOrStripHeight); |
| |
| if (planarConfiguration |
| == BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) { |
| |
| decompressor.setPlanar(true); |
| |
| int[] sb = new int[1]; |
| int[] db = new int[1]; |
| for (int tj = minTileY; tj <= maxTileY; tj++) { |
| for (int ti = minTileX; ti <= maxTileX; ti++) { |
| for (int band = 0; band < numBands; band++) { |
| sb[0] = sourceBands[band]; |
| decompressor.setSourceBands(sb); |
| db[0] = destinationBands[band]; |
| decompressor.setDestinationBands(db); |
| |
| decodeTile(ti, tj, band); |
| } |
| |
| reportProgress(); |
| if (abortRequested()) { |
| processReadAborted(); |
| return theImage; |
| } |
| } |
| } |
| } else { |
| for (int tj = minTileY; tj <= maxTileY; tj++) { |
| for (int ti = minTileX; ti <= maxTileX; ti++) { |
| decodeTile(ti, tj, -1); |
| |
| reportProgress(); |
| if (abortRequested()) { |
| processReadAborted(); |
| return theImage; |
| } |
| } |
| } |
| } |
| processImageComplete(); |
| return theImage; |
| } |
| |
| public void reset() { |
| super.reset(); |
| resetLocal(); |
| } |
| |
| protected void resetLocal() { |
| stream = null; |
| gotHeader = false; |
| imageReadParam = getDefaultReadParam(); |
| streamMetadata = null; |
| currIndex = -1; |
| imageMetadata = null; |
| imageStartPosition = new ArrayList<Long>(); |
| numImages = -1; |
| imageTypeMap = new HashMap<Integer, List<ImageTypeSpecifier>>(); |
| width = -1; |
| height = -1; |
| numBands = -1; |
| tileOrStripWidth = -1; |
| tileOrStripHeight = -1; |
| planarConfiguration = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY; |
| } |
| |
| /** |
| * Package scope method to allow decompressors, for example, to emit warning |
| * messages. |
| */ |
| void forwardWarningMessage(String warning) { |
| processWarningOccurred(warning); |
| } |
| } |