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