diff --git a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java
deleted file mode 100755
index 8794b649..00000000
--- a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * * Neither the name "TwelveMonkeys" nor the
- * names of its contributors may be used to endorse or promote products
- * derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package com.twelvemonkeys.imageio.plugins.tiff;
-
-import com.twelvemonkeys.image.ImageUtil;
-import com.twelvemonkeys.imageio.ImageReaderBase;
-import org.apache.batik.ext.awt.image.codec.SeekableStream;
-import org.apache.batik.ext.awt.image.codec.tiff.TIFFDecodeParam;
-import org.apache.batik.ext.awt.image.codec.tiff.TIFFImageDecoder;
-
-import javax.imageio.ImageReadParam;
-import javax.imageio.ImageTypeSpecifier;
-import javax.imageio.spi.ImageReaderSpi;
-import java.awt.image.BufferedImage;
-import java.awt.image.RenderedImage;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * TIFFImageReader class description.
- *
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- * @version $Id: TIFFImageReader.java,v 1.0 29.jul.2004 12:52:33 haku Exp $
- */
-// TODO: Massive clean-up
-// TODO: Support raster decoding...
-public class TIFFImageReader extends ImageReaderBase {
-
- private TIFFImageDecoder decoder = null;
- private List images = new ArrayList();
-
- protected TIFFImageReader(final ImageReaderSpi pOriginatingProvider) {
- super(pOriginatingProvider);
- }
-
- protected void resetMembers() {
- decoder = null;
- }
-
- public BufferedImage read(int pIndex, ImageReadParam pParam) throws IOException {
- // Decode image, convert and return as BufferedImage
- RenderedImage image = readAsRenderedImage(pIndex, pParam);
- return ImageUtil.toBuffered(image);
- }
-
- public RenderedImage readAsRenderedImage(int pIndex, ImageReadParam pParam) throws IOException {
- init(pIndex);
-
- processImageStarted(pIndex);
-
- if (pParam == null) {
- // Cache image for use by getWidth and getHeight methods
- RenderedImage image;
- if (images.size() > pIndex && images.get(pIndex) != null) {
- image = images.get(pIndex);
- }
- else {
- // Decode
- image = decoder.decodeAsRenderedImage(pIndex);
-
- // Make room
- for (int i = images.size(); i < pIndex; i++) {
- images.add(pIndex, null);
- }
- images.add(pIndex, image);
- }
-
- if (abortRequested()) {
- processReadAborted();
- return image;
- }
-
- processImageComplete();
- return image;
- }
- else {
- // TODO: Parameter conversion
- decoder.setParam(new TIFFDecodeParam());
-
- RenderedImage image = decoder.decodeAsRenderedImage(pIndex);
-
- // Subsample and apply AOI
- if (pParam.getSourceRegion() != null) {
- image = fakeAOI(ImageUtil.toBuffered(image), pParam);
- }
- if (pParam.getSourceXSubsampling() > 1 || pParam.getSourceYSubsampling() > 1) {
- image = ImageUtil.toBuffered(fakeSubsampling(ImageUtil.toBuffered(image), pParam));
- }
-
- processImageComplete();
- return image;
- }
- }
-
- private void init(int pIndex) throws IOException {
- init();
- checkBounds(pIndex);
- }
-
- protected void checkBounds(int index) throws IOException {
- if (index < getMinIndex()){
- throw new IndexOutOfBoundsException("index < minIndex");
- }
- else if (index >= getNumImages(true)) {
- throw new IndexOutOfBoundsException("index > numImages");
- }
- }
-
- private synchronized void init() {
- if (decoder == null) {
- if (imageInput == null) {
- throw new IllegalStateException("input == null");
- }
-
- decoder = new TIFFImageDecoder(new SeekableStream() {
- public int read() throws IOException {
- return imageInput.read();
- }
-
- public int read(final byte[] pBytes, final int pStart, final int pLength) throws IOException {
- return imageInput.read(pBytes, pStart, pLength);
- }
-
- public long getFilePointer() throws IOException {
- return imageInput.getStreamPosition();
- }
-
- public void seek(final long pPos) throws IOException {
- imageInput.seek(pPos);
- }
- }, null);
- }
- }
-
- public int getWidth(int pIndex) throws IOException {
- init(pIndex);
-
- // TODO: Use cache...
- return decoder.decodeAsRenderedImage(pIndex).getWidth();
- }
-
- public int getHeight(int pIndex) throws IOException {
- init(pIndex);
-
- // TODO: Use cache...
- return decoder.decodeAsRenderedImage(pIndex).getHeight();
- }
-
- public Iterator getImageTypes(final int imageIndex) throws IOException {
- throw new UnsupportedOperationException("Method getImageTypes not implemented");// TODO: Implement
- }
-
- public int getNumImages(boolean allowSearch) throws IOException {
- init();
- if (allowSearch) {
- return decoder.getNumPages();
- }
- return -1;
- }
-}
diff --git a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderSpi.java b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderSpi.java
deleted file mode 100755
index f8ae25a1..00000000
--- a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderSpi.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * * Neither the name "TwelveMonkeys" nor the
- * names of its contributors may be used to endorse or promote products
- * derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package com.twelvemonkeys.imageio.plugins.tiff;
-
-import com.twelvemonkeys.imageio.spi.ProviderInfo;
-import com.twelvemonkeys.lang.SystemUtil;
-import com.twelvemonkeys.imageio.util.IIOUtil;
-
-import javax.imageio.ImageReader;
-import javax.imageio.spi.ImageReaderSpi;
-import javax.imageio.spi.ServiceRegistry;
-import javax.imageio.stream.ImageInputStream;
-import java.io.IOException;
-import java.util.Locale;
-
-/**
- * TIFFImageReaderSpi
- *
- *
- * @author Harald Kuhr
- * @version $Id: TIFFImageReaderSpi.java,v 1.1 2003/12/02 16:45:00 wmhakur Exp $
- */
-public class TIFFImageReaderSpi extends ImageReaderSpi {
-
- final static boolean TIFF_CLASSES_AVAILABLE = SystemUtil.isClassAvailable("com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReader");
-
- /**
- * Creates a {@code TIFFImageReaderSpi}.
- */
- public TIFFImageReaderSpi() {
- this(IIOUtil.getProviderInfo(TIFFImageReaderSpi.class));
- }
-
- private TIFFImageReaderSpi(final ProviderInfo pProviderInfo) {
- super(
- pProviderInfo.getVendorName(), // Vendor name
- pProviderInfo.getVersion(), // Version
- TIFF_CLASSES_AVAILABLE ? new String[]{"tiff", "TIFF"} : new String[] {""}, // Names
- TIFF_CLASSES_AVAILABLE ? new String[]{"tiff", "tif"} : null, // Suffixes
- TIFF_CLASSES_AVAILABLE ? new String[]{"image/tiff", "image/x-tiff"} : null, // Mime-types
- "com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReader", // Writer class name..?
- ImageReaderSpi.STANDARD_INPUT_TYPE, // Output types
- new String[]{"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriterSpi"}, // Writer SPI names
- true, // Supports standard stream metadata format
- null, // Native stream metadata format name
- null, // Native stream metadata format class name
- null, // Extra stream metadata format names
- null, // Extra stream metadata format class names
- true, // Supports standard image metadata format
- null, // Native image metadata format name
- null, // Native image metadata format class name
- null, // Extra image metadata format names
- null // Extra image metadata format class names
- );
- }
-
- public boolean canDecodeInput(Object source) throws IOException {
- return source instanceof ImageInputStream && TIFF_CLASSES_AVAILABLE && canDecode((ImageInputStream) source);
- }
-
-
- static boolean canDecode(ImageInputStream pInput) throws IOException {
- try {
- pInput.mark();
- int byte0 = pInput.read(); // Byte order 1 (M or I)
- int byte1 = pInput.read(); // Byte order 2 (always same as 1)
- int byte2 = pInput.read(); // Version number 1 (M: 0, I: 42)
- int byte3 = pInput.read(); // Version number 2 (M: 42, I: 0)
-
- // Test for Motorola or Intel byte order, and version number == 42
- if ((byte0 == 'M' && byte1 == 'M' && byte2 == 0 && byte3 == 42)
- || (byte0 == 'I' && byte1 == 'I' && byte2 == 42 && byte3 == 0)) {
- return true;
- }
-
- }
- finally {
- pInput.reset();
- }
-
- return false;
- }
-
- public ImageReader createReaderInstance(Object extension) throws IOException {
- return new TIFFImageReader(this);
- }
-
- public String getDescription(Locale locale) {
- return "Tagged Image File Format (TIFF) image reader";
- }
-
- @SuppressWarnings({"deprecation"})
- @Override
- public void onRegistration(ServiceRegistry registry, Class> category) {
- if (!TIFF_CLASSES_AVAILABLE) {
- IIOUtil.deregisterProvider(registry, this, category);
- }
- }
-}
\ No newline at end of file
diff --git a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriter.java b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriter.java
deleted file mode 100755
index 40af8a76..00000000
--- a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriter.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * * Neither the name "TwelveMonkeys" nor the
- * names of its contributors may be used to endorse or promote products
- * derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package com.twelvemonkeys.imageio.plugins.tiff;
-
-import com.twelvemonkeys.image.ImageUtil;
-import com.twelvemonkeys.imageio.ImageWriterBase;
-import com.twelvemonkeys.imageio.util.IIOUtil;
-import org.apache.batik.ext.awt.image.codec.ImageEncodeParam;
-import org.apache.batik.ext.awt.image.codec.tiff.TIFFEncodeParam;
-import org.apache.batik.ext.awt.image.codec.tiff.TIFFImageEncoder;
-
-import javax.imageio.IIOImage;
-import javax.imageio.ImageTypeSpecifier;
-import javax.imageio.ImageWriteParam;
-import javax.imageio.metadata.IIOMetadata;
-import javax.imageio.spi.ImageWriterSpi;
-import java.awt.image.BufferedImage;
-import java.awt.image.RenderedImage;
-import java.io.IOException;
-
-/**
- * TIFFImageWriter class description.
- *
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- * @version $Id: TIFFImageWriter.java,v 1.0 29.jul.2004 12:52:54 haku Exp $
- */
-public class TIFFImageWriter extends ImageWriterBase {
-
- private TIFFImageEncoder encoder;
-
- protected TIFFImageWriter(final ImageWriterSpi pProvider) {
- super(pProvider);
- }
-
- @Override
- public void setOutput(final Object output) {
- encoder = null;
- super.setOutput(output);
- }
-
- public IIOMetadata getDefaultImageMetadata(final ImageTypeSpecifier imageType, final ImageWriteParam param) {
- throw new UnsupportedOperationException("Method getDefaultImageMetadata not implemented");// TODO: Implement
- }
-
- public IIOMetadata convertImageMetadata(final IIOMetadata inData, final ImageTypeSpecifier imageType, final ImageWriteParam param) {
- throw new UnsupportedOperationException("Method convertImageMetadata not implemented");// TODO: Implement
- }
-
- public void write(final IIOMetadata pStreamMetadata, final IIOImage pImage, final ImageWriteParam pParam) throws IOException {
- RenderedImage renderedImage = pImage.getRenderedImage();
- init();
-
- ImageEncodeParam param;
- if (pParam != null) {
- param = new TIFFEncodeParam();
- // TODO: Convert params
-
- encoder.setParam(param);
- }
-
- BufferedImage image;
-
- // FIX: TIFFEnocder chokes on a any of the TYPE_INT_* types...
- // (The TIFFEncoder expects int types to have 1 sample of size 32
- // while there actually is 4 samples of size 8, according to the
- // SampleModel...)
- if (renderedImage instanceof BufferedImage && (
- ((BufferedImage) renderedImage).getType() == BufferedImage.TYPE_INT_ARGB
- || ((BufferedImage) renderedImage).getType() == BufferedImage.TYPE_INT_ARGB_PRE)) {
- image = ImageUtil.toBuffered(renderedImage, BufferedImage.TYPE_4BYTE_ABGR);
- }
- else if (renderedImage instanceof BufferedImage && (
- ((BufferedImage) renderedImage).getType() == BufferedImage.TYPE_INT_BGR
- || ((BufferedImage) renderedImage).getType() == BufferedImage.TYPE_INT_RGB)) {
- image = ImageUtil.toBuffered(renderedImage, BufferedImage.TYPE_3BYTE_BGR);
- }
- else {
- image = ImageUtil.toBuffered(renderedImage);
- }
-
- image = fakeAOI(image, pParam);
- image = ImageUtil.toBuffered(fakeSubsampling(image, pParam));
-
- /*
- System.out.println("Image: " + pImage);
- SampleModel sampleModel = pImage.getSampleModel();
- System.out.println("SampleModel: " + sampleModel);
- int sampleSize[] = sampleModel.getSampleSize();
- System.out.println("Samples: " + sampleSize.length);
- for (int i = 0; i < sampleSize.length; i++) {
- System.out.println("SampleSize[" + i + "]: " + sampleSize[i]);
- }
- int dataType = sampleModel.getDataType();
- System.out.println("DataType: " + dataType);
- */
-
- processImageStarted(0);
-
- encoder.encode(image);
- imageOutput.flush();
-
- processImageComplete();
- }
-
- public void dispose() {
- super.dispose();
- encoder = null;
- }
-
- private synchronized void init() {
- if (encoder == null) {
- if (imageOutput == null) {
- throw new IllegalStateException("output == null");
- }
- encoder = new TIFFImageEncoder(IIOUtil.createStreamAdapter(imageOutput), null);
- }
- }
-}
diff --git a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriterSpi.java b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriterSpi.java
deleted file mode 100755
index 48d69594..00000000
--- a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriterSpi.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * * Neither the name "TwelveMonkeys" nor the
- * names of its contributors may be used to endorse or promote products
- * derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package com.twelvemonkeys.imageio.plugins.tiff;
-
-import com.twelvemonkeys.imageio.spi.ProviderInfo;
-import com.twelvemonkeys.imageio.util.IIOUtil;
-
-import javax.imageio.ImageTypeSpecifier;
-import javax.imageio.ImageWriter;
-import javax.imageio.spi.ImageWriterSpi;
-import javax.imageio.spi.ServiceRegistry;
-import java.io.IOException;
-import java.util.Locale;
-
-/**
- * TIFFmageWriterSpi
- *
- * @author Harald Kuhr
- * @version $Id: TIFFImageWriterSpi.java,v 1.2 2004/01/14 15:21:44 wmhakur Exp $
- */
-public class TIFFImageWriterSpi extends ImageWriterSpi {
-
- /**
- * Creates a {@code TIFFImageWriterSpi}.
- */
- public TIFFImageWriterSpi() {
- this(IIOUtil.getProviderInfo(TIFFImageWriterSpi.class));
- }
-
- private TIFFImageWriterSpi(final ProviderInfo pProviderInfo) {
- super(
- pProviderInfo.getVendorName(), // Vendor name
- pProviderInfo.getVersion(), // Version
- new String[]{"tiff", "TIFF"}, // Names
- new String[]{"tif", "tiff"}, // Suffixes
- new String[]{"image/tiff", "image/x-tiff"}, // Mime-types
- "com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriter", // Writer class name..?
- STANDARD_OUTPUT_TYPE, // Output types
- new String[]{"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReaderSpi"}, // Reader SPI names
- true, // Supports standard stream metadata format
- null, // Native stream metadata format name
- null, // Native stream metadata format class name
- null, // Extra stream metadata format names
- null, // Extra stream metadata format class names
- true, // Supports standard image metadata format
- null, // Native image metadata format name
- null, // Native image metadata format class name
- null, // Extra image metadata format names
- null // Extra image metadata format class names
- );
- }
-
- public boolean canEncodeImage(ImageTypeSpecifier type) {
- return true;
- }
-
- public ImageWriter createWriterInstance(Object extension) throws IOException {
- try {
- return new TIFFImageWriter(this);
- }
- catch (Throwable t) {
- // Wrap in IOException if the writer can't be instantiated.
- // This makes the IIORegistry deregister this service provider
- IOException exception = new IOException(t.getMessage());
- exception.initCause(t);
- throw exception;
- }
- }
-
- public String getDescription(Locale locale) {
- return "Tagged Image File Format (TIFF) image writer";
- }
-
- @SuppressWarnings({"deprecation"})
- @Override
- public void onRegistration(ServiceRegistry registry, Class> category) {
- if (!TIFFImageReaderSpi.TIFF_CLASSES_AVAILABLE) {
- IIOUtil.deregisterProvider(registry, this, category);
- }
- }
-}
diff --git a/imageio/imageio-batik/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi b/imageio/imageio-batik/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi
index 42f4f345..6ce07b66 100755
--- a/imageio/imageio-batik/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi
+++ b/imageio/imageio-batik/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi
@@ -1,3 +1,2 @@
com.twelvemonkeys.imageio.plugins.svg.SVGImageReaderSpi
com.twelvemonkeys.imageio.plugins.wmf.WMFImageReaderSpi
-#com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReaderSpi
diff --git a/imageio/imageio-batik/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi b/imageio/imageio-batik/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi
deleted file mode 100755
index 54dbaa61..00000000
--- a/imageio/imageio-batik/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi
+++ /dev/null
@@ -1 +0,0 @@
-#com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriterSpi
\ No newline at end of file
diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java
index 1fb7a788..efa13180 100644
--- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java
+++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java
@@ -90,6 +90,8 @@ final class EXIFEntry extends AbstractEntry {
return "Orientation";
case TIFF.TAG_SAMPLES_PER_PIXELS:
return "SamplesPerPixels";
+ case TIFF.TAG_ROWS_PER_STRIP:
+ return "RowsPerStrip";
case TIFF.TAG_X_RESOLUTION:
return "XResolution";
case TIFF.TAG_Y_RESOLUTION:
@@ -120,6 +122,10 @@ final class EXIFEntry extends AbstractEntry {
return "YCbCrSubSampling";
case TIFF.TAG_YCBCR_POSITIONING:
return "YCbCrPositioning";
+ case TIFF.TAG_COLOR_MAP:
+ return "ColorMap";
+ case TIFF.TAG_EXTRA_SAMPLES:
+ return "ExtraSamples";
case EXIF.TAG_EXPOSURE_TIME:
return "ExposureTime";
diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java
index 78190a55..276cb873 100644
--- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java
+++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java
@@ -95,6 +95,7 @@ public final class EXIFReader extends MetadataReader {
EXIFEntry entry = readEntry(pInput);
if (entry == null) {
+// System.err.println("Expected: " + entryCount + " values, found only " + i);
// TODO: Log warning?
nextOffset = 0;
break;
@@ -199,13 +200,13 @@ public final class EXIFReader extends MetadataReader {
Object value = entry.getValue();
if (value instanceof Byte) {
- offset = ((Byte) value & 0xff);
+ offset = (Byte) value & 0xff;
}
else if (value instanceof Short) {
- offset = ((Short) value & 0xffff);
+ offset = (Short) value & 0xffff;
}
else if (value instanceof Integer) {
- offset = ((Integer) value & 0xffffffffL);
+ offset = (Integer) value & 0xffffffffL;
}
else if (value instanceof Long) {
offset = (Long) value;
@@ -222,7 +223,7 @@ public final class EXIFReader extends MetadataReader {
int tagId = pInput.readUnsignedShort();
short type = pInput.readShort();
- // This isn't really an entry, and the directory entry count was wront
+ // This isn't really an entry, and the directory entry count was wrong OR bad data...
if (tagId == 0 && type == 0) {
return null;
}
@@ -236,24 +237,28 @@ public final class EXIFReader extends MetadataReader {
if (type <= 0 || type > 13) {
// Invalid tag, this is just for debugging
- System.err.printf("Bad EXIF data at offset: %08x\n", pInput.getStreamPosition() - 8l);
- System.err.println("tagId: " + tagId);
+ long offset = pInput.getStreamPosition() - 8l;
+
+ System.err.printf("Bad EXIF");
+ System.err.println("tagId: " + tagId + (tagId <= 0 ? "(INVALID)" : ""));
System.err.println("type: " + type + " (INVALID)");
System.err.println("count: " + count);
pInput.mark();
- pInput.seek(pInput.getStreamPosition() - 8);
+ pInput.seek(offset);
try {
byte[] bytes = new byte[8 + Math.max(20, count)];
int len = pInput.read(bytes);
- System.err.print("data: " + HexDump.dump(bytes, 0, len));
+ System.err.print(HexDump.dump(offset, bytes, 0, len));
System.err.println(len < count ? "[...]" : "");
}
finally {
pInput.reset();
}
+
+ return null;
}
int valueLength = getValueLength(type, count);
@@ -484,7 +489,7 @@ public final class EXIFReader extends MetadataReader {
Object value = entry.getValue();
if (value instanceof byte[]) {
byte[] bytes = (byte[]) value;
- System.err.println(HexDump.dump(bytes, 0, Math.min(bytes.length, 128)));
+ System.err.println(HexDump.dump(0, bytes, 0, Math.min(bytes.length, 128)));
}
}
}
@@ -501,10 +506,10 @@ public final class EXIFReader extends MetadataReader {
private static final int WIDTH = 32;
public static String dump(byte[] bytes) {
- return dump(bytes, 0, bytes.length);
+ return dump(0, bytes, 0, bytes.length);
}
- public static String dump(byte[] bytes, int off, int len) {
+ public static String dump(long offset, byte[] bytes, int off, int len) {
StringBuilder builder = new StringBuilder();
int i;
@@ -513,7 +518,7 @@ public final class EXIFReader extends MetadataReader {
if (i > 0 ) {
builder.append("\n");
}
- builder.append(String.format("%08x: ", i + off));
+ builder.append(String.format("%08x: ", i + off + offset));
}
else if (i > 0 && i % 2 == 0) {
builder.append(" ");
diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java
index 00a954fd..361ba3b7 100644
--- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java
+++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java
@@ -118,8 +118,11 @@ public interface TIFF {
/// C. Tags relating to image data characteristics
int TAG_TRANSFER_FUNCTION = 301;
+ int TAG_PREDICTOR = 317;
int TAG_WHITE_POINT = 318;
int TAG_PRIMARY_CHROMATICITIES = 319;
+ int TAG_COLOR_MAP = 320;
+ int TAG_EXTRA_SAMPLES = 338;
int TAG_YCBCR_COEFFICIENTS = 529;
int TAG_REFERENCE_BLACK_WHITE = 532;
@@ -151,4 +154,11 @@ public interface TIFF {
int TAG_MODI_PLAIN_TEXT = 37679;
int TAG_MODI_OLE_PROPERTY_SET = 37680;
int TAG_MODI_TEXT_POS_INFO = 37681;
+
+ int TAG_TILE_WIDTH = 322;
+ int TAG_TILE_HEIGTH = 323;
+ int TAG_TILE_OFFSETS = 324;
+ int TAG_TILE_BYTE_COUNTS = 325;
+
+ int TAG_JPEG_TABLES = 347;
}
diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEG.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEG.java
index 2e5dd8c5..8753c26e 100644
--- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEG.java
+++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEG.java
@@ -45,6 +45,8 @@ public interface JPEG {
/** Define Quantization Tables segment marker (DQT). */
int DQT = 0xFFDB;
+ /** Define Huffman Tables segment marker (DHT). */
+ int DHT = 0xFFC4;
// App segment markers (APPn).
int APP0 = 0xFFE0;
diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGQuality.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGQuality.java
index f609148c..fbb7aa4a 100644
--- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGQuality.java
+++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGQuality.java
@@ -30,10 +30,12 @@ package com.twelvemonkeys.imageio.metadata.jpeg;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
+import javax.imageio.plugins.jpeg.JPEGQTable;
import javax.imageio.stream.ImageInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -83,7 +85,7 @@ public final class JPEGQuality {
}
// Adapted from ImageMagick coders/jpeg.c & http://blog.apokalyptik.com/2009/09/16/quality-time-with-your-jpegs/
- private static int getJPEGQuality(final short[][] quantizationTables) throws IOException {
+ private static int getJPEGQuality(final int[][] quantizationTables) throws IOException {
// System.err.println("tables: " + Arrays.deepToString(tables));
// TODO: Determine lossless JPEG
@@ -188,8 +190,24 @@ public final class JPEGQuality {
return -1;
}
- private static short[][] getQuantizationTables(List dqtSegments) throws IOException {
- short[][] tables = new short[4][];
+ public static JPEGQTable[] getQTables(final List dqtSegments) throws IOException {
+ int[][] tables = getQuantizationTables(dqtSegments);
+
+ List qTables = new ArrayList();
+ for (int[] table : tables) {
+ if (table != null) {
+ qTables.add(new JPEGQTable(table));
+ }
+ else {
+ break;
+ }
+ }
+
+ return qTables.toArray(new JPEGQTable[qTables.size()]);
+ }
+
+ private static int[][] getQuantizationTables(final List dqtSegments) throws IOException {
+ int[][] tables = new int[4][];
// JPEG may contain multiple DQT marker segments
for (JPEGSegment segment : dqtSegments) {
@@ -223,7 +241,7 @@ public final class JPEGQuality {
byte[] qtData = new byte[DCT_SIZE_2 * (bits + 1)];
data.readFully(qtData);
read += qtData.length;
- tables[num] = new short[DCT_SIZE_2];
+ tables[num] = new int[DCT_SIZE_2];
// Expand (this is slightly inefficient)
switch (bits) {
diff --git a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReaderTest.java b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReaderTest.java
index e5738037..5e184bd4 100644
--- a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReaderTest.java
+++ b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReaderTest.java
@@ -175,4 +175,19 @@ public class EXIFReaderTest extends MetadataReaderAbstractTest {
assertNotNull(exif);
assertEquals(3, exif.size());
}
+
+ @Test
+ public void testTIFFWithBadExifIFD() throws IOException {
+ // This image seems to contain bad TIFF data. But as other tools are able to read, so should we..
+ // It seems that the EXIF data (at offset 494196 or 0x78a74) overlaps with a custom
+ // Microsoft 'OLE Property Set' entry at 0x78a70 (UNDEFINED, count 5632)...
+ ImageInputStream stream = ImageIO.createImageInputStream(getResource("/tiff/chifley_logo.tif"));
+ Directory directory = createReader().read(stream);
+ assertEquals(22, directory.size());
+
+ // Some (all?) of the EXIF data is duplicated in the XMP, meaning PhotoShop can probably re-create it
+ Directory exif = (Directory) directory.getEntryById(TIFF.TAG_EXIF_IFD).getValue();
+ assertNotNull(exif);
+ assertEquals(0, exif.size()); // EXIFTool reports "Warning: Bad ExifIFD directory"
+ }
}
diff --git a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGQualityTest.java b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGQualityTest.java
index 52e55f49..cfb5e211 100644
--- a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGQualityTest.java
+++ b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGQualityTest.java
@@ -145,6 +145,10 @@ public class JPEGQualityTest {
}
}
+ @Test
+ public void testGetQTables() {
+ fail("Not implemented");
+ }
private BufferedImage createTestImage() {
BufferedImage image = new BufferedImage(90, 60, BufferedImage.TYPE_3BYTE_BGR);
diff --git a/imageio/imageio-metadata/src/test/resources/tiff/chifley_logo.tif b/imageio/imageio-metadata/src/test/resources/tiff/chifley_logo.tif
new file mode 100644
index 00000000..90fb5eb3
Binary files /dev/null and b/imageio/imageio-metadata/src/test/resources/tiff/chifley_logo.tif differ
diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java
index 61cfb25d..c3a588b3 100644
--- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java
+++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java
@@ -441,7 +441,7 @@ public class PSDImageReader extends ImageReaderBase {
case PSD.COMPRESSION_ZIP:
// TODO: Could probably use the ZIPDecoder (DeflateDecoder) here..
case PSD.COMPRESSION_ZIP_PREDICTION:
- // TODO: Need to find out if the normal java.util.zip can handle this...
+ // TODO: Look at TIFF prediction reading
// Could be same as PNG prediction? Read up...
throw new IIOException("PSD with ZIP compression not supported");
default:
diff --git a/imageio/imageio-tiff/pom.xml b/imageio/imageio-tiff/pom.xml
new file mode 100644
index 00000000..9084a3fd
--- /dev/null
+++ b/imageio/imageio-tiff/pom.xml
@@ -0,0 +1,32 @@
+
+
+ 4.0.0
+
+ com.twelvemonkeys.imageio
+ imageio
+ 3.0-SNAPSHOT
+
+ imageio-tiff
+ TwelveMonkeys :: ImageIO :: TIFF plugin
+
+ ImageIO plugin for Aldus/Adobe Tagged Image File Format (TIFF).
+
+
+
+
+ com.twelvemonkeys.imageio
+ imageio-core
+
+
+ com.twelvemonkeys.imageio
+ imageio-metadata
+
+
+ com.twelvemonkeys.imageio
+ imageio-core
+ tests
+
+
+
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/JPEGTables.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/JPEGTables.java
new file mode 100644
index 00000000..a806ec11
--- /dev/null
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/JPEGTables.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2012, Harald Kuhr
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name "TwelveMonkeys" nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.twelvemonkeys.imageio.plugins.tiff;
+
+import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
+import com.twelvemonkeys.imageio.metadata.jpeg.JPEGQuality;
+import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment;
+import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil;
+
+import javax.imageio.IIOException;
+import javax.imageio.plugins.jpeg.JPEGHuffmanTable;
+import javax.imageio.plugins.jpeg.JPEGQTable;
+import javax.imageio.stream.ImageInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * JPEGTables
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: JPEGTables.java,v 1.0 11.05.12 09:13 haraldk Exp$
+ */
+class JPEGTables {
+ private static final int DHT_LENGTH = 16;
+ private static final Map> SEGMENT_IDS = createSegmentIdsMap();
+
+ private JPEGQTable[] qTables;
+ private JPEGHuffmanTable[] dcHTables;
+ private JPEGHuffmanTable[] acHTables;
+
+ private static Map> createSegmentIdsMap() {
+ Map> segmentIds = new HashMap>();
+ segmentIds.put(JPEG.DQT, null);
+ segmentIds.put(JPEG.DHT, null);
+
+ return Collections.unmodifiableMap(segmentIds);
+ }
+
+ private final List segments;
+
+ public JPEGTables(ImageInputStream input) throws IOException {
+ segments = JPEGSegmentUtil.readSegments(input, SEGMENT_IDS);
+ }
+
+ public JPEGQTable[] getQTables() throws IOException {
+ if (qTables == null) {
+ qTables = JPEGQuality.getQTables(segments);
+ }
+
+ return qTables;
+ }
+
+ private void getHuffmanTables() throws IOException {
+ if (dcHTables == null || acHTables == null) {
+ List dc = new ArrayList();
+ List ac = new ArrayList();
+
+ // JPEG may contain multiple DHT marker segments
+ for (JPEGSegment segment : segments) {
+ if (segment.marker() != JPEG.DHT) {
+ continue;
+ }
+
+ DataInputStream data = new DataInputStream(segment.data());
+ int read = 0;
+
+ // A single DHT marker segment may contain multiple tables
+ while (read < segment.length()) {
+ int htInfo = data.read();
+ read++;
+
+ int num = htInfo & 0x0f; // 0-3
+ int type = htInfo >> 4; // 0 == DC, 1 == AC
+
+ if (type > 1) {
+ throw new IIOException("Bad DHT type: " + type);
+ }
+ if (num >= 4) {
+ throw new IIOException("Bad DHT table index: " + num);
+ }
+ else if (type == 0 ? dc.size() > num : ac.size() > num) {
+ throw new IIOException("Duplicate DHT table index: " + num);
+ }
+
+ // Read lengths as short array
+ short[] lengths = new short[DHT_LENGTH];
+ for (int i = 0, lengthsLength = lengths.length; i < lengthsLength; i++) {
+ lengths[i] = (short) data.readUnsignedByte();
+ }
+ read += lengths.length;
+
+ int sum = 0;
+ for (short length : lengths) {
+ sum += length;
+ }
+
+ // Expand table to short array
+ short[] table = new short[sum];
+ for (int j = 0; j < sum; j++) {
+ table[j] = (short) data.readUnsignedByte();
+ }
+
+ JPEGHuffmanTable hTable = new JPEGHuffmanTable(lengths, table);
+ if (type == 0) {
+ dc.add(num, hTable);
+ }
+ else {
+ ac.add(num, hTable);
+ }
+
+ read += sum;
+ }
+ }
+
+ dcHTables = dc.toArray(new JPEGHuffmanTable[dc.size()]);
+ acHTables = ac.toArray(new JPEGHuffmanTable[ac.size()]);
+ }
+ }
+
+ public JPEGHuffmanTable[] getDCHuffmanTables() throws IOException {
+ getHuffmanTables();
+ return dcHTables;
+ }
+
+ public JPEGHuffmanTable[] getACHuffmanTables() throws IOException {
+ getHuffmanTables();
+ return acHTables;
+ }
+}
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java
new file mode 100644
index 00000000..63af0f08
--- /dev/null
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (c) 2012, Harald Kuhr
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name "TwelveMonkeys" nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.twelvemonkeys.imageio.plugins.tiff;
+
+import com.twelvemonkeys.io.enc.DecodeException;
+import com.twelvemonkeys.io.enc.Decoder;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+/**
+ * LZWDecoder
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: LZWDecoder.java,v 1.0 08.05.12 21:11 haraldk Exp$
+ */
+final class LZWDecoder implements Decoder {
+ /** Clear: Re-initialize tables. */
+ static final int CLEAR_CODE = 256;
+ /** End of Information. */
+ static final int EOI_CODE = 257;
+
+ private static final int MIN_BITS = 9;
+ private static final int MAX_BITS = 12;
+
+ private final boolean reverseBitOrder;
+
+ private int currentByte = -1;
+ private int bitPos;
+
+ // TODO: Consider speeding things up with a "string" type (instead of the inner byte[]),
+ // that uses variable size/dynamic allocation, to avoid the excessive array copying?
+// private final byte[][] table = new byte[4096][0]; // libTiff adds another 1024 "for compatibility"...
+ private final byte[][] table = new byte[4096 + 1024][0]; // libTiff adds another 1024 "for compatibility"...
+ private int tableLength;
+ private int bitsPerCode;
+ private int oldCode = CLEAR_CODE;
+ private int maxCode;
+ private int maxString;
+ private boolean eofReached;
+
+ LZWDecoder(final boolean reverseBitOrder) {
+ this.reverseBitOrder = reverseBitOrder;
+
+ for (int i = 0; i < 256; i++) {
+ table[i] = new byte[] {(byte) i};
+ }
+
+ init();
+ }
+
+ LZWDecoder() {
+ this(false);
+ }
+
+
+ private int maxCodeFor(final int bits) {
+ return reverseBitOrder ? (1 << bits) - 2 : (1 << bits) - 1;
+ }
+
+ private void init() {
+ tableLength = 258;
+ bitsPerCode = MIN_BITS;
+ maxCode = maxCodeFor(bitsPerCode);
+ maxString = 1;
+ }
+
+ public int decode(final InputStream stream, final byte[] buffer) throws IOException {
+ // Adapted from the pseudo-code example found in the TIFF 6.0 Specification, 1992.
+ // See Section 13: "LZW Compression"/"LZW Decoding", page 61+
+ int bufferPos = 0;
+ int code;
+
+ while ((code = getNextCode(stream)) != EOI_CODE) {
+ if (code == CLEAR_CODE) {
+ init();
+ code = getNextCode(stream);
+
+ if (code == EOI_CODE) {
+ break;
+ }
+
+ bufferPos += writeString(table[code], buffer, bufferPos);
+ }
+ else {
+ if (code > tableLength + 1 || oldCode >= tableLength) {
+ // TODO: FixMe for old, borked streams
+ System.err.println("code: " + code);
+ System.err.println("oldCode: " + oldCode);
+ System.err.println("tableLength: " + tableLength);
+ throw new DecodeException("Corrupted LZW table");
+ }
+
+ if (isInTable(code)) {
+ bufferPos += writeString(table[code], buffer, bufferPos);
+ addStringToTable(concatenate(table[oldCode], table[code][0]));
+ }
+ else {
+ byte[] outString = concatenate(table[oldCode], table[oldCode][0]);
+
+ bufferPos += writeString(outString, buffer, bufferPos);
+ addStringToTable(outString);
+ }
+ }
+
+ oldCode = code;
+
+ if (bufferPos >= buffer.length - maxString - 1) {
+ // Buffer full, stop decoding for now
+ break;
+ }
+ }
+
+ return bufferPos;
+ }
+
+ private byte[] concatenate(final byte[] string, final byte firstChar) {
+ byte[] result = Arrays.copyOf(string, string.length + 1);
+ result[string.length] = firstChar;
+
+ return result;
+ }
+
+ private void addStringToTable(final byte[] string) throws IOException {
+ table[tableLength++] = string;
+
+ if (tableLength >= maxCode) {
+ bitsPerCode++;
+
+ if (bitsPerCode > MAX_BITS) {
+ if (reverseBitOrder) {
+ bitsPerCode--;
+ }
+ else {
+ throw new DecodeException(String.format("TIFF LZW with more than %d bits per code encountered (table overflow)", MAX_BITS));
+ }
+ }
+
+ maxCode = maxCodeFor(bitsPerCode);
+ }
+
+ if (string.length > maxString) {
+ maxString = string.length;
+ }
+ }
+
+ private int writeString(final byte[] string, final byte[] buffer, final int bufferPos) {
+ if (string.length == 0) {
+ return 0;
+ }
+ else if (string.length == 1) {
+ buffer[bufferPos] = string[0];
+
+ return 1;
+ }
+ else {
+ System.arraycopy(string, 0, buffer, bufferPos, string.length);
+
+ return string.length;
+ }
+ }
+
+ private boolean isInTable(int code) {
+ return code < tableLength;
+ }
+
+ private int getNextCode(final InputStream stream) throws IOException {
+ if (eofReached) {
+ return EOI_CODE;
+ }
+
+ int bitsToFill = bitsPerCode;
+ int value = 0;
+
+ while (bitsToFill > 0) {
+ int nextBits;
+ if (bitPos == 0) {
+ nextBits = stream.read();
+
+ if (nextBits == -1) {
+ // This is really a bad stream, but should be safe to handle this way, rather than throwing an EOFException.
+ // An EOFException will be thrown by the decoder stream later, if further reading is attempted.
+ eofReached = true;
+ return EOI_CODE;
+ }
+ }
+ else {
+ nextBits = currentByte;
+ }
+
+ int bitsFromHere = 8 - bitPos;
+ if (bitsFromHere > bitsToFill) {
+ bitsFromHere = bitsToFill;
+ }
+
+ if (reverseBitOrder) {
+ // NOTE: This is a spec violation. However, libTiff reads such files.
+ // TIFF 6.0 Specification, Section 13: "LZW Compression"/"The Algorithm", page 61, says:
+ // "LZW compression codes are stored into bytes in high-to-low-order fashion, i.e., FillOrder
+ // is assumed to be 1. The compressed codes are written as bytes (not words) so that the
+ // compressed data will be identical whether it is an ‘II’ or ‘MM’ file."
+
+ // Fill bytes from right-to-left
+ for (int i = 0; i < bitsFromHere; i++) {
+ int destBitPos = bitsPerCode - bitsToFill + i;
+ int srcBitPos = bitPos + i;
+ value |= ((nextBits & (1 << srcBitPos)) >> srcBitPos) << destBitPos;
+ }
+ }
+ else {
+ value |= (nextBits >> 8 - bitPos - bitsFromHere & 0xff >> 8 - bitsFromHere) << bitsToFill - bitsFromHere;
+ }
+
+ bitsToFill -= bitsFromHere;
+ bitPos += bitsFromHere;
+
+ if (bitPos >= 8) {
+ bitPos = 0;
+ }
+
+ currentByte = nextBits;
+ }
+
+ if (value == EOI_CODE) {
+ eofReached = true;
+ }
+
+ return value;
+ }
+
+ static boolean isOldBitReversedStream(final InputStream stream) throws IOException {
+ stream.mark(2);
+ try {
+ int one = stream.read();
+ int two = stream.read();
+
+ return one == 0 && (two & 0x1) == 1; // => (reversed) 1 00000000 == 256 (CLEAR_CODE)
+ }
+ finally {
+ stream.reset();
+ }
+ }
+}
+
diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/enc/LZWEncoder.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFBaseline.java
similarity index 67%
rename from sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/enc/LZWEncoder.java
rename to imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFBaseline.java
index a18faaa0..c96316e8 100644
--- a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/enc/LZWEncoder.java
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFBaseline.java
@@ -1,46 +1,59 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * * Neither the name "TwelveMonkeys" nor the
- * names of its contributors may be used to endorse or promote products
- * derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package com.twelvemonkeys.io.enc;
-
-import java.io.OutputStream;
-import java.io.IOException;
-
-/**
- * LZWEncoder.
- *
- *
- * @author Harald Kuhr
- * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/LZWEncoder.java#2 $
- */
-final class LZWEncoder implements Encoder {
- public void encode(OutputStream pStream, byte[] pBuffer, int pOffset, int pLength) throws IOException {
- // TODO: Implement
- // TODO: We probably need a GIF specific subclass
- }
-}
\ No newline at end of file
+/*
+ * Copyright (c) 2012, Harald Kuhr
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name "TwelveMonkeys" nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.twelvemonkeys.imageio.plugins.tiff;
+
+/**
+ * TIFFBaseline
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: TIFFBaseline.java,v 1.0 08.05.12 16:43 haraldk Exp$
+ */
+interface TIFFBaseline {
+ int COMPRESSION_NONE = 1;
+ int COMPRESSION_CCITT_HUFFMAN = 2;
+ int COMPRESSION_PACKBITS = 32773;
+
+ int PHOTOMETRIC_WHITE_IS_ZERO = 0;
+ int PHOTOMETRIC_BLACK_IS_ZERO = 1;
+ int PHOTOMETRIC_RGB = 2;
+ int PHOTOMETRIC_PALETTE = 3;
+ int PHOTOMETRIC_MASK = 4;
+
+ int SAMPLEFORMAT_UINT = 1;
+ int SAMPLEFORMAT_INT = 2;
+ int SAMPLEFORMAT_FP = 3;
+ int SAMPLEFORMAT_UNDEFINED = 4;
+
+ int PLANARCONFIG_CHUNKY = 1;
+
+ int EXTRASAMPLE_UNSPECIFIED = 0;
+ int EXTRASAMPLE_ASSOCALPHA = 1;
+ int EXTRASAMPLE_UNASSALPHA = 2;
+}
diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/enc/LZWDecoder.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFCustom.java
similarity index 74%
rename from sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/enc/LZWDecoder.java
rename to imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFCustom.java
index b2d56869..67a07059 100644
--- a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/enc/LZWDecoder.java
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFCustom.java
@@ -1,46 +1,49 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * * Neither the name "TwelveMonkeys" nor the
- * names of its contributors may be used to endorse or promote products
- * derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package com.twelvemonkeys.io.enc;
-
-import java.io.InputStream;
-import java.io.IOException;
-
-/**
- * LZWDecoder.
- *
- *
- * @author Harald Kuhr
- * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/LZWDecoder.java#2 $
- */
-final class LZWDecoder implements Decoder {
- public int decode(InputStream pStream, byte[] pBuffer) throws IOException {
- return 0; // TODO: Implement
- // TODO: We probably need a GIF specific subclass
- }
-}
\ No newline at end of file
+/*
+ * Copyright (c) 2012, Harald Kuhr
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name "TwelveMonkeys" nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.twelvemonkeys.imageio.plugins.tiff;
+
+/**
+ * TIFFCustom
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: TIFFCustom.java,v 1.0 10.05.12 17:35 haraldk Exp$
+ */
+interface TIFFCustom {
+ int PHOTOMETRIC_LOGL = 32844;
+ int PHOTOMETRIC_LOGLUV = 32845;
+
+ /** DNG: CFA (Color Filter Array)*/
+ int PHOTOMETRIC_CFA = 32803;
+ /** DNG: LinearRaw*/
+ int PHOTOMETRIC_LINEAR_RAW = 34892;
+
+ int SAMPLEFORMAT_COMPLEXINT = 5;
+ int SAMPLEFORMAT_COMPLEXIEEEFP = 6;
+}
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFExtension.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFExtension.java
new file mode 100644
index 00000000..44f9e9d7
--- /dev/null
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFExtension.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2012, Harald Kuhr
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name "TwelveMonkeys" nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.twelvemonkeys.imageio.plugins.tiff;
+
+/**
+ * TIFFExtension
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: TIFFExtension.java,v 1.0 08.05.12 16:45 haraldk Exp$
+ */
+interface TIFFExtension {
+ /** CCITT T.4/Group 3 Fax compression. */
+ int COMPRESSION_CCITT_T4 = 3;
+ /** CCITT T.6/Group 4 Fax compression. */
+ int COMPRESSION_CCITT_T6 = 4;
+ /** LZW Compression. Was baseline, but moved to extension due to license issues in the LZW algorithm. */
+ int COMPRESSION_LZW = 5;
+ /** Deprecated. */
+ int COMPRESSION_OLD_JPEG = 6;
+ /** JPEG Compression (lossy). */
+ int COMPRESSION_JPEG = 7;
+ /** Custom: PKZIP-style Deflate. */
+ int COMPRESSION_DEFLATE = 32946;
+ /** Adobe-style Deflate. */
+ int COMPRESSION_ZLIB = 8;
+
+ /*
+ LibTIFF:
+ COMPRESSION_NONE = 1;
+COMPRESSION_CCITTRLE = 2;
+COMPRESSION_CCITTFAX3 = COMPRESSION_CCITT_T4 = 3;
+COMPRESSION_CCITTFAX4 = COMPRESSION_CCITT_T6 = 4;
+COMPRESSION_LZW = 5;
+COMPRESSION_OJPEG = 6;
+COMPRESSION_JPEG = 7;
+COMPRESSION_NEXT = 32766;
+COMPRESSION_CCITTRLEW = 32771;
+COMPRESSION_PACKBITS = 32773;
+COMPRESSION_THUNDERSCAN = 32809;
+COMPRESSION_IT8CTPAD = 32895;
+COMPRESSION_IT8LW = 32896;
+COMPRESSION_IT8MP = 32897;
+COMPRESSION_IT8BL = 32898;
+COMPRESSION_PIXARFILM = 32908;
+COMPRESSION_PIXARLOG = 32909;
+COMPRESSION_DEFLATE = 32946;
+COMPRESSION_ADOBE_DEFLATE = 8;
+COMPRESSION_DCS = 32947;
+COMPRESSION_JBIG = 34661;
+COMPRESSION_SGILOG = 34676;
+COMPRESSION_SGILOG24 = 34677;
+COMPRESSION_JP2000 = 34712;
+ */
+
+ int PHOTOMETRIC_SEPARATED = 5;
+ int PHOTOMETRIC_YCBCR = 6;
+ int PHOTOMETRIC_CIELAB = 8;
+ int PHOTOMETRIC_ICCLAB = 9;
+ int PHOTOMETRIC_ITULAB = 10;
+
+ int PLANARCONFIG_PLANAR = 2;
+
+ int PREDICTOR_NONE = 1;
+ int PREDICTOR_HORIZONTAL_DIFFERENCING = 2;
+ int PREDICTOR_HORIZONTAL_FLOATINGPOINT = 3;
+}
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java
new file mode 100644
index 00000000..2491f377
--- /dev/null
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java
@@ -0,0 +1,990 @@
+/*
+ * Copyright (c) 2012, Harald Kuhr
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name "TwelveMonkeys" nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.twelvemonkeys.imageio.plugins.tiff;
+
+import com.sun.imageio.plugins.jpeg.JPEGImageReader;
+import com.twelvemonkeys.imageio.ImageReaderBase;
+import com.twelvemonkeys.imageio.color.ColorSpaces;
+import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
+import com.twelvemonkeys.imageio.metadata.Directory;
+import com.twelvemonkeys.imageio.metadata.Entry;
+import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
+import com.twelvemonkeys.imageio.metadata.exif.TIFF;
+import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
+import com.twelvemonkeys.imageio.stream.SubImageInputStream;
+import com.twelvemonkeys.imageio.util.IIOUtil;
+import com.twelvemonkeys.imageio.util.IndexedImageTypeSpecifier;
+import com.twelvemonkeys.imageio.util.ProgressListenerBase;
+import com.twelvemonkeys.io.LittleEndianDataInputStream;
+import com.twelvemonkeys.io.enc.DecoderStream;
+import com.twelvemonkeys.io.enc.PackBitsDecoder;
+
+import javax.imageio.*;
+import javax.imageio.event.IIOReadWarningListener;
+import javax.imageio.plugins.jpeg.JPEGImageReadParam;
+import javax.imageio.spi.IIORegistry;
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.spi.ServiceRegistry;
+import javax.imageio.stream.ImageInputStream;
+import java.awt.*;
+import java.awt.color.ColorSpace;
+import java.awt.color.ICC_Profile;
+import java.awt.image.*;
+import java.io.*;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterInputStream;
+import java.util.zip.ZipInputStream;
+
+/**
+ * ImageReader implementation for Aldus/Adobe Tagged Image File Format (TIFF).
+ *
+ * @see Adobe TIFF developer resources
+ * @see Wikipedia
+ * @see AWare Systems TIFF pages
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: TIFFImageReader.java,v 1.0 08.05.12 15:14 haraldk Exp$
+ */
+public class TIFFImageReader extends ImageReaderBase {
+ // TODO: Full BaseLine support
+ // TODO: Support ExtraSamples (an array!) (1: Associated Alpha (pre-multiplied), 2: Unassociated Alpha (non-multiplied)
+ // TODO: Handle SampleFormat (and give up if not == 1)
+ // TODO: Support Compression 2 (CCITT Modified Huffman) for bi-level images
+
+ // TODO: ImageIO functionality
+ // TODO: Subsampling
+ // TODO: Source region
+
+ // TODO: Extension support
+ // TODO: Support PlanarConfiguration 2
+ // TODO: Support ICCProfile (fully)
+ // TODO: Support Compression 3 & 4 (CCITT T.4 & T.6)
+ // TODO: Support Compression 6 ('Old-style' JPEG)
+ // TODO: Support Compression 34712 (JPEG2000)? Depends on JPEG2000 ImageReader
+ // TODO: Support Compression 34661 (JBIG)? Depends on JBIG ImageReader
+
+ // DONE:
+ // Delete the old Batik-based TIFFImageReader/Spi
+
+ private final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.tiff.debug"));
+
+ private CompoundDirectory ifds;
+ private Directory currentIFD;
+
+ TIFFImageReader(final TIFFImageReaderSpi provider) {
+ super(provider);
+ }
+
+ @Override
+ protected void resetMembers() {
+ ifds = null;
+ currentIFD = null;
+ }
+
+ private void readMetadata() throws IOException {
+ if (imageInput == null) {
+ throw new IllegalStateException("input not set");
+ }
+
+ if (ifds == null) {
+ ifds = (CompoundDirectory) new EXIFReader().read(imageInput); // NOTE: Sets byte order as a side effect
+
+ if (DEBUG) {
+ for (int i = 0; i < ifds.directoryCount(); i++) {
+ System.err.printf("ifd[%d]: %s\n", i, ifds.getDirectory(i));
+ }
+
+ System.err.println("Byte order: " + imageInput.getByteOrder());
+ System.err.println("numImages: " + ifds.directoryCount());
+ }
+ }
+ }
+
+ private void readIFD(final int imageIndex) throws IOException {
+ readMetadata();
+ checkBounds(imageIndex);
+ currentIFD = ifds.getDirectory(imageIndex);
+ }
+
+ @Override
+ public int getNumImages(final boolean allowSearch) throws IOException {
+ readMetadata();
+ return ifds.directoryCount();
+ }
+
+ private int getValueAsIntWithDefault(final int tag, String tagName, Integer defaultValue) throws IIOException {
+ Entry entry = currentIFD.getEntryById(tag);
+
+ if (entry == null) {
+ if (defaultValue != null) {
+ return defaultValue;
+ }
+
+ throw new IIOException("Missing TIFF tag: " + (tagName != null ? tagName : tag));
+ }
+
+ return ((Number) entry.getValue()).intValue();
+ }
+
+ private int getValueAsIntWithDefault(final int tag, Integer defaultValue) throws IIOException {
+ return getValueAsIntWithDefault(tag, null, defaultValue);
+ }
+
+ private int getValueAsInt(final int tag, String tagName) throws IIOException {
+ return getValueAsIntWithDefault(tag, tagName, null);
+ }
+
+ @Override
+ public int getWidth(int imageIndex) throws IOException {
+ readIFD(imageIndex);
+
+ return getValueAsInt(TIFF.TAG_IMAGE_WIDTH, "ImageWidth");
+ }
+
+ @Override
+ public int getHeight(int imageIndex) throws IOException {
+ readIFD(imageIndex);
+
+ return getValueAsInt(TIFF.TAG_IMAGE_HEIGHT, "ImageHeight");
+ }
+
+ @Override
+ public ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException {
+ readIFD(imageIndex);
+
+ int planarConfiguration = getValueAsIntWithDefault(TIFF.TAG_PLANAR_CONFIGURATION, TIFFExtension.PLANARCONFIG_PLANAR);
+ int interpretation = getValueAsInt(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, "PhotometricInterpretation");
+ int samplesPerPixel = getValueAsIntWithDefault(TIFF.TAG_SAMPLES_PER_PIXELS, 1);
+ int bitsPerSample = getBitsPerSample();
+ int dataType = bitsPerSample <= 8 ? DataBuffer.TYPE_BYTE : bitsPerSample <= 16 ? DataBuffer.TYPE_USHORT : DataBuffer.TYPE_INT;
+
+ // Read embedded cs
+ ICC_Profile profile = getICCProfile();
+ ColorSpace cs;
+
+ switch (interpretation) {
+ // TIFF 6.0 baseline
+ case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO:
+ // WhiteIsZero
+ // NOTE: We handle this by inverting the values when reading, as Java has no ColorModel that easily supports this.
+ // TODO: Consider returning null?
+ case TIFFBaseline.PHOTOMETRIC_BLACK_IS_ZERO:
+ // BlackIsZero
+ // Gray scale or B/W
+ switch (samplesPerPixel) {
+ case 1:
+ // TIFF 6.0 Spec says: 1, 4 or 8 for baseline (1 for bi-level, 4/8 for gray)
+ // ImageTypeSpecifier supports only 1, 2, 4, 8 or 16 bits, we'll go with that for now
+ cs = profile == null ? ColorSpace.getInstance(ColorSpace.CS_GRAY) : ColorSpaces.createColorSpace(profile);
+
+ if (cs == ColorSpace.getInstance(ColorSpace.CS_GRAY) && (bitsPerSample == 1 || bitsPerSample == 2 || bitsPerSample == 4 || bitsPerSample == 8 || bitsPerSample == 16)) {
+ return ImageTypeSpecifier.createGrayscale(bitsPerSample, dataType, false);
+ }
+ else if (bitsPerSample == 1 || bitsPerSample == 2 || bitsPerSample == 4 || bitsPerSample == 8 || bitsPerSample == 16 || bitsPerSample == 32) {
+ return ImageTypeSpecifier.createInterleaved(cs, new int[] {0}, dataType, false, false);
+ }
+ default:
+ throw new IIOException(String.format("Unsupported SamplesPerPixel/BitsPerSample combination for Bi-level/Gray TIFF (expected 1/1, 1/2, 1/4, 1/8 or 1/16): %d/%d", samplesPerPixel, bitsPerSample));
+ }
+
+ case TIFFExtension.PHOTOMETRIC_YCBCR:
+ // JPEG reader will handle YCbCr to RGB for us, we'll have to do it ourselves if not JPEG...
+ // TODO: Handle YCbCrSubsampling (up-scaler stream, or read data as-is + up-sample (sub-)raster after read? Apply smoothing?)
+ // TODO: We might want to handle USHORT_565 type, and allow different samplesPerPixel in that case especially
+ case TIFFBaseline.PHOTOMETRIC_RGB:
+ // RGB
+ cs = profile == null ? ColorSpace.getInstance(ColorSpace.CS_sRGB) : ColorSpaces.createColorSpace(profile);
+
+ switch (samplesPerPixel) {
+ case 3:
+ if (bitsPerSample == 8 || bitsPerSample == 16) {
+ switch (planarConfiguration) {
+ case TIFFBaseline.PLANARCONFIG_CHUNKY:
+ if (bitsPerSample == 8 && cs.isCS_sRGB()) {
+ return ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
+ }
+
+ return ImageTypeSpecifier.createInterleaved(cs, new int[] {0, 1, 2}, dataType, false, false);
+
+ case TIFFExtension.PLANARCONFIG_PLANAR:
+ return ImageTypeSpecifier.createBanded(cs, new int[] {0, 1, 2}, new int[] {0, 0, 0}, dataType, false, false);
+ }
+ }
+ case 4:
+ // TODO: Consult ExtraSamples!
+ if (bitsPerSample == 8 || bitsPerSample == 16) {
+ switch (planarConfiguration) {
+ case TIFFBaseline.PLANARCONFIG_CHUNKY:
+ if (bitsPerSample == 8 && cs.isCS_sRGB()) {
+ return ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR);
+ }
+
+ return ImageTypeSpecifier.createInterleaved(cs, new int[] {0, 1, 2, 3}, dataType, false, false);
+
+ case TIFFExtension.PLANARCONFIG_PLANAR:
+ return ImageTypeSpecifier.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, dataType, false, false);
+ }
+ }
+ // TODO: More samples might be ok, if multiple alpha or unknown samples
+ default:
+ throw new IIOException(String.format("Unsupported SamplesPerPixels/BitsPerSample combination for RGB TIF (expected 3/8, 4/8, 3/16 or 4/16): %d/%d", samplesPerPixel, bitsPerSample));
+ }
+ case TIFFBaseline.PHOTOMETRIC_PALETTE:
+ // Palette
+ if (samplesPerPixel != 1) {
+ throw new IIOException("Bad SamplesPerPixel value for Palette TIFF (expected 1): " + samplesPerPixel);
+ }
+ else if (bitsPerSample <= 0 || bitsPerSample > 16) {
+ throw new IIOException("Bad BitsPerSample value for Palette TIFF (expected <= 16): " + bitsPerSample);
+ }
+
+ Entry colorMap = currentIFD.getEntryById(TIFF.TAG_COLOR_MAP);
+ if (colorMap == null) {
+ throw new IIOException("Missing ColorMap for Palette TIFF");
+ }
+
+ int[] cmapShort = (int[]) colorMap.getValue();
+ int[] cmap = new int[colorMap.valueCount() / 3];
+
+ // All reds, then greens, and finally blues
+ for (int i = 0; i < cmap.length; i++) {
+ cmap[i] = (cmapShort[i ] / 256) << 16
+ | (cmapShort[i + cmap.length] / 256) << 8
+ | (cmapShort[i + 2 * cmap.length] / 256);
+ }
+
+ IndexColorModel icm = new IndexColorModel(bitsPerSample, cmap.length, cmap, 0, false, -1, dataType);
+
+ return IndexedImageTypeSpecifier.createFromIndexColorModel(icm);
+
+ case TIFFExtension.PHOTOMETRIC_SEPARATED:
+ // Separated (CMYK etc)
+ cs = profile == null ? ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK) : ColorSpaces.createColorSpace(profile);
+
+ switch (samplesPerPixel) {
+ case 4:
+ // TODO: Consult the 332/InkSet (1=CMYK, 2=Not CMYK; see InkNames), 334/NumberOfInks (def=4) and optionally 333/InkNames
+ if (bitsPerSample == 8 || bitsPerSample == 16) {
+ switch (planarConfiguration) {
+ case TIFFBaseline.PLANARCONFIG_CHUNKY:
+ return ImageTypeSpecifier.createInterleaved(cs, new int[] {0, 1, 2, 3}, dataType, false, false);
+ case TIFFExtension.PLANARCONFIG_PLANAR:
+ return ImageTypeSpecifier.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, dataType, false, false);
+ }
+ }
+
+ // TODO: More samples might be ok, if multiple alpha or unknown samples, consult ExtraSamples
+
+ default:
+ throw new IIOException(
+ String.format("Unsupported TIFF SamplesPerPixels/BitsPerSample combination for Separated TIFF (expected 4/8 or 4/16): %d/%s", samplesPerPixel, bitsPerSample)
+ );
+ }
+ case TIFFBaseline.PHOTOMETRIC_MASK:
+ // Transparency mask
+
+ // TODO: Known extensions
+ throw new IIOException("Unsupported TIFF PhotometricInterpretation value: " + interpretation);
+ default:
+ throw new IIOException("Unknown TIFF PhotometricInterpretation value: " + interpretation);
+ }
+ }
+
+ private int getBitsPerSample() throws IIOException {
+ long[] value = getValueAsLongArray(TIFF.TAG_BITS_PER_SAMPLE, "BitsPerSample", false);
+
+ if (value == null || value.length == 0) {
+ return 1;
+ }
+ else {
+ int bitsPerSample = (int) value[0];
+
+ if (value.length > 1) {
+ for (long bps : value) {
+ if (bps != bitsPerSample) {
+ throw new IIOException("Varying BitsPerSample not supported: " + Arrays.toString(value));
+ }
+ }
+ }
+
+ return bitsPerSample;
+ }
+ }
+
+ @Override
+ public Iterator getImageTypes(int imageIndex) throws IOException {
+ readIFD(imageIndex);
+
+ ImageTypeSpecifier rawType = getRawImageType(imageIndex);
+ List specs = new ArrayList();
+
+ // TODO: Based on raw type, we can probably convert to most RGB types at least, maybe gray etc
+ // TODO: Planar to chunky by default
+
+ specs.add(rawType);
+
+ return specs.iterator();
+ }
+
+ @Override
+ public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException {
+ readIFD(imageIndex);
+
+ int width = getWidth(imageIndex);
+ int height = getHeight(imageIndex);
+
+ BufferedImage destination = getDestination(param, getImageTypes(imageIndex), width, height);
+ ImageTypeSpecifier rawType = getRawImageType(imageIndex);
+ checkReadParamBandSettings(param, rawType.getNumBands(), destination.getSampleModel().getNumBands());
+
+ final Rectangle source = new Rectangle();
+ final Rectangle dest = new Rectangle();
+ computeRegions(param, width, height, destination, source, dest);
+
+ WritableRaster raster = destination.getRaster();
+
+ final int interpretation = getValueAsInt(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, "PhotometricInterpretation");
+ final int compression = getValueAsIntWithDefault(TIFF.TAG_COMPRESSION, TIFFBaseline.COMPRESSION_NONE);
+ final int predictor = getValueAsIntWithDefault(TIFF.TAG_PREDICTOR, 1);
+ final int planarConfiguration = getValueAsIntWithDefault(TIFF.TAG_PLANAR_CONFIGURATION, TIFFBaseline.PLANARCONFIG_CHUNKY);
+ final int numBands = planarConfiguration == TIFFExtension.PLANARCONFIG_PLANAR ? 1 : raster.getNumBands();
+
+ // NOTE: We handle strips as tiles of tileWidth == width by tileHeight == rowsPerStrip
+ // Strips are top/down, tiles are left/right, top/down
+ int stripTileWidth = width;
+ int stripTileHeight = getValueAsIntWithDefault(TIFF.TAG_ROWS_PER_STRIP, height);
+ long[] stripTileOffsets = getValueAsLongArray(TIFF.TAG_TILE_OFFSETS, "TileOffsets", false);
+ long[] stripTileByteCounts;
+
+ if (stripTileOffsets != null) {
+ stripTileByteCounts = getValueAsLongArray(TIFF.TAG_TILE_BYTE_COUNTS, "TileByteCounts", false);
+ if (stripTileByteCounts == null) {
+ processWarningOccurred("Missing TileByteCounts for tiled TIFF with compression: " + compression);
+ }
+
+ stripTileWidth = getValueAsInt(TIFF.TAG_TILE_WIDTH, "TileWidth");
+ stripTileHeight = getValueAsInt(TIFF.TAG_TILE_HEIGTH, "TileHeight");
+ }
+ else {
+ stripTileOffsets = getValueAsLongArray(TIFF.TAG_STRIP_OFFSETS, "StripOffsets", true);
+ stripTileByteCounts = getValueAsLongArray(TIFF.TAG_STRIP_BYTE_COUNTS, "StripByteCounts", false);
+ if (stripTileByteCounts == null) {
+ processWarningOccurred("Missing StripByteCounts for TIFF with compression: " + compression);
+ }
+
+ // NOTE: This is really against the spec, but libTiff seems to handle it. TIFF 6.0 says:
+ // "Do not use both strip- oriented and tile-oriented fields in the same TIFF file".
+ stripTileWidth = getValueAsIntWithDefault(TIFF.TAG_TILE_WIDTH, "TileWidth", stripTileWidth);
+ stripTileHeight = getValueAsIntWithDefault(TIFF.TAG_TILE_HEIGTH, "TileHeight", stripTileHeight);
+ }
+
+ int tilesAcross = (width + stripTileWidth - 1) / stripTileWidth;
+ int tilesDown = (height + stripTileHeight - 1) / stripTileHeight;
+ WritableRaster rowRaster = rawType.getColorModel().createCompatibleWritableRaster(stripTileWidth, 1);
+ int row = 0;
+
+ // Read data
+ processImageStarted(imageIndex);
+
+ switch (compression) {
+ // TIFF Baseline
+ case TIFFBaseline.COMPRESSION_NONE:
+ // No compression
+ case TIFFExtension.COMPRESSION_DEFLATE:
+ // 'PKZIP-style' Deflate
+ case TIFFBaseline.COMPRESSION_PACKBITS:
+ // PackBits
+ case TIFFExtension.COMPRESSION_LZW:
+ // LZW
+ case TIFFExtension.COMPRESSION_ZLIB:
+ // 'Adobe-style' Deflate
+
+ // General uncompressed/compressed reading
+ for (int y = 0; y < tilesDown; y++) {
+ int col = 0;
+ int rowsInTile = Math.min(stripTileHeight, height - row);
+
+ for (int x = 0; x < tilesAcross; x++) {
+ int colsInTile = Math.min(stripTileWidth, width - col);
+ int i = y * tilesAcross + x;
+
+ imageInput.seek(stripTileOffsets[i]);
+
+ DataInput input;
+ if (compression == TIFFBaseline.COMPRESSION_NONE) {
+ input = imageInput;
+ }
+ else {
+ InputStream adapter = stripTileByteCounts != null
+ ? IIOUtil.createStreamAdapter(imageInput, stripTileByteCounts[i])
+ : IIOUtil.createStreamAdapter(imageInput);
+
+ // According to the spec, short/long/etc should follow order of containing stream
+ input = imageInput.getByteOrder() == ByteOrder.BIG_ENDIAN
+ ? new DataInputStream(createDecoderInputStream(compression, adapter))
+ : new LittleEndianDataInputStream(createDecoderInputStream(compression, adapter));
+
+ }
+
+ // Read a full strip/tile
+ readStripTileData(rowRaster, interpretation, predictor, raster, numBands, col, row, colsInTile, rowsInTile, input);
+
+ if (abortRequested()) {
+ break;
+ }
+
+ col += colsInTile;
+ }
+
+ processImageProgress(100f * row / (float) height);
+
+ if (abortRequested()) {
+ processReadAborted();
+ break;
+ }
+
+ row += rowsInTile;
+ }
+
+ break;
+
+ case TIFFExtension.COMPRESSION_JPEG:
+ // JPEG ('new-style' JPEG)
+
+ // TIFF is strictly ISO JPEG, so we should probably stick to the standard reader
+ ImageReader jpegReader = new JPEGImageReader(getOriginatingProvider());
+ JPEGImageReadParam jpegParam = (JPEGImageReadParam) jpegReader.getDefaultReadParam();
+
+ // JPEG_TABLES should be a full JPEG 'abbreviated table specification', containing:
+ // SOI, DQT, DHT, (optional markers that we ignore)..., EOI
+ Entry tablesEntry = currentIFD.getEntryById(TIFF.TAG_JPEG_TABLES);
+ byte[] tablesValue = tablesEntry != null ? (byte[]) tablesEntry.getValue() : null;
+ if (tablesValue != null) {
+ // TODO: Work this out...
+ // Whatever values I pass the reader as the read param, it never gets the same quality as if
+ // I just invoke jpegReader.getStreamMetadata...
+ // Might have something to do with subsampling?
+ // How do we pass the chroma-subsampling parameter from the TIFF structure to the JPEG reader?
+
+ jpegReader.setInput(new ByteArrayImageInputStream(tablesValue));
+
+ // NOTE: This initializes the tables AND MORE for the reader (as if by magic).
+ // This is probably a bug, as later setInput calls should clear/override the tables
+ // However, it would be extremely convenient, not having to actually fiddle with the stream meta data (as below)
+ /*IIOMetadata streamMetadata = */jpegReader.getStreamMetadata();
+
+ /*
+ IIOMetadataNode root = (IIOMetadataNode) streamMetadata.getAsTree(streamMetadata.getNativeMetadataFormatName());
+ NodeList dqt = root.getElementsByTagName("dqt");
+ NodeList dqtables = ((IIOMetadataNode) dqt.item(0)).getElementsByTagName("dqtable");
+ JPEGQTable[] qTables = new JPEGQTable[dqtables.getLength()];
+ for (int i = 0; i < dqtables.getLength(); i++) {
+ qTables[i] = (JPEGQTable) ((IIOMetadataNode) dqtables.item(i)).getUserObject();
+ System.err.println("qTables: " + qTables[i]);
+ }
+
+ List acHTables = new ArrayList();
+ List dcHTables = new ArrayList();
+
+ NodeList dht = root.getElementsByTagName("dht");
+ for (int i = 0; i < dht.getLength(); i++) {
+ NodeList dhtables = ((IIOMetadataNode) dht.item(i)).getElementsByTagName("dhtable");
+ for (int j = 0; j < dhtables.getLength(); j++) {
+ System.err.println("dhtables.getLength(): " + dhtables.getLength());
+ IIOMetadataNode dhtable = (IIOMetadataNode) dhtables.item(j);
+ JPEGHuffmanTable userObject = (JPEGHuffmanTable) dhtable.getUserObject();
+ if ("0".equals(dhtable.getAttribute("class"))) {
+ dcHTables.add(userObject);
+ }
+ else {
+ acHTables.add(userObject);
+ }
+ }
+ }
+
+ JPEGHuffmanTable[] dcTables = dcHTables.toArray(new JPEGHuffmanTable[dcHTables.size()]);
+ JPEGHuffmanTable[] acTables = acHTables.toArray(new JPEGHuffmanTable[acHTables.size()]);
+*/
+// JPEGTables tables = new JPEGTables(new ByteArrayImageInputStream(tablesValue));
+// JPEGQTable[] qTables = tables.getQTables();
+// JPEGHuffmanTable[] dcTables = tables.getDCHuffmanTables();
+// JPEGHuffmanTable[] acTables = tables.getACHuffmanTables();
+
+// System.err.println("qTables: " + Arrays.toString(qTables));
+// System.err.println("dcTables: " + Arrays.toString(dcTables));
+// System.err.println("acTables: " + Arrays.toString(acTables));
+
+// jpegParam.setDecodeTables(qTables, dcTables, acTables);
+ }
+ else {
+ processWarningOccurred("Missing JPEGTables for TIFF with compression: 7 (JPEG)");
+ // ...and the JPEG reader will probably choke on missing tables...
+ }
+
+ for (int y = 0; y < tilesDown; y++) {
+ int col = 0;
+ int rowsInTile = Math.min(stripTileHeight, height - row);
+
+ for (int x = 0; x < tilesAcross; x++) {
+ int i = y * tilesAcross + x;
+ int colsInTile = Math.min(stripTileWidth, width - col);
+
+ imageInput.seek(stripTileOffsets[i]);
+ SubImageInputStream subStream = new SubImageInputStream(imageInput, stripTileByteCounts != null ? (int) stripTileByteCounts[i] : Short.MAX_VALUE);
+ try {
+ jpegReader.setInput(subStream);
+ jpegParam.setSourceRegion(new Rectangle(0, 0, colsInTile, rowsInTile));
+ jpegParam.setDestinationOffset(new Point(col, row));
+ jpegParam.setDestination(destination);
+ // TODO: This works only if Gray/YCbCr/RGB, not CMYK...
+ jpegReader.read(0, jpegParam);
+ }
+ finally {
+ subStream.close();
+ }
+
+ if (abortRequested()) {
+ break;
+ }
+
+ col += colsInTile;
+ }
+
+ processImageProgress(100f * row / (float) height);
+
+ if (abortRequested()) {
+ processReadAborted();
+ break;
+ }
+
+ row += rowsInTile;
+ }
+
+ break;
+
+ case TIFFBaseline.COMPRESSION_CCITT_HUFFMAN:
+ // CCITT modified Huffman
+
+ // Additionally, the specification defines these values as part of the TIFF extensions:
+ case TIFFExtension.COMPRESSION_CCITT_T4:
+ // CCITT Group 3 fax encoding
+ case TIFFExtension.COMPRESSION_CCITT_T6:
+ // CCITT Group 4 fax encoding
+ case TIFFExtension.COMPRESSION_OLD_JPEG:
+ // JPEG ('old-style' JPEG, later overridden in Technote2)
+
+ throw new IIOException("Unsupported TIFF Compression value: " + compression);
+ default:
+ throw new IIOException("Unknown TIFF Compression value: " + compression);
+ }
+
+ processImageComplete();
+
+ return destination;
+ }
+
+ private void readStripTileData(final WritableRaster rowRaster, final int interpretation, final int predictor,
+ final WritableRaster raster, final int numBands, final int col, final int startRow,
+ final int colsInStrip, final int rowsInStrip, final DataInput input)
+ throws IOException {
+ switch (rowRaster.getTransferType()) {
+ case DataBuffer.TYPE_BYTE:
+ byte[] rowData = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
+ for (int j = 0; j < rowsInStrip; j++) {
+ int row = startRow + j;
+
+// input.readFully(rowData);
+ for (int k = 0; k < rowData.length; k++) {
+ try {
+ rowData[k] = input.readByte();
+ }
+ catch (IOException e) {
+ Arrays.fill(rowData, k, rowData.length, (byte) -1);
+ System.err.printf("Unexpected EOF or bad data at [%d %d]\n", col + k, row);
+ break;
+ }
+ }
+
+ unPredict(predictor, colsInStrip, 1, numBands, rowData);
+ normalizeBlack(interpretation, rowData);
+
+ if (colsInStrip == rowRaster.getWidth()) {
+ raster.setDataElements(col, row, rowRaster);
+ }
+ else {
+ raster.setDataElements(col, row, rowRaster.createChild(0, 0, colsInStrip, 1, 0, 0, null));
+ }
+ }
+
+ break;
+ case DataBuffer.TYPE_USHORT:
+ short [] rowDataShort = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
+ for (int j = 0; j < rowsInStrip; j++) {
+ int row = startRow + j;
+
+ for (int k = 0; k < rowDataShort.length; k++) {
+ rowDataShort[k] = input.readShort();
+ }
+
+ // TODO: Not sure how this works for USHORT... unpredict on byte level? In that case, we'll have to rewrite...
+ unPredict(predictor, colsInStrip, 1, numBands, rowDataShort);
+ normalizeBlack(interpretation, rowDataShort);
+ if (colsInStrip == rowRaster.getWidth()) {
+ raster.setDataElements(col, row, rowRaster);
+ }
+ else {
+ raster.setDataElements(col, row, rowRaster.createChild(0, 0, colsInStrip, 1, 0, 0, null));
+ }
+ }
+
+ break;
+ case DataBuffer.TYPE_INT:
+ int [] rowDataInt = ((DataBufferInt) rowRaster.getDataBuffer()).getData();
+ for (int j = 0; j < rowsInStrip; j++) {
+ int row = startRow + j;
+
+ for (int k = 0; k < rowDataInt.length; k++) {
+ rowDataInt[k] = input.readInt();
+ }
+
+ // TODO: Not sure how this works for USHORT... unpredict on byte level? In that case, we'll have to rewrite...
+// unPredict(predictor, colsInStrip, 1, numBands, rowDataInt);
+ normalizeBlack(interpretation, rowDataInt);
+ if (colsInStrip == rowRaster.getWidth()) {
+ raster.setDataElements(col, row, rowRaster);
+ }
+ else {
+ raster.setDataElements(col, row, rowRaster.createChild(0, 0, colsInStrip, 1, 0, 0, null));
+ }
+ }
+
+ break;
+ }
+ }
+
+ private void normalizeBlack(int photometricInterpretation, short[] data) {
+ if (photometricInterpretation == TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO) {
+ // Inverse values
+ for (int i = 0; i < data.length; i++) {
+ data[i] = (short) (0xffff - data[i] & 0xffff);
+ }
+ }
+ }
+
+ private void normalizeBlack(int photometricInterpretation, int[] data) {
+ if (photometricInterpretation == TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO) {
+ // Inverse values
+ for (int i = 0; i < data.length; i++) {
+ data[i] = (0xffffffff - data[i]);
+ }
+ }
+ }
+
+ private void normalizeBlack(int photometricInterpretation, byte[] data) {
+ if (photometricInterpretation == TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO) {
+ // Inverse values
+ for (int i = 0; i < data.length; i++) {
+ data[i] = (byte) (0xff - data[i] & 0xff);
+ }
+ }
+ }
+
+ @SuppressWarnings("UnusedParameters")
+ private void unPredict(final int predictor, int scanLine, int rows, int bands, int[] data) throws IIOException {
+ // See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64.
+ switch (predictor) {
+ case TIFFExtension.PREDICTOR_NONE:
+ break;
+ case TIFFExtension.PREDICTOR_HORIZONTAL_DIFFERENCING:
+ // TODO: Implement
+ case TIFFExtension.PREDICTOR_HORIZONTAL_FLOATINGPOINT:
+ throw new IIOException("Unsupported TIFF Predictor value: " + predictor);
+ default:
+ throw new IIOException("Unknown TIFF Predictor value: " + predictor);
+ }
+ }
+
+ @SuppressWarnings("UnusedParameters")
+ private void unPredict(final int predictor, int scanLine, int rows, int bands, short[] data) throws IIOException {
+ // See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64.
+ switch (predictor) {
+ case TIFFExtension.PREDICTOR_NONE:
+ break;
+ case TIFFExtension.PREDICTOR_HORIZONTAL_DIFFERENCING:
+ // TODO: Implement
+ case TIFFExtension.PREDICTOR_HORIZONTAL_FLOATINGPOINT:
+ throw new IIOException("Unsupported TIFF Predictor value: " + predictor);
+ default:
+ throw new IIOException("Unknown TIFF Predictor value: " + predictor);
+ }
+ }
+
+ private void unPredict(final int predictor, int scanLine, int rows, final int bands, byte[] data) throws IIOException {
+ // See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64.
+ switch (predictor) {
+ case TIFFExtension.PREDICTOR_NONE:
+ break;
+ case TIFFExtension.PREDICTOR_HORIZONTAL_DIFFERENCING:
+ for (int y = 0; y < rows; y++) {
+ for (int x = 1; x < scanLine; x++) {
+ // TODO: For planar data (PlanarConfiguration == 2), treat as bands == 1
+ for (int b = 0; b < bands; b++) {
+ int off = y * scanLine + x;
+ data[off * bands + b] = (byte) (data[(off - 1) * bands + b] + data[off * bands + b]);
+ }
+ }
+ }
+
+ break;
+ case TIFFExtension.PREDICTOR_HORIZONTAL_FLOATINGPOINT:
+ throw new IIOException("Unsupported TIFF Predictor value: " + predictor);
+ default:
+ throw new IIOException("Unknown TIFF Predictor value: " + predictor);
+ }
+ }
+
+ private InputStream createDecoderInputStream(final int compression, final InputStream stream) throws IOException {
+ switch (compression) {
+ case TIFFBaseline.COMPRESSION_PACKBITS:
+ return new DecoderStream(stream, new PackBitsDecoder(), 1024);
+ case TIFFExtension.COMPRESSION_LZW:
+ return new DecoderStream(stream, new LZWDecoder(LZWDecoder.isOldBitReversedStream(stream)), 1024);
+ case TIFFExtension.COMPRESSION_ZLIB:
+ return new InflaterInputStream(stream, new Inflater(), 1024);
+ case TIFFExtension.COMPRESSION_DEFLATE:
+ return new ZipInputStream(stream);
+ default:
+ throw new IllegalArgumentException("Unsupported TIFF compression: " + compression);
+ }
+ }
+
+ private long[] getValueAsLongArray(final int tag, final String tagName, boolean required) throws IIOException {
+ Entry entry = currentIFD.getEntryById(tag);
+ if (entry == null) {
+ if (required) {
+ throw new IIOException("Missing TIFF tag " + tagName);
+ }
+
+ return null;
+ }
+
+ long[] value;
+
+ if (entry.valueCount() == 1) {
+ // For single entries, this will be a boxed type
+ value = new long[] {((Number) entry.getValue()).longValue()};
+ }
+ else if (entry.getValue() instanceof short[]) {
+ short[] shorts = (short[]) entry.getValue();
+ value = new long[shorts.length];
+
+ for (int i = 0, stripOffsetsValueLength = value.length; i < stripOffsetsValueLength; i++) {
+ value[i] = shorts[i];
+ }
+ }
+ else if (entry.getValue() instanceof int[]) {
+ int[] ints = (int[]) entry.getValue();
+ value = new long[ints.length];
+
+ for (int i = 0, stripOffsetsValueLength = value.length; i < stripOffsetsValueLength; i++) {
+ value[i] = ints[i];
+ }
+ }
+ else if (entry.getValue() instanceof long[]) {
+ value = (long[]) entry.getValue();
+ }
+ else {
+ throw new IIOException(String.format("Unsupported %s type: %s (%s)", tagName, entry.getTypeName(), entry.getValue().getClass()));
+ }
+
+ return value;
+ }
+
+ public ICC_Profile getICCProfile() {
+ Entry entry = currentIFD.getEntryById(TIFF.TAG_ICC_PROFILE);
+ if (entry == null) {
+ return null;
+ }
+
+ byte[] value = (byte[]) entry.getValue();
+ return ICC_Profile.getInstance(value);
+ }
+
+ public static void main(final String[] args) throws IOException {
+ for (final String arg : args) {
+ File file = new File(arg);
+
+ ImageInputStream input = ImageIO.createImageInputStream(file);
+ if (input == null) {
+ System.err.println("Could not read file: " + file);
+ continue;
+ }
+
+ deregisterOSXTIFFImageReaderSpi();
+
+ Iterator readers = ImageIO.getImageReaders(input);
+
+ if (!readers.hasNext()) {
+ System.err.println("No reader for: " + file);
+ continue;
+ }
+
+ ImageReader reader = readers.next();
+ System.err.println("Reading using: " + reader);
+
+ reader.addIIOReadWarningListener(new IIOReadWarningListener() {
+ public void warningOccurred(ImageReader source, String warning) {
+ System.err.println("Warning: " + arg + ": " + warning);
+ }
+ });
+ reader.addIIOReadProgressListener(new ProgressListenerBase() {
+ private static final int MAX_W = 78;
+ int lastProgress = 0;
+
+ @Override
+ public void imageStarted(ImageReader source, int imageIndex) {
+ System.out.print("[");
+ }
+
+ @Override
+ public void imageProgress(ImageReader source, float percentageDone) {
+ int steps = ((int) (percentageDone * MAX_W) / 100);
+
+ for (int i = lastProgress; i < steps; i++) {
+ System.out.print(".");
+ }
+
+ System.out.flush();
+ lastProgress = steps;
+ }
+
+ @Override
+ public void imageComplete(ImageReader source) {
+ for (int i = lastProgress; i < MAX_W; i++) {
+ System.out.print(".");
+ }
+
+ System.out.println("]");
+ }
+ });
+
+ reader.setInput(input);
+
+ try {
+ ImageReadParam param = reader.getDefaultReadParam();
+ int numImages = reader.getNumImages(true);
+ for (int imageNo = 0; imageNo < numImages; imageNo++) {
+ // if (args.length > 1) {
+ // int sub = Integer.parseInt(args[1]);
+ // int sub = 4;
+ // param.setSourceSubsampling(sub, sub, 0, 0);
+ // }
+
+ long start = System.currentTimeMillis();
+ BufferedImage image = reader.read(imageNo, param);
+ System.err.println("Read time: " + (System.currentTimeMillis() - start) + " ms");
+// System.err.println("image: " + image);
+
+// File tempFile = File.createTempFile("lzw-", ".bin");
+// byte[] data = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
+// FileOutputStream stream = new FileOutputStream(tempFile);
+// try {
+// FileUtil.copy(new ByteArrayInputStream(data, 45 * image.getWidth() * 3, 5 * image.getWidth() * 3), stream);
+//
+// showIt(image.getSubimage(0, 45, image.getWidth(), 5), tempFile.getAbsolutePath());
+// }
+// finally {
+// stream.close();
+// }
+//
+// System.err.println("tempFile: " + tempFile.getAbsolutePath());
+
+ // image = new ResampleOp(reader.getWidth(0) / 4, reader.getHeight(0) / 4, ResampleOp.FILTER_LANCZOS).filter(image, null);
+//
+// int maxW = 800;
+// int maxH = 800;
+//
+// if (image.getWidth() > maxW || image.getHeight() > maxH) {
+// start = System.currentTimeMillis();
+// float aspect = reader.getAspectRatio(0);
+// if (aspect >= 1f) {
+// image = ImageUtil.createResampled(image, maxW, Math.round(maxW / aspect), Image.SCALE_DEFAULT);
+// }
+// else {
+// image = ImageUtil.createResampled(image, Math.round(maxH * aspect), maxH, Image.SCALE_DEFAULT);
+// }
+// // System.err.println("Scale time: " + (System.currentTimeMillis() - start) + " ms");
+// }
+
+ showIt(image, String.format("Image: %s [%d x %d]", file.getName(), reader.getWidth(imageNo), reader.getHeight(imageNo)));
+
+ try {
+ int numThumbnails = reader.getNumThumbnails(0);
+ for (int thumbnailNo = 0; thumbnailNo < numThumbnails; thumbnailNo++) {
+ BufferedImage thumbnail = reader.readThumbnail(imageNo, thumbnailNo);
+ // System.err.println("thumbnail: " + thumbnail);
+ showIt(thumbnail, String.format("Thumbnail: %s [%d x %d]", file.getName(), thumbnail.getWidth(), thumbnail.getHeight()));
+ }
+ }
+ catch (IIOException e) {
+ System.err.println("Could not read thumbnails: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+ }
+ catch (Throwable t) {
+ System.err.println(file);
+ t.printStackTrace();
+ }
+ finally {
+ input.close();
+ }
+ }
+ }
+
+ private static void deregisterOSXTIFFImageReaderSpi() {
+ IIORegistry registry = IIORegistry.getDefaultInstance();
+ Iterator providers = registry.getServiceProviders(ImageReaderSpi.class, new ServiceRegistry.Filter() {
+ public boolean filter(Object provider) {
+ return provider.getClass().getName().equals("com.sun.imageio.plugins.tiff.TIFFImageReaderSpi");
+ }
+ }, false);
+
+ while (providers.hasNext()) {
+ ImageReaderSpi next = providers.next();
+ registry.deregisterServiceProvider(next);
+ }
+ }
+}
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderSpi.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderSpi.java
new file mode 100644
index 00000000..c4254176
--- /dev/null
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderSpi.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2012, Harald Kuhr
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name "TwelveMonkeys" nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.twelvemonkeys.imageio.plugins.tiff;
+
+import com.twelvemonkeys.imageio.metadata.exif.TIFF;
+import com.twelvemonkeys.imageio.spi.ProviderInfo;
+import com.twelvemonkeys.imageio.util.IIOUtil;
+
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.stream.ImageInputStream;
+import java.io.IOException;
+import java.nio.ByteOrder;
+import java.util.Locale;
+
+/**
+ * TIFFImageReaderSpi
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: TIFFImageReaderSpi.java,v 1.0 08.05.12 15:14 haraldk Exp$
+ */
+public class TIFFImageReaderSpi extends ImageReaderSpi {
+ // TODO: Should we make sure we register (order) before the com.sun.imageio thing (that isn't what is says) provided by Apple?
+ /**
+ * Creates a {@code TIFFImageReaderSpi}.
+ */
+ public TIFFImageReaderSpi() {
+ this(IIOUtil.getProviderInfo(TIFFImageReaderSpi.class));
+ }
+
+ private TIFFImageReaderSpi(final ProviderInfo providerInfo) {
+ super(
+ providerInfo.getVendorName(),
+ providerInfo.getVersion(),
+ new String[]{"tiff", "TIFF"},
+ new String[]{"tif", "tiff"},
+ new String[]{
+ "image/tiff", "image/x-tiff"
+ },
+ "com.twelvemkonkeys.imageio.plugins.tiff.TIFFImageReader",
+ STANDARD_INPUT_TYPE,
+// new String[]{"com.twelvemkonkeys.imageio.plugins.tif.TIFFImageWriterSpi"},
+ null,
+ true, // supports standard stream metadata
+ null, null, // native stream format name and class
+ null, null, // extra stream formats
+ true, // supports standard image metadata
+ null, null,
+ null, null // extra image metadata formats
+ );
+ }
+
+ public boolean canDecodeInput(final Object pSource) throws IOException {
+ if (!(pSource instanceof ImageInputStream)) {
+ return false;
+ }
+
+ ImageInputStream stream = (ImageInputStream) pSource;
+
+ stream.mark();
+ try {
+ byte[] bom = new byte[2];
+ stream.readFully(bom);
+
+ ByteOrder originalOrder = stream.getByteOrder();
+
+ try {
+ if (bom[0] == 'I' && bom[1] == 'I') {
+ stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+ }
+ else if (bom[0] == 'M' && bom[1] == 'M') {
+ stream.setByteOrder(ByteOrder.BIG_ENDIAN);
+ }
+ else {
+ return false;
+ }
+
+ // TODO: BigTiff uses version 43 instead of TIFF's 42, and header is slightly different, see
+ // http://www.awaresystems.be/imaging/tiff/bigtiff.html
+ int magic = stream.readUnsignedShort();
+
+ return magic == TIFF.TIFF_MAGIC;
+ }
+ finally {
+ stream.setByteOrder(originalOrder);
+ }
+ }
+ finally {
+ stream.reset();
+ }
+ }
+
+ public TIFFImageReader createReaderInstance(final Object pExtension) {
+ return new TIFFImageReader(this);
+ }
+
+ public String getDescription(final Locale pLocale) {
+ return "Aldus/Adobe Tagged Image File Format (TIFF) image reader";
+ }
+}
diff --git a/imageio/imageio-tiff/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi b/imageio/imageio-tiff/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi
new file mode 100755
index 00000000..be0208a5
--- /dev/null
+++ b/imageio/imageio-tiff/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi
@@ -0,0 +1 @@
+com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReaderSpi
diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoderTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoderTest.java
new file mode 100644
index 00000000..e2d8dc62
--- /dev/null
+++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoderTest.java
@@ -0,0 +1,122 @@
+package com.twelvemonkeys.imageio.plugins.tiff;/*
+ * Copyright (c) 2012, Harald Kuhr
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name "TwelveMonkeys" nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+import com.twelvemonkeys.io.enc.Decoder;
+import com.twelvemonkeys.io.enc.DecoderAbstractTestCase;
+import com.twelvemonkeys.io.enc.DecoderStream;
+import com.twelvemonkeys.io.enc.Encoder;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import static org.junit.Assert.*;
+
+/**
+ * LZWDecoderTest
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: LZWDecoderTest.java,v 1.0 08.05.12 23:44 haraldk Exp$
+ */
+public class LZWDecoderTest extends DecoderAbstractTestCase {
+
+ @Test
+ public void testIsOldBitReversedStreamTrue() throws IOException {
+ assertTrue(LZWDecoder.isOldBitReversedStream(getClass().getResourceAsStream("/lzw/lzw-short.bin")));
+ }
+
+ @Test
+ public void testIsOldBitReversedStreamFalse() throws IOException {
+ assertFalse(LZWDecoder.isOldBitReversedStream(getClass().getResourceAsStream("/lzw/lzw-long.bin")));
+ }
+
+ @Test
+ public void testShortBitReversedStream() throws IOException {
+ InputStream stream = new DecoderStream(getClass().getResourceAsStream("/lzw/lzw-short.bin"), new LZWDecoder(true), 128);
+ InputStream unpacked = new ByteArrayInputStream(new byte[512 * 3 * 5]); // Should be all 0's
+
+ assertSameStreamContents(unpacked, stream);
+ }
+
+ @Ignore("Known issue")
+ @Test
+ public void testShortBitReversedStreamLine45To49() throws IOException {
+ InputStream stream = new DecoderStream(getClass().getResourceAsStream("/lzw/lzw-short-45-49.bin"), new LZWDecoder(true), 128);
+ InputStream unpacked = getClass().getResourceAsStream("/lzw/unpacked-short-45-49.bin");
+
+ assertSameStreamContents(unpacked, stream);
+ }
+
+ @Test
+ public void testLongStream() throws IOException {
+ InputStream stream = new DecoderStream(getClass().getResourceAsStream("/lzw/lzw-long.bin"), new LZWDecoder(), 1024);
+ InputStream unpacked = getClass().getResourceAsStream("/lzw/unpacked-long.bin");
+
+ assertSameStreamContents(unpacked, stream);
+ }
+
+ private void assertSameStreamContents(InputStream expected, InputStream actual) {
+ int count = 0;
+ int data;
+
+ try {
+// long toSkip = 3800;
+// while ((toSkip -= expected.skip(toSkip)) > 0) {
+// }
+// toSkip = 3800;
+// while ((toSkip -= actual.skip(toSkip)) > 0) {
+// }
+
+ while ((data = actual.read()) != -1) {
+ count++;
+
+ assertEquals(String.format("Incorrect data at offset 0x%04x", count), expected.read(), data);
+ }
+
+ assertEquals(-1, data);
+ assertEquals(expected.read(), actual.read());
+ }
+ catch (IOException e) {
+ fail(String.format("Bad/corrupted data or EOF at offset 0x%04x: %s", count, e.getMessage()));
+ }
+ }
+
+ @Override
+ public Decoder createDecoder() {
+ return new LZWDecoder();
+ }
+
+ @Override
+ public Encoder createCompatibleEncoder() {
+ // Don't have an encoder yet
+ return null;
+ }
+}
diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java
new file mode 100644
index 00000000..ced889d5
--- /dev/null
+++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java
@@ -0,0 +1,91 @@
+package com.twelvemonkeys.imageio.plugins.tiff;/*
+ * Copyright (c) 2012, Harald Kuhr
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name "TwelveMonkeys" nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
+
+import javax.imageio.spi.ImageReaderSpi;
+import java.awt.*;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * TIFFImageReaderTest
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: TIFFImageReaderTest.java,v 1.0 08.05.12 15:25 haraldk Exp$
+ */
+public class TIFFImageReaderTest extends ImageReaderAbstractTestCase {
+
+ private static final TIFFImageReaderSpi SPI = new TIFFImageReaderSpi();
+
+ @Override
+ protected List getTestData() {
+ return Arrays.asList(
+ new TestData(getClassLoaderResource("/tiff/balloons.tif"), new Dimension(640, 480)), // RGB, uncompressed
+ new TestData(getClassLoaderResource("/tiff/sm_colors_pb.tif"), new Dimension(64, 64)), // RGB, PackBits compressed
+ new TestData(getClassLoaderResource("/tiff/sm_colors_tile.tif"), new Dimension(64, 64)), // RGB, uncompressed, tiled
+ new TestData(getClassLoaderResource("/tiff/sm_colors_pb_tile.tif"), new Dimension(64, 64)), // RGB, PackBits compressed, tiled
+ new TestData(getClassLoaderResource("/tiff/galaxy.tif"), new Dimension(965, 965)), // RGB, LZW compressed
+ new TestData(getClassLoaderResource("/tiff/bali.tif"), new Dimension(725, 489)), // Palette-based, LZW compressed
+ new TestData(getClassLoaderResource("/tiff/f14.tif"), new Dimension(640, 480)), // Gray, uncompressed
+ new TestData(getClassLoaderResource("/tiff/marbles.tif"), new Dimension(1419, 1001)), // RGB, LZW compressed w/predictor
+ new TestData(getClassLoaderResource("/tiff/chifley_logo.tif"), new Dimension(591, 177)) // CMYK, uncompressed
+ );
+ }
+
+ @Override
+ protected ImageReaderSpi createProvider() {
+ return SPI;
+ }
+
+ @Override
+ protected Class getReaderClass() {
+ return TIFFImageReader.class;
+ }
+
+ @Override
+ protected TIFFImageReader createReader() {
+ return SPI.createReaderInstance(null);
+ }
+
+ @Override
+ protected List getFormatNames() {
+ return Arrays.asList("tiff", "TIFF");
+ }
+
+ @Override
+ protected List getSuffixes() {
+ return Arrays.asList("tif", "tiff");
+ }
+
+ @Override
+ protected List getMIMETypes() {
+ return Arrays.asList("image/tiff");
+ }
+}
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/bali.tif b/imageio/imageio-tiff/src/test/resources/tiff/bali.tif
new file mode 100644
index 00000000..1ae13b79
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/bali.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/balloons.tif b/imageio/imageio-tiff/src/test/resources/tiff/balloons.tif
new file mode 100644
index 00000000..833db35d
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/balloons.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/chifley_logo.tif b/imageio/imageio-tiff/src/test/resources/tiff/chifley_logo.tif
new file mode 100644
index 00000000..90fb5eb3
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/chifley_logo.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/f14.tif b/imageio/imageio-tiff/src/test/resources/tiff/f14.tif
new file mode 100644
index 00000000..813e0c5e
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/f14.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/galaxy.tif b/imageio/imageio-tiff/src/test/resources/tiff/galaxy.tif
new file mode 100644
index 00000000..b90ac4b2
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/galaxy.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/marbles.tif b/imageio/imageio-tiff/src/test/resources/tiff/marbles.tif
new file mode 100644
index 00000000..06ccd3ec
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/marbles.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/sm_colors_pb.tif b/imageio/imageio-tiff/src/test/resources/tiff/sm_colors_pb.tif
new file mode 100644
index 00000000..29999dba
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/sm_colors_pb.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/sm_colors_pb_tile.tif b/imageio/imageio-tiff/src/test/resources/tiff/sm_colors_pb_tile.tif
new file mode 100644
index 00000000..c655f41e
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/sm_colors_pb_tile.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/sm_colors_tile.tif b/imageio/imageio-tiff/src/test/resources/tiff/sm_colors_tile.tif
new file mode 100644
index 00000000..a6bc40d5
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/sm_colors_tile.tif differ
diff --git a/imageio/pom.xml b/imageio/pom.xml
index fbfb4444..f91ba124 100644
--- a/imageio/pom.xml
+++ b/imageio/pom.xml
@@ -10,7 +10,6 @@
4.0.0
com.twelvemonkeys.imageio
imageio
- 3.0-SNAPSHOT
pom
TwelveMonkeys :: ImageIO
@@ -39,6 +38,7 @@
imageio-pict
imageio-psd
imageio-thumbsdb
+ imageio-tiff
imageio-batik