0001: /*
0002: * $RCSfile: TIFFImageReader.java,v $
0003: *
0004: *
0005: * Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved.
0006: *
0007: * Redistribution and use in source and binary forms, with or without
0008: * modification, are permitted provided that the following conditions
0009: * are met:
0010: *
0011: * - Redistribution of source code must retain the above copyright
0012: * notice, this list of conditions and the following disclaimer.
0013: *
0014: * - Redistribution in binary form must reproduce the above copyright
0015: * notice, this list of conditions and the following disclaimer in
0016: * the documentation and/or other materials provided with the
0017: * distribution.
0018: *
0019: * Neither the name of Sun Microsystems, Inc. or the names of
0020: * contributors may be used to endorse or promote products derived
0021: * from this software without specific prior written permission.
0022: *
0023: * This software is provided "AS IS," without a warranty of any
0024: * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
0025: * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
0026: * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
0027: * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
0028: * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
0029: * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
0030: * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
0031: * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
0032: * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
0033: * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
0034: * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
0035: * POSSIBILITY OF SUCH DAMAGES.
0036: *
0037: * You acknowledge that this software is not designed or intended for
0038: * use in the design, construction, operation or maintenance of any
0039: * nuclear facility.
0040: *
0041: * $Revision: 1.13 $
0042: * $Date: 2007/12/19 20:17:02 $
0043: * $State: Exp $
0044: */
0045: package com.sun.media.imageioimpl.plugins.tiff;
0046:
0047: import java.awt.Point;
0048: import java.awt.Rectangle;
0049: import java.awt.color.ColorSpace;
0050: import java.awt.color.ICC_ColorSpace;
0051: import java.awt.color.ICC_Profile;
0052: import java.awt.image.BufferedImage;
0053: import java.awt.image.ColorModel;
0054: import java.awt.image.ComponentColorModel;
0055: import java.awt.image.ComponentSampleModel;
0056: import java.awt.image.DataBuffer;
0057: import java.awt.image.DataBufferByte;
0058: import java.awt.image.MultiPixelPackedSampleModel;
0059: import java.awt.image.Raster;
0060: import java.awt.image.RenderedImage;
0061: import java.awt.image.SampleModel;
0062: import java.awt.image.SinglePixelPackedSampleModel;
0063: import java.awt.image.WritableRaster;
0064: import java.io.ByteArrayInputStream;
0065: import java.io.IOException;
0066: import java.io.InputStream;
0067: import java.nio.ByteOrder;
0068: import java.util.ArrayList;
0069: import java.util.Arrays;
0070: import java.util.HashMap;
0071: import java.util.Iterator;
0072: import java.util.List;
0073: import javax.imageio.IIOException;
0074: import javax.imageio.ImageIO;
0075: import javax.imageio.ImageReader;
0076: import javax.imageio.ImageReadParam;
0077: import javax.imageio.ImageTypeSpecifier;
0078: import javax.imageio.metadata.IIOMetadata;
0079: import javax.imageio.spi.ImageReaderSpi;
0080: import javax.imageio.stream.ImageInputStream;
0081: import javax.imageio.stream.MemoryCacheImageInputStream;
0082: import org.w3c.dom.Node;
0083: import com.sun.media.imageio.plugins.tiff.BaselineTIFFTagSet;
0084: import com.sun.media.imageio.plugins.tiff.TIFFColorConverter;
0085: import com.sun.media.imageio.plugins.tiff.TIFFDecompressor;
0086: import com.sun.media.imageio.plugins.tiff.TIFFField;
0087: import com.sun.media.imageio.plugins.tiff.TIFFImageReadParam;
0088: import com.sun.media.imageio.plugins.tiff.TIFFTag;
0089: import com.sun.media.imageioimpl.common.ImageUtil;
0090: import com.sun.media.imageioimpl.common.PackageUtil;
0091:
0092: public class TIFFImageReader extends ImageReader {
0093:
0094: private static final boolean DEBUG = false; // XXX 'false' for release!!!
0095:
0096: // The current ImageInputStream source.
0097: ImageInputStream stream = null;
0098:
0099: // True if the file header has been read.
0100: boolean gotHeader = false;
0101:
0102: ImageReadParam imageReadParam = getDefaultReadParam();
0103:
0104: // Stream metadata, or null.
0105: TIFFStreamMetadata streamMetadata = null;
0106:
0107: // The current image index.
0108: int currIndex = -1;
0109:
0110: // Metadata for image at 'currIndex', or null.
0111: TIFFImageMetadata imageMetadata = null;
0112:
0113: // A <code>List</code> of <code>Long</code>s indicating the stream
0114: // positions of the start of the IFD for each image. Entries
0115: // are added as needed.
0116: List imageStartPosition = new ArrayList();
0117:
0118: // The number of images in the stream, if known, otherwise -1.
0119: int numImages = -1;
0120:
0121: // The ImageTypeSpecifiers of the images in the stream.
0122: // Contains a map of Integers to Lists.
0123: HashMap imageTypeMap = new HashMap();
0124:
0125: BufferedImage theImage = null;
0126:
0127: int width = -1;
0128: int height = -1;
0129: int numBands = -1;
0130: int tileOrStripWidth = -1, tileOrStripHeight = -1;
0131:
0132: int planarConfiguration = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
0133:
0134: int rowsDone = 0;
0135:
0136: int compression;
0137: int photometricInterpretation;
0138: int samplesPerPixel;
0139: int[] sampleFormat;
0140: int[] bitsPerSample;
0141: int[] extraSamples;
0142: char[] colorMap;
0143:
0144: int sourceXOffset;
0145: int sourceYOffset;
0146: int srcXSubsampling;
0147: int srcYSubsampling;
0148:
0149: int dstWidth;
0150: int dstHeight;
0151: int dstMinX;
0152: int dstMinY;
0153: int dstXOffset;
0154: int dstYOffset;
0155:
0156: int tilesAcross;
0157: int tilesDown;
0158:
0159: int pixelsRead;
0160: int pixelsToRead;
0161:
0162: public TIFFImageReader(ImageReaderSpi originatingProvider) {
0163: super (originatingProvider);
0164: }
0165:
0166: public void setInput(Object input, boolean seekForwardOnly,
0167: boolean ignoreMetadata) {
0168: super .setInput(input, seekForwardOnly, ignoreMetadata);
0169:
0170: // Clear all local values based on the previous stream contents.
0171: resetLocal();
0172:
0173: if (input != null) {
0174: if (!(input instanceof ImageInputStream)) {
0175: throw new IllegalArgumentException(
0176: "input not an ImageInputStream!");
0177: }
0178: this .stream = (ImageInputStream) input;
0179: } else {
0180: this .stream = null;
0181: }
0182: }
0183:
0184: // Do not seek to the beginning of the stream so as to allow users to
0185: // point us at an IFD within some other file format
0186: private void readHeader() throws IIOException {
0187: if (gotHeader) {
0188: return;
0189: }
0190: if (stream == null) {
0191: throw new IllegalStateException("Input not set!");
0192: }
0193:
0194: // Create an object to store the stream metadata
0195: this .streamMetadata = new TIFFStreamMetadata();
0196:
0197: try {
0198: int byteOrder = stream.readUnsignedShort();
0199: if (byteOrder == 0x4d4d) {
0200: streamMetadata.byteOrder = ByteOrder.BIG_ENDIAN;
0201: stream.setByteOrder(ByteOrder.BIG_ENDIAN);
0202: } else if (byteOrder == 0x4949) {
0203: streamMetadata.byteOrder = ByteOrder.LITTLE_ENDIAN;
0204: stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
0205: } else {
0206: processWarningOccurred("Bad byte order in header, assuming little-endian");
0207: streamMetadata.byteOrder = ByteOrder.LITTLE_ENDIAN;
0208: stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
0209: }
0210:
0211: int magic = stream.readUnsignedShort();
0212: if (magic != 42) {
0213: processWarningOccurred("Bad magic number in header, continuing");
0214: }
0215:
0216: // Seek to start of first IFD
0217: long offset = stream.readUnsignedInt();
0218: imageStartPosition.add(new Long(offset));
0219: stream.seek(offset);
0220: } catch (IOException e) {
0221: throw new IIOException("I/O error reading header!", e);
0222: }
0223:
0224: gotHeader = true;
0225: }
0226:
0227: private int locateImage(int imageIndex) throws IIOException {
0228: readHeader();
0229:
0230: try {
0231: // Find closest known index
0232: int index = Math.min(imageIndex,
0233: imageStartPosition.size() - 1);
0234:
0235: // Seek to that position
0236: Long l = (Long) imageStartPosition.get(index);
0237: stream.seek(l.longValue());
0238:
0239: // Skip IFDs until at desired index or last image found
0240: while (index < imageIndex) {
0241: int count = stream.readUnsignedShort();
0242: stream.skipBytes(12 * count);
0243:
0244: long offset = stream.readUnsignedInt();
0245: if (offset == 0) {
0246: return index;
0247: }
0248:
0249: imageStartPosition.add(new Long(offset));
0250: stream.seek(offset);
0251: ++index;
0252: }
0253: } catch (IOException e) {
0254: throw new IIOException("Couldn't seek!", e);
0255: }
0256:
0257: if (currIndex != imageIndex) {
0258: imageMetadata = null;
0259: }
0260: currIndex = imageIndex;
0261: return imageIndex;
0262: }
0263:
0264: public int getNumImages(boolean allowSearch) throws IOException {
0265: if (stream == null) {
0266: throw new IllegalStateException("Input not set!");
0267: }
0268: if (seekForwardOnly && allowSearch) {
0269: throw new IllegalStateException(
0270: "seekForwardOnly and allowSearch can't both be true!");
0271: }
0272:
0273: if (numImages > 0) {
0274: return numImages;
0275: }
0276: if (allowSearch) {
0277: this .numImages = locateImage(Integer.MAX_VALUE) + 1;
0278: }
0279: return numImages;
0280: }
0281:
0282: public IIOMetadata getStreamMetadata() throws IIOException {
0283: readHeader();
0284: return streamMetadata;
0285: }
0286:
0287: // Throw an IndexOutOfBoundsException if index < minIndex,
0288: // and bump minIndex if required.
0289: private void checkIndex(int imageIndex) {
0290: if (imageIndex < minIndex) {
0291: throw new IndexOutOfBoundsException(
0292: "imageIndex < minIndex!");
0293: }
0294: if (seekForwardOnly) {
0295: minIndex = imageIndex;
0296: }
0297: }
0298:
0299: // Verify that imageIndex is in bounds, find the image IFD, read the
0300: // image metadata, initialize instance variables from the metadata.
0301: private void seekToImage(int imageIndex) throws IIOException {
0302: checkIndex(imageIndex);
0303:
0304: int index = locateImage(imageIndex);
0305: if (index != imageIndex) {
0306: throw new IndexOutOfBoundsException(
0307: "imageIndex out of bounds!");
0308: }
0309:
0310: readMetadata();
0311:
0312: initializeFromMetadata();
0313: }
0314:
0315: // Stream must be positioned at start of IFD for 'currIndex'
0316: private void readMetadata() throws IIOException {
0317: if (stream == null) {
0318: throw new IllegalStateException("Input not set!");
0319: }
0320:
0321: if (imageMetadata != null) {
0322: return;
0323: }
0324: try {
0325: // Create an object to store the image metadata
0326: List tagSets;
0327: if (imageReadParam instanceof TIFFImageReadParam) {
0328: tagSets = ((TIFFImageReadParam) imageReadParam)
0329: .getAllowedTagSets();
0330: } else {
0331: tagSets = new ArrayList(1);
0332: tagSets.add(BaselineTIFFTagSet.getInstance());
0333: }
0334:
0335: this .imageMetadata = new TIFFImageMetadata(tagSets);
0336: imageMetadata.initializeFromStream(stream, ignoreMetadata);
0337: } catch (IIOException iioe) {
0338: throw iioe;
0339: } catch (IOException ioe) {
0340: throw new IIOException("I/O error reading image metadata!",
0341: ioe);
0342: }
0343: }
0344:
0345: private int getWidth() {
0346: return this .width;
0347: }
0348:
0349: private int getHeight() {
0350: return this .height;
0351: }
0352:
0353: private int getNumBands() {
0354: return this .numBands;
0355: }
0356:
0357: // Returns tile width if image is tiled, else image width
0358: private int getTileOrStripWidth() {
0359: TIFFField f = imageMetadata
0360: .getTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH);
0361: return (f == null) ? getWidth() : f.getAsInt(0);
0362: }
0363:
0364: // Returns tile height if image is tiled, else strip height
0365: private int getTileOrStripHeight() {
0366: TIFFField f = imageMetadata
0367: .getTIFFField(BaselineTIFFTagSet.TAG_TILE_LENGTH);
0368: if (f != null) {
0369: return f.getAsInt(0);
0370: }
0371:
0372: f = imageMetadata
0373: .getTIFFField(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP);
0374: // Default for ROWS_PER_STRIP is 2^32 - 1, i.e., infinity
0375: int h = (f == null) ? -1 : f.getAsInt(0);
0376: return (h == -1) ? getHeight() : h;
0377: }
0378:
0379: private int getPlanarConfiguration() {
0380: TIFFField f = imageMetadata
0381: .getTIFFField(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION);
0382: if (f != null) {
0383: int planarConfigurationValue = f.getAsInt(0);
0384: if (planarConfigurationValue == BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) {
0385: // Some writers (e.g. Kofax standard Multi-Page TIFF
0386: // Storage Filter v2.01.000; cf. bug 4929147) do not
0387: // correctly set the value of this field. Attempt to
0388: // ascertain whether the value is correctly Planar.
0389: if (getCompression() == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG
0390: && imageMetadata
0391: .getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT) != null) {
0392: // JPEG interchange format cannot have
0393: // PlanarConfiguration value Chunky so reset.
0394: processWarningOccurred("PlanarConfiguration \"Planar\" value inconsistent with JPEGInterchangeFormat; resetting to \"Chunky\".");
0395: planarConfigurationValue = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
0396: } else {
0397: TIFFField offsetField = imageMetadata
0398: .getTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS);
0399: if (offsetField == null) {
0400: // Tiles
0401: offsetField = imageMetadata
0402: .getTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS);
0403: int tw = getTileOrStripWidth();
0404: int th = getTileOrStripHeight();
0405: int tAcross = (getWidth() + tw - 1) / tw;
0406: int tDown = (getHeight() + th - 1) / th;
0407: int tilesPerImage = tAcross * tDown;
0408: long[] offsetArray = offsetField.getAsLongs();
0409: if (offsetArray != null
0410: && offsetArray.length == tilesPerImage) {
0411: // Length of offsets array is
0412: // TilesPerImage for Chunky and
0413: // SamplesPerPixel*TilesPerImage for Planar.
0414: processWarningOccurred("PlanarConfiguration \"Planar\" value inconsistent with TileOffsets field value count; resetting to \"Chunky\".");
0415: planarConfigurationValue = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
0416: }
0417: } else {
0418: // Strips
0419: int rowsPerStrip = getTileOrStripHeight();
0420: int stripsPerImage = (getHeight()
0421: + rowsPerStrip - 1)
0422: / rowsPerStrip;
0423: long[] offsetArray = offsetField.getAsLongs();
0424: if (offsetArray != null
0425: && offsetArray.length == stripsPerImage) {
0426: // Length of offsets array is
0427: // StripsPerImage for Chunky and
0428: // SamplesPerPixel*StripsPerImage for Planar.
0429: processWarningOccurred("PlanarConfiguration \"Planar\" value inconsistent with StripOffsets field value count; resetting to \"Chunky\".");
0430: planarConfigurationValue = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
0431: }
0432: }
0433: }
0434: }
0435: return planarConfigurationValue;
0436: }
0437:
0438: return BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
0439: }
0440:
0441: private long getTileOrStripOffset(int tileIndex)
0442: throws IIOException {
0443: TIFFField f = imageMetadata
0444: .getTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS);
0445: if (f == null) {
0446: f = imageMetadata
0447: .getTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS);
0448: }
0449: if (f == null) {
0450: f = imageMetadata
0451: .getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT);
0452: }
0453:
0454: if (f == null) {
0455: throw new IIOException(
0456: "Missing required strip or tile offsets field.");
0457: }
0458:
0459: return f.getAsLong(tileIndex);
0460: }
0461:
0462: private long getTileOrStripByteCount(int tileIndex)
0463: throws IOException {
0464: TIFFField f = imageMetadata
0465: .getTIFFField(BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS);
0466: if (f == null) {
0467: f = imageMetadata
0468: .getTIFFField(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS);
0469: }
0470: if (f == null) {
0471: f = imageMetadata
0472: .getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
0473: }
0474:
0475: long tileOrStripByteCount;
0476: if (f != null) {
0477: tileOrStripByteCount = f.getAsLong(tileIndex);
0478: } else {
0479: processWarningOccurred("TIFF directory contains neither StripByteCounts nor TileByteCounts field: attempting to calculate from strip or tile width and height.");
0480:
0481: // Initialize to number of bytes per strip or tile assuming
0482: // no compression.
0483: int bitsPerPixel = bitsPerSample[0];
0484: for (int i = 1; i < samplesPerPixel; i++) {
0485: bitsPerPixel += bitsPerSample[i];
0486: }
0487: int bytesPerRow = (getTileOrStripWidth() * bitsPerPixel + 7) / 8;
0488: tileOrStripByteCount = bytesPerRow * getTileOrStripHeight();
0489:
0490: // Clamp to end of stream if possible.
0491: long streamLength = stream.length();
0492: if (streamLength != -1) {
0493: tileOrStripByteCount = Math.min(tileOrStripByteCount,
0494: streamLength - getTileOrStripOffset(tileIndex));
0495: } else {
0496: processWarningOccurred("Stream length is unknown: cannot clamp estimated strip or tile byte count to EOF.");
0497: }
0498: }
0499:
0500: return tileOrStripByteCount;
0501: }
0502:
0503: private int getCompression() {
0504: TIFFField f = imageMetadata
0505: .getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION);
0506: if (f == null) {
0507: return BaselineTIFFTagSet.COMPRESSION_NONE;
0508: } else {
0509: return f.getAsInt(0);
0510: }
0511: }
0512:
0513: public int getWidth(int imageIndex) throws IOException {
0514: seekToImage(imageIndex);
0515: return getWidth();
0516: }
0517:
0518: public int getHeight(int imageIndex) throws IOException {
0519: seekToImage(imageIndex);
0520: return getHeight();
0521: }
0522:
0523: /**
0524: * Initializes these instance variables from the image metadata:
0525: * <pre>
0526: * compression
0527: * width
0528: * height
0529: * samplesPerPixel
0530: * numBands
0531: * colorMap
0532: * photometricInterpretation
0533: * sampleFormat
0534: * bitsPerSample
0535: * extraSamples
0536: * tileOrStripWidth
0537: * tileOrStripHeight
0538: * </pre>
0539: */
0540: private void initializeFromMetadata() {
0541: TIFFField f;
0542:
0543: // Compression
0544: f = imageMetadata
0545: .getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION);
0546: if (f == null) {
0547: processWarningOccurred("Compression field is missing; assuming no compression");
0548: compression = BaselineTIFFTagSet.COMPRESSION_NONE;
0549: } else {
0550: compression = f.getAsInt(0);
0551: }
0552:
0553: // Whether key dimensional information is absent.
0554: boolean isMissingDimension = false;
0555:
0556: // ImageWidth -> width
0557: f = imageMetadata
0558: .getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH);
0559: if (f != null) {
0560: this .width = f.getAsInt(0);
0561: } else {
0562: processWarningOccurred("ImageWidth field is missing.");
0563: isMissingDimension = true;
0564: }
0565:
0566: // ImageLength -> height
0567: f = imageMetadata
0568: .getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH);
0569: if (f != null) {
0570: this .height = f.getAsInt(0);
0571: } else {
0572: processWarningOccurred("ImageLength field is missing.");
0573: isMissingDimension = true;
0574: }
0575:
0576: // SamplesPerPixel
0577: f = imageMetadata
0578: .getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL);
0579: if (f != null) {
0580: samplesPerPixel = f.getAsInt(0);
0581: } else {
0582: samplesPerPixel = 1;
0583: isMissingDimension = true;
0584: }
0585:
0586: // If any dimension is missing and there is a JPEG stream available
0587: // get the information from it.
0588: int defaultBitDepth = 1;
0589: if (isMissingDimension
0590: && (f = imageMetadata
0591: .getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT)) != null) {
0592: Iterator iter = ImageIO.getImageReadersByFormatName("JPEG");
0593: if (iter != null && iter.hasNext()) {
0594: ImageReader jreader = (ImageReader) iter.next();
0595: try {
0596: stream.mark();
0597: stream.seek(f.getAsLong(0));
0598: jreader.setInput(stream);
0599: if (imageMetadata
0600: .getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH) == null) {
0601: this .width = jreader.getWidth(0);
0602: }
0603: if (imageMetadata
0604: .getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH) == null) {
0605: this .height = jreader.getHeight(0);
0606: }
0607: ImageTypeSpecifier imageType = jreader
0608: .getRawImageType(0);
0609: if (imageMetadata
0610: .getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL) == null) {
0611: this .samplesPerPixel = imageType
0612: .getSampleModel().getNumBands();
0613: }
0614: stream.reset();
0615: defaultBitDepth = imageType.getColorModel()
0616: .getComponentSize(0);
0617: } catch (IOException e) {
0618: // Ignore it and proceed: an error will occur later.
0619: }
0620: jreader.dispose();
0621: }
0622: }
0623:
0624: if (samplesPerPixel < 1) {
0625: processWarningOccurred("Samples per pixel < 1!");
0626: }
0627:
0628: // SamplesPerPixel -> numBands
0629: numBands = samplesPerPixel;
0630:
0631: // ColorMap
0632: this .colorMap = null;
0633: f = imageMetadata
0634: .getTIFFField(BaselineTIFFTagSet.TAG_COLOR_MAP);
0635: if (f != null) {
0636: // Grab color map
0637: colorMap = f.getAsChars();
0638: }
0639:
0640: // PhotometricInterpretation
0641: f = imageMetadata
0642: .getTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION);
0643: if (f == null) {
0644: if (compression == BaselineTIFFTagSet.COMPRESSION_CCITT_RLE
0645: || compression == BaselineTIFFTagSet.COMPRESSION_CCITT_T_4
0646: || compression == BaselineTIFFTagSet.COMPRESSION_CCITT_T_6) {
0647: processWarningOccurred("PhotometricInterpretation field is missing; "
0648: + "assuming WhiteIsZero");
0649: photometricInterpretation = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO;
0650: } else if (this .colorMap != null) {
0651: photometricInterpretation = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR;
0652: } else if (samplesPerPixel == 3 || samplesPerPixel == 4) {
0653: photometricInterpretation = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB;
0654: } else {
0655: processWarningOccurred("PhotometricInterpretation field is missing; "
0656: + "assuming BlackIsZero");
0657: photometricInterpretation = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO;
0658: }
0659: } else {
0660: photometricInterpretation = f.getAsInt(0);
0661: }
0662:
0663: // SampleFormat
0664: boolean replicateFirst = false;
0665: int first = -1;
0666:
0667: f = imageMetadata
0668: .getTIFFField(BaselineTIFFTagSet.TAG_SAMPLE_FORMAT);
0669: sampleFormat = new int[samplesPerPixel];
0670: replicateFirst = false;
0671: if (f == null) {
0672: replicateFirst = true;
0673: first = BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED;
0674: } else if (f.getCount() != samplesPerPixel) {
0675: replicateFirst = true;
0676: first = f.getAsInt(0);
0677: }
0678:
0679: for (int i = 0; i < samplesPerPixel; i++) {
0680: sampleFormat[i] = replicateFirst ? first : f.getAsInt(i);
0681: if (sampleFormat[i] != BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER
0682: && sampleFormat[i] != BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER
0683: && sampleFormat[i] != BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT
0684: && sampleFormat[i] != BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED) {
0685: processWarningOccurred("Illegal value for SAMPLE_FORMAT, assuming SAMPLE_FORMAT_UNDEFINED");
0686: sampleFormat[i] = BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED;
0687: }
0688: }
0689:
0690: // BitsPerSample
0691: f = imageMetadata
0692: .getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
0693: this .bitsPerSample = new int[samplesPerPixel];
0694: replicateFirst = false;
0695: if (f == null) {
0696: replicateFirst = true;
0697: first = defaultBitDepth;
0698: } else if (f.getCount() != samplesPerPixel) {
0699: replicateFirst = true;
0700: first = f.getAsInt(0);
0701: }
0702:
0703: for (int i = 0; i < samplesPerPixel; i++) {
0704: // Replicate initial value if not enough values provided
0705: bitsPerSample[i] = replicateFirst ? first : f.getAsInt(i);
0706:
0707: if (DEBUG) {
0708: System.out.println("bitsPerSample[" + i + "] = "
0709: + bitsPerSample[i]);
0710: }
0711: }
0712:
0713: // ExtraSamples
0714: this .extraSamples = null;
0715: f = imageMetadata
0716: .getTIFFField(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES);
0717: if (f != null) {
0718: extraSamples = f.getAsInts();
0719: }
0720:
0721: // System.out.println("colorMap = " + colorMap);
0722: // if (colorMap != null) {
0723: // for (int i = 0; i < colorMap.length; i++) {
0724: // System.out.println("colorMap[" + i + "] = " + (int)(colorMap[i]));
0725: // }
0726: // }
0727:
0728: }
0729:
0730: public Iterator getImageTypes(int imageIndex) throws IIOException {
0731: List l; // List of ImageTypeSpecifiers
0732:
0733: Integer imageIndexInteger = new Integer(imageIndex);
0734: if (imageTypeMap.containsKey(imageIndexInteger)) {
0735: // Return the cached ITS List.
0736: l = (List) imageTypeMap.get(imageIndexInteger);
0737: } else {
0738: // Create a new ITS List.
0739: l = new ArrayList(1);
0740:
0741: // Create the ITS and cache if for later use so that this method
0742: // always returns an Iterator containing the same ITS objects.
0743: seekToImage(imageIndex);
0744: ImageTypeSpecifier itsRaw = TIFFDecompressor
0745: .getRawImageTypeSpecifier(
0746: photometricInterpretation, compression,
0747: samplesPerPixel, bitsPerSample,
0748: sampleFormat, extraSamples, colorMap);
0749:
0750: // Check for an ICCProfile field.
0751: TIFFField iccProfileField = imageMetadata
0752: .getTIFFField(BaselineTIFFTagSet.TAG_ICC_PROFILE);
0753:
0754: // If an ICCProfile field is present change the ImageTypeSpecifier
0755: // to use it if the data layout is component type.
0756: if (iccProfileField != null
0757: && itsRaw.getColorModel() instanceof ComponentColorModel) {
0758: // Create a ColorSpace from the profile.
0759: byte[] iccProfileValue = iccProfileField.getAsBytes();
0760: ICC_Profile iccProfile = ICC_Profile
0761: .getInstance(iccProfileValue);
0762: ICC_ColorSpace iccColorSpace = new ICC_ColorSpace(
0763: iccProfile);
0764:
0765: // Get the raw sample and color information.
0766: ColorModel cmRaw = itsRaw.getColorModel();
0767: ColorSpace csRaw = cmRaw.getColorSpace();
0768: SampleModel smRaw = itsRaw.getSampleModel();
0769:
0770: // Get the number of samples per pixel and the number
0771: // of color components.
0772: int numBands = smRaw.getNumBands();
0773: int numComponents = iccColorSpace.getNumComponents();
0774:
0775: // Replace the ColorModel with the ICC ColorModel if the
0776: // numbers of samples and color components are amenable.
0777: if (numBands == numComponents
0778: || numBands == numComponents + 1) {
0779: // Set alpha flags.
0780: boolean hasAlpha = numComponents != numBands;
0781: boolean isAlphaPre = hasAlpha
0782: && cmRaw.isAlphaPremultiplied();
0783:
0784: // Create a ColorModel of the same class and with
0785: // the same transfer type.
0786: ColorModel iccColorModel = new ComponentColorModel(
0787: iccColorSpace, cmRaw.getComponentSize(),
0788: hasAlpha, isAlphaPre, cmRaw
0789: .getTransparency(), cmRaw
0790: .getTransferType());
0791:
0792: // Prepend the ICC profile-based ITS to the List. The
0793: // ColorModel and SampleModel are guaranteed to be
0794: // compatible as the old and new ColorModels are both
0795: // ComponentColorModels with the same transfer type
0796: // and the same number of components.
0797: l.add(new ImageTypeSpecifier(iccColorModel, smRaw));
0798:
0799: // Append the raw ITS to the List if and only if its
0800: // ColorSpace has the same type and number of components
0801: // as the ICC ColorSpace.
0802: if (csRaw.getType() == iccColorSpace.getType()
0803: && csRaw.getNumComponents() == iccColorSpace
0804: .getNumComponents()) {
0805: l.add(itsRaw);
0806: }
0807: } else { // ICCProfile not compatible with SampleModel.
0808: // Append the raw ITS to the List.
0809: l.add(itsRaw);
0810: }
0811: } else { // No ICCProfile field or raw ColorModel not component.
0812: // Append the raw ITS to the List.
0813: l.add(itsRaw);
0814: }
0815:
0816: // Cache the ITS List.
0817: imageTypeMap.put(imageIndexInteger, l);
0818: }
0819:
0820: return l.iterator();
0821: }
0822:
0823: public IIOMetadata getImageMetadata(int imageIndex)
0824: throws IIOException {
0825: seekToImage(imageIndex);
0826: TIFFImageMetadata im = new TIFFImageMetadata(imageMetadata
0827: .getRootIFD().getTagSetList());
0828: Node root = imageMetadata
0829: .getAsTree(TIFFImageMetadata.nativeMetadataFormatName);
0830: im
0831: .setFromTree(
0832: TIFFImageMetadata.nativeMetadataFormatName,
0833: root);
0834: return im;
0835: }
0836:
0837: public IIOMetadata getStreamMetadata(int imageIndex)
0838: throws IIOException {
0839: readHeader();
0840: TIFFStreamMetadata sm = new TIFFStreamMetadata();
0841: Node root = sm
0842: .getAsTree(TIFFStreamMetadata.nativeMetadataFormatName);
0843: sm.setFromTree(TIFFStreamMetadata.nativeMetadataFormatName,
0844: root);
0845: return sm;
0846: }
0847:
0848: public boolean isRandomAccessEasy(int imageIndex)
0849: throws IOException {
0850: if (currIndex != -1) {
0851: seekToImage(currIndex);
0852: return getCompression() == BaselineTIFFTagSet.COMPRESSION_NONE;
0853: } else {
0854: return false;
0855: }
0856: }
0857:
0858: // Thumbnails
0859:
0860: public boolean readSupportsThumbnails() {
0861: return false;
0862: }
0863:
0864: public boolean hasThumbnails(int imageIndex) {
0865: return false;
0866: }
0867:
0868: public int getNumThumbnails(int imageIndex) throws IOException {
0869: return 0;
0870: }
0871:
0872: public ImageReadParam getDefaultReadParam() {
0873: return new TIFFImageReadParam();
0874: }
0875:
0876: public boolean isImageTiled(int imageIndex) throws IOException {
0877: seekToImage(imageIndex);
0878:
0879: TIFFField f = imageMetadata
0880: .getTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH);
0881: return f != null;
0882: }
0883:
0884: public int getTileWidth(int imageIndex) throws IOException {
0885: seekToImage(imageIndex);
0886: return getTileOrStripWidth();
0887: }
0888:
0889: public int getTileHeight(int imageIndex) throws IOException {
0890: seekToImage(imageIndex);
0891: return getTileOrStripHeight();
0892: }
0893:
0894: public BufferedImage readTile(int imageIndex, int tileX, int tileY)
0895: throws IOException {
0896:
0897: int w = getWidth(imageIndex);
0898: int h = getHeight(imageIndex);
0899: int tw = getTileWidth(imageIndex);
0900: int th = getTileHeight(imageIndex);
0901:
0902: int x = tw * tileX;
0903: int y = th * tileY;
0904:
0905: if (tileX < 0 || tileY < 0 || x >= w || y >= h) {
0906: throw new IllegalArgumentException(
0907: "Tile indices are out of bounds!");
0908: }
0909:
0910: if (x + tw > w) {
0911: tw = w - x;
0912: }
0913:
0914: if (y + th > h) {
0915: th = h - y;
0916: }
0917:
0918: ImageReadParam param = getDefaultReadParam();
0919: Rectangle tileRect = new Rectangle(x, y, tw, th);
0920: param.setSourceRegion(tileRect);
0921:
0922: return read(imageIndex, param);
0923: }
0924:
0925: public boolean canReadRaster() {
0926: // Enable this?
0927: return false;
0928: }
0929:
0930: public Raster readRaster(int imageIndex, ImageReadParam param)
0931: throws IOException {
0932: // Enable this?
0933: throw new UnsupportedOperationException();
0934: }
0935:
0936: // public BufferedImage readTileRaster(int imageIndex,
0937: // int tileX, int tileY)
0938: // throws IOException {
0939: // }
0940:
0941: private int[] sourceBands;
0942: private int[] destinationBands;
0943:
0944: private TIFFDecompressor decompressor;
0945:
0946: // floor(num/den)
0947: private static int ifloor(int num, int den) {
0948: if (num < 0) {
0949: num -= den - 1;
0950: }
0951: return num / den;
0952: }
0953:
0954: // ceil(num/den)
0955: private static int iceil(int num, int den) {
0956: if (num > 0) {
0957: num += den - 1;
0958: }
0959: return num / den;
0960: }
0961:
0962: private void prepareRead(int imageIndex, ImageReadParam param)
0963: throws IOException {
0964: if (stream == null) {
0965: throw new IllegalStateException("Input not set!");
0966: }
0967:
0968: // A null ImageReadParam means we use the default
0969: if (param == null) {
0970: param = getDefaultReadParam();
0971: }
0972:
0973: this .imageReadParam = param;
0974:
0975: seekToImage(imageIndex);
0976:
0977: this .tileOrStripWidth = getTileOrStripWidth();
0978: this .tileOrStripHeight = getTileOrStripHeight();
0979: this .planarConfiguration = getPlanarConfiguration();
0980:
0981: this .sourceBands = param.getSourceBands();
0982: if (sourceBands == null) {
0983: sourceBands = new int[numBands];
0984: for (int i = 0; i < numBands; i++) {
0985: sourceBands[i] = i;
0986: }
0987: }
0988:
0989: // Initialize the destination image
0990: Iterator imageTypes = getImageTypes(imageIndex);
0991: ImageTypeSpecifier theImageType = ImageUtil.getDestinationType(
0992: param, imageTypes);
0993:
0994: int destNumBands = theImageType.getSampleModel().getNumBands();
0995:
0996: this .destinationBands = param.getDestinationBands();
0997: if (destinationBands == null) {
0998: destinationBands = new int[destNumBands];
0999: for (int i = 0; i < destNumBands; i++) {
1000: destinationBands[i] = i;
1001: }
1002: }
1003:
1004: if (sourceBands.length != destinationBands.length) {
1005: throw new IllegalArgumentException(
1006: "sourceBands.length != destinationBands.length");
1007: }
1008:
1009: for (int i = 0; i < sourceBands.length; i++) {
1010: int sb = sourceBands[i];
1011: if (sb < 0 || sb >= numBands) {
1012: throw new IllegalArgumentException(
1013: "Source band out of range!");
1014: }
1015: int db = destinationBands[i];
1016: if (db < 0 || db >= destNumBands) {
1017: throw new IllegalArgumentException(
1018: "Destination band out of range!");
1019: }
1020: }
1021: }
1022:
1023: public RenderedImage readAsRenderedImage(int imageIndex,
1024: ImageReadParam param) 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)
1031: throws IOException {
1032: if (DEBUG) {
1033: System.out.println("decodeTile(" + ti + "," + tj + ","
1034: + band + ")");
1035: }
1036:
1037: // Compute the region covered by the strip or tile
1038: Rectangle tileRect = new Rectangle(ti * tileOrStripWidth, tj
1039: * tileOrStripHeight, tileOrStripWidth,
1040: tileOrStripHeight);
1041:
1042: // Clip against the image bounds if the image is not tiled. If it
1043: // is tiled, the tile may legally extend beyond the image bounds.
1044: if (!isImageTiled(currIndex)) {
1045: tileRect = tileRect.intersection(new Rectangle(0, 0, width,
1046: height));
1047: }
1048:
1049: // Return if the intersection is empty.
1050: if (tileRect.width <= 0 || tileRect.height <= 0) {
1051: return;
1052: }
1053:
1054: int srcMinX = tileRect.x;
1055: int srcMinY = tileRect.y;
1056: int srcWidth = tileRect.width;
1057: int srcHeight = tileRect.height;
1058:
1059: // Determine dest region that can be derived from the
1060: // source region
1061:
1062: dstMinX = iceil(srcMinX - sourceXOffset, srcXSubsampling);
1063: int dstMaxX = ifloor(srcMinX + srcWidth - 1 - sourceXOffset,
1064: srcXSubsampling);
1065:
1066: dstMinY = iceil(srcMinY - sourceYOffset, srcYSubsampling);
1067: int dstMaxY = ifloor(srcMinY + srcHeight - 1 - sourceYOffset,
1068: srcYSubsampling);
1069:
1070: dstWidth = dstMaxX - dstMinX + 1;
1071: dstHeight = dstMaxY - dstMinY + 1;
1072:
1073: dstMinX += dstXOffset;
1074: dstMinY += dstYOffset;
1075:
1076: // Clip against image bounds
1077:
1078: Rectangle dstRect = new Rectangle(dstMinX, dstMinY, dstWidth,
1079: dstHeight);
1080: dstRect = dstRect
1081: .intersection(theImage.getRaster().getBounds());
1082:
1083: dstMinX = dstRect.x;
1084: dstMinY = dstRect.y;
1085: dstWidth = dstRect.width;
1086: dstHeight = dstRect.height;
1087:
1088: if (dstWidth <= 0 || dstHeight <= 0) {
1089: return;
1090: }
1091:
1092: // Backwards map dest region to source to determine
1093: // active source region
1094:
1095: int activeSrcMinX = (dstMinX - dstXOffset) * srcXSubsampling
1096: + sourceXOffset;
1097: int sxmax = (dstMinX + dstWidth - 1 - dstXOffset)
1098: * srcXSubsampling + sourceXOffset;
1099: int activeSrcWidth = sxmax - activeSrcMinX + 1;
1100:
1101: int activeSrcMinY = (dstMinY - dstYOffset) * srcYSubsampling
1102: + sourceYOffset;
1103: int symax = (dstMinY + dstHeight - 1 - dstYOffset)
1104: * srcYSubsampling + sourceYOffset;
1105: int activeSrcHeight = symax - activeSrcMinY + 1;
1106:
1107: decompressor.setSrcMinX(srcMinX);
1108: decompressor.setSrcMinY(srcMinY);
1109: decompressor.setSrcWidth(srcWidth);
1110: decompressor.setSrcHeight(srcHeight);
1111:
1112: decompressor.setDstMinX(dstMinX);
1113: decompressor.setDstMinY(dstMinY);
1114: decompressor.setDstWidth(dstWidth);
1115: decompressor.setDstHeight(dstHeight);
1116:
1117: decompressor.setActiveSrcMinX(activeSrcMinX);
1118: decompressor.setActiveSrcMinY(activeSrcMinY);
1119: decompressor.setActiveSrcWidth(activeSrcWidth);
1120: decompressor.setActiveSrcHeight(activeSrcHeight);
1121:
1122: int tileIndex = tj * tilesAcross + ti;
1123:
1124: if (planarConfiguration == BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) {
1125: tileIndex += band * tilesAcross * tilesDown;
1126: }
1127:
1128: long offset = getTileOrStripOffset(tileIndex);
1129: long byteCount = getTileOrStripByteCount(tileIndex);
1130:
1131: //
1132: // Attempt to handle truncated streams, i.e., where reading the
1133: // compressed strip or tile would result in an EOFException. The
1134: // number of bytes to read is clamped to the number available
1135: // from the stream starting at the indicated position in the hope
1136: // that the decompressor will handle it.
1137: //
1138: long streamLength = stream.length();
1139: if (streamLength > 0 && offset + byteCount > streamLength) {
1140: processWarningOccurred("Attempting to process truncated stream.");
1141: if (Math.max(byteCount = streamLength - offset, 0) == 0) {
1142: processWarningOccurred("No bytes in strip/tile: skipping.");
1143: return;
1144: }
1145: }
1146:
1147: decompressor.setStream(stream);
1148: decompressor.setOffset(offset);
1149: decompressor.setByteCount((int) byteCount);
1150:
1151: decompressor.beginDecoding();
1152:
1153: stream.mark();
1154: decompressor.decode();
1155: stream.reset();
1156: }
1157:
1158: private void reportProgress() {
1159: // Report image progress/update to listeners after each tile
1160: pixelsRead += dstWidth * dstHeight;
1161: processImageProgress(100.0f * pixelsRead / pixelsToRead);
1162: processImageUpdate(theImage, dstMinX, dstMinY, dstWidth,
1163: dstHeight, 1, 1, destinationBands);
1164: }
1165:
1166: public BufferedImage read(int imageIndex, ImageReadParam param)
1167: throws IOException {
1168: prepareRead(imageIndex, param);
1169: this .theImage = getDestination(param,
1170: getImageTypes(imageIndex), width, height);
1171:
1172: srcXSubsampling = imageReadParam.getSourceXSubsampling();
1173: srcYSubsampling = imageReadParam.getSourceYSubsampling();
1174:
1175: Point p = imageReadParam.getDestinationOffset();
1176: dstXOffset = p.x;
1177: dstYOffset = p.y;
1178:
1179: // This could probably be made more efficient...
1180: Rectangle srcRegion = new Rectangle(0, 0, 0, 0);
1181: Rectangle destRegion = new Rectangle(0, 0, 0, 0);
1182:
1183: computeRegions(imageReadParam, width, height, theImage,
1184: srcRegion, destRegion);
1185:
1186: // Initial source pixel, taking source region and source
1187: // subsamplimg offsets into account
1188: sourceXOffset = srcRegion.x;
1189: sourceYOffset = srcRegion.y;
1190:
1191: pixelsToRead = destRegion.width * destRegion.height;
1192: pixelsRead = 0;
1193:
1194: processImageStarted(imageIndex);
1195: processImageProgress(0.0f);
1196:
1197: tilesAcross = (width + tileOrStripWidth - 1) / tileOrStripWidth;
1198: tilesDown = (height + tileOrStripHeight - 1)
1199: / tileOrStripHeight;
1200:
1201: int compression = getCompression();
1202:
1203: // Attempt to get decompressor and color converted from the read param
1204:
1205: TIFFColorConverter colorConverter = null;
1206: if (imageReadParam instanceof TIFFImageReadParam) {
1207: TIFFImageReadParam tparam = (TIFFImageReadParam) imageReadParam;
1208: this .decompressor = tparam.getTIFFDecompressor();
1209: colorConverter = tparam.getColorConverter();
1210: }
1211:
1212: // If we didn't find one, use a standard decompressor
1213: if (this .decompressor == null) {
1214: if (compression == BaselineTIFFTagSet.COMPRESSION_NONE) {
1215: // Get the fillOrder field.
1216: TIFFField fillOrderField = imageMetadata
1217: .getTIFFField(BaselineTIFFTagSet.TAG_FILL_ORDER);
1218:
1219: // Set the decompressor based on the fill order.
1220: if (fillOrderField != null
1221: && fillOrderField.getAsInt(0) == 2) {
1222: this .decompressor = new TIFFLSBDecompressor();
1223: } else {
1224: this .decompressor = new TIFFNullDecompressor();
1225: }
1226: } else if (compression == BaselineTIFFTagSet.COMPRESSION_CCITT_T_6) {
1227:
1228: // Try to create the codecLib decompressor.
1229: if (PackageUtil.isCodecLibAvailable()) {
1230: try {
1231: this .decompressor = new TIFFCodecLibFaxDecompressor(
1232: compression);
1233: if (DEBUG) {
1234: System.out
1235: .println("Using codecLib T.6 decompressor");
1236: }
1237: } catch (RuntimeException re) {
1238: if (DEBUG) {
1239: System.out.println(re);
1240: }
1241: }
1242: }
1243:
1244: // Fall back to the Java decompressor.
1245: if (this .decompressor == null) {
1246: if (DEBUG) {
1247: System.out
1248: .println("Using Java T.6 decompressor");
1249: }
1250: this .decompressor = new TIFFFaxDecompressor();
1251: }
1252: } else if (compression == BaselineTIFFTagSet.COMPRESSION_CCITT_T_4) {
1253:
1254: if (PackageUtil.isCodecLibAvailable()) {
1255: // Try to create the codecLib decompressor.
1256: try {
1257: this .decompressor = new TIFFCodecLibFaxDecompressor(
1258: compression);
1259: if (DEBUG) {
1260: System.out
1261: .println("Using codecLib T.4 decompressor");
1262: }
1263: } catch (RuntimeException re) {
1264: if (DEBUG) {
1265: System.out.println(re);
1266: }
1267: }
1268: }
1269:
1270: // Fall back to the Java decompressor.
1271: if (this .decompressor == null) {
1272: if (DEBUG) {
1273: System.out
1274: .println("Using Java T.4 decompressor");
1275: }
1276: this .decompressor = new TIFFFaxDecompressor();
1277: }
1278: } else if (compression == BaselineTIFFTagSet.COMPRESSION_CCITT_RLE) {
1279: this .decompressor = new TIFFFaxDecompressor();
1280: } else if (compression == BaselineTIFFTagSet.COMPRESSION_PACKBITS) {
1281: if (DEBUG) {
1282: System.out
1283: .println("Using TIFFPackBitsDecompressor");
1284: }
1285: this .decompressor = new TIFFPackBitsDecompressor();
1286: } else if (compression == BaselineTIFFTagSet.COMPRESSION_LZW) {
1287: if (DEBUG) {
1288: System.out.println("Using TIFFLZWDecompressor");
1289: }
1290: TIFFField predictorField = imageMetadata
1291: .getTIFFField(BaselineTIFFTagSet.TAG_PREDICTOR);
1292: int predictor = ((predictorField == null) ? BaselineTIFFTagSet.PREDICTOR_NONE
1293: : predictorField.getAsInt(0));
1294: this .decompressor = new TIFFLZWDecompressor(predictor);
1295: } else if (compression == BaselineTIFFTagSet.COMPRESSION_JPEG) {
1296: this .decompressor = new TIFFJPEGDecompressor();
1297: } else if (compression == BaselineTIFFTagSet.COMPRESSION_ZLIB
1298: || compression == BaselineTIFFTagSet.COMPRESSION_DEFLATE) {
1299: TIFFField predictorField = imageMetadata
1300: .getTIFFField(BaselineTIFFTagSet.TAG_PREDICTOR);
1301: int predictor = ((predictorField == null) ? BaselineTIFFTagSet.PREDICTOR_NONE
1302: : predictorField.getAsInt(0));
1303: this .decompressor = new TIFFDeflateDecompressor(
1304: predictor);
1305: } else if (compression == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
1306: TIFFField JPEGProcField = imageMetadata
1307: .getTIFFField(BaselineTIFFTagSet.TAG_JPEG_PROC);
1308: if (JPEGProcField == null) {
1309: processWarningOccurred("JPEGProc field missing; assuming baseline sequential JPEG process.");
1310: } else if (JPEGProcField.getAsInt(0) != BaselineTIFFTagSet.JPEG_PROC_BASELINE) {
1311: throw new IIOException(
1312: "Old-style JPEG supported for baseline sequential JPEG process only!");
1313: }
1314: this .decompressor = new TIFFOldJPEGDecompressor();
1315: //throw new IIOException("Old-style JPEG not supported!");
1316: } else {
1317: throw new IIOException(
1318: "Unsupported compression type (tag number = "
1319: + compression + ")!");
1320: }
1321:
1322: if (photometricInterpretation == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR
1323: && compression != BaselineTIFFTagSet.COMPRESSION_JPEG
1324: && compression != BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
1325: boolean convertYCbCrToRGB = theImage.getColorModel()
1326: .getColorSpace().getType() == ColorSpace.TYPE_RGB;
1327: TIFFDecompressor wrappedDecompressor = this .decompressor instanceof TIFFNullDecompressor ? null
1328: : this .decompressor;
1329: this .decompressor = new TIFFYCbCrDecompressor(
1330: wrappedDecompressor, convertYCbCrToRGB);
1331: }
1332: }
1333:
1334: if (DEBUG) {
1335: System.out.println("\nDecompressor class = "
1336: + decompressor.getClass().getName() + "\n");
1337: }
1338:
1339: if (colorConverter == null) {
1340: if (photometricInterpretation == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CIELAB
1341: && theImage.getColorModel().getColorSpace()
1342: .getType() == ColorSpace.TYPE_RGB) {
1343: colorConverter = new TIFFCIELabColorConverter();
1344: } else if (photometricInterpretation == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR
1345: && !(this .decompressor instanceof TIFFYCbCrDecompressor)
1346: && compression != BaselineTIFFTagSet.COMPRESSION_JPEG
1347: && compression != BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
1348: colorConverter = new TIFFYCbCrColorConverter(
1349: imageMetadata);
1350: }
1351: }
1352:
1353: decompressor.setReader(this );
1354: decompressor.setMetadata(imageMetadata);
1355: decompressor.setImage(theImage);
1356:
1357: decompressor
1358: .setPhotometricInterpretation(photometricInterpretation);
1359: decompressor.setCompression(compression);
1360: decompressor.setSamplesPerPixel(samplesPerPixel);
1361: decompressor.setBitsPerSample(bitsPerSample);
1362: decompressor.setSampleFormat(sampleFormat);
1363: decompressor.setExtraSamples(extraSamples);
1364: decompressor.setColorMap(colorMap);
1365:
1366: decompressor.setColorConverter(colorConverter);
1367:
1368: decompressor.setSourceXOffset(sourceXOffset);
1369: decompressor.setSourceYOffset(sourceYOffset);
1370: decompressor.setSubsampleX(srcXSubsampling);
1371: decompressor.setSubsampleY(srcYSubsampling);
1372:
1373: decompressor.setDstXOffset(dstXOffset);
1374: decompressor.setDstYOffset(dstYOffset);
1375:
1376: decompressor.setSourceBands(sourceBands);
1377: decompressor.setDestinationBands(destinationBands);
1378:
1379: // Compute bounds on the tile indices for this source region.
1380: int minTileX = TIFFImageWriter.XToTileX(srcRegion.x, 0,
1381: tileOrStripWidth);
1382: int minTileY = TIFFImageWriter.YToTileY(srcRegion.y, 0,
1383: tileOrStripHeight);
1384: int maxTileX = TIFFImageWriter.XToTileX(srcRegion.x
1385: + srcRegion.width - 1, 0, tileOrStripWidth);
1386: int maxTileY = TIFFImageWriter.YToTileY(srcRegion.y
1387: + srcRegion.height - 1, 0, tileOrStripHeight);
1388:
1389: boolean isAbortRequested = false;
1390: if (planarConfiguration == BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) {
1391:
1392: decompressor.setPlanar(true);
1393:
1394: int[] sb = new int[1];
1395: int[] db = new int[1];
1396: for (int tj = minTileY; tj <= maxTileY; tj++) {
1397: for (int ti = minTileX; ti <= maxTileX; ti++) {
1398: for (int band = 0; band < numBands; band++) {
1399: sb[0] = sourceBands[band];
1400: decompressor.setSourceBands(sb);
1401: db[0] = destinationBands[band];
1402: decompressor.setDestinationBands(db);
1403: //XXX decompressor.beginDecoding();
1404:
1405: // The method abortRequested() is synchronized
1406: // so check it only once per loop just before
1407: // doing any actual decoding.
1408: if (abortRequested()) {
1409: isAbortRequested = true;
1410: break;
1411: }
1412:
1413: decodeTile(ti, tj, band);
1414: }
1415:
1416: if (isAbortRequested)
1417: break;
1418:
1419: reportProgress();
1420: }
1421:
1422: if (isAbortRequested)
1423: break;
1424: }
1425: } else {
1426: //XXX decompressor.beginDecoding();
1427:
1428: for (int tj = minTileY; tj <= maxTileY; tj++) {
1429: for (int ti = minTileX; ti <= maxTileX; ti++) {
1430: // The method abortRequested() is synchronized
1431: // so check it only once per loop just before
1432: // doing any actual decoding.
1433: if (abortRequested()) {
1434: isAbortRequested = true;
1435: break;
1436: }
1437:
1438: decodeTile(ti, tj, -1);
1439:
1440: reportProgress();
1441: }
1442:
1443: if (isAbortRequested)
1444: break;
1445: }
1446: }
1447:
1448: if (isAbortRequested) {
1449: processReadAborted();
1450: } else {
1451: processImageComplete();
1452: }
1453:
1454: return theImage;
1455: }
1456:
1457: public void reset() {
1458: super .reset();
1459: resetLocal();
1460: }
1461:
1462: protected void resetLocal() {
1463: stream = null;
1464: gotHeader = false;
1465: imageReadParam = getDefaultReadParam();
1466: streamMetadata = null;
1467: currIndex = -1;
1468: imageMetadata = null;
1469: imageStartPosition = new ArrayList();
1470: numImages = -1;
1471: imageTypeMap = new HashMap();
1472: width = -1;
1473: height = -1;
1474: numBands = -1;
1475: tileOrStripWidth = -1;
1476: tileOrStripHeight = -1;
1477: planarConfiguration = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
1478: rowsDone = 0;
1479: }
1480:
1481: /**
1482: * Package scope method to allow decompressors, for example, to
1483: * emit warning messages.
1484: */
1485: void forwardWarningMessage(String warning) {
1486: processWarningOccurred(warning);
1487: }
1488:
1489: protected static BufferedImage getDestination(ImageReadParam param,
1490: Iterator imageTypes, int width, int height)
1491: throws IIOException {
1492: if (imageTypes == null || !imageTypes.hasNext()) {
1493: throw new IllegalArgumentException(
1494: "imageTypes null or empty!");
1495: }
1496:
1497: BufferedImage dest = null;
1498: ImageTypeSpecifier imageType = null;
1499:
1500: // If param is non-null, use it
1501: if (param != null) {
1502: // Try to get the image itself
1503: dest = param.getDestination();
1504: if (dest != null) {
1505: return dest;
1506: }
1507:
1508: // No image, get the image type
1509: imageType = param.getDestinationType();
1510: }
1511:
1512: // No info from param, use fallback image type
1513: if (imageType == null) {
1514: Object o = imageTypes.next();
1515: if (!(o instanceof ImageTypeSpecifier)) {
1516: throw new IllegalArgumentException(
1517: "Non-ImageTypeSpecifier retrieved from imageTypes!");
1518: }
1519: imageType = (ImageTypeSpecifier) o;
1520: } else {
1521: boolean foundIt = false;
1522: while (imageTypes.hasNext()) {
1523: ImageTypeSpecifier type = (ImageTypeSpecifier) imageTypes
1524: .next();
1525: if (type.equals(imageType)) {
1526: foundIt = true;
1527: break;
1528: }
1529: }
1530:
1531: if (!foundIt) {
1532: throw new IIOException(
1533: "Destination type from ImageReadParam does not match!");
1534: }
1535: }
1536:
1537: Rectangle srcRegion = new Rectangle(0, 0, 0, 0);
1538: Rectangle destRegion = new Rectangle(0, 0, 0, 0);
1539: computeRegions(param, width, height, null, srcRegion,
1540: destRegion);
1541:
1542: int destWidth = destRegion.x + destRegion.width;
1543: int destHeight = destRegion.y + destRegion.height;
1544: // Create a new image based on the type specifier
1545:
1546: if ((long) destWidth * destHeight > Integer.MAX_VALUE) {
1547: throw new IllegalArgumentException(
1548: "width*height > Integer.MAX_VALUE!");
1549: }
1550:
1551: return imageType.createBufferedImage(destWidth, destHeight);
1552: }
1553: }
|