diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStream.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStream.java new file mode 100644 index 00000000..cdb895f9 --- /dev/null +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStream.java @@ -0,0 +1,452 @@ +/* + * 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.lang.Validate; + +import java.io.EOFException; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * CCITT Modified Huffman RLE, Group 3 (T4) and Group 4 (T6) fax compression. + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: CCITTFaxDecoderStream.java,v 1.0 23.05.12 15:55 haraldk Exp$ + */ +final class CCITTFaxDecoderStream extends FilterInputStream { + // See TIFF 6.0 Specification, Section 10: "Modified Huffman Compression", page 43. + + private final int columns; + private final byte[] decodedRow; + + private int decodedLength; + private int decodedPos; + + private int bitBuffer; + private int bitBufferLength; + + // Need to take fill order into account (?) (use flip table?) + private final int fillOrder; + private final int type; + + private final int[] changes; + private int changesCount; + + private static final int EOL_CODE = 0x01; // 12 bit + + public CCITTFaxDecoderStream(final InputStream stream, final int columns, final int type, final int fillOrder) { + super(Validate.notNull(stream, "stream")); + + this.columns = Validate.isTrue(columns > 0, columns, "width must be greater than 0"); + // We know this is only used for b/w (1 bit) + this.decodedRow = new byte[(columns + 7) / 8]; + this.type = Validate.isTrue(type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, type, "Only CCITT Modified Huffman RLE compression (2) supported: %s"); // TODO: Implement group 3 and 4 + this.fillOrder = Validate.isTrue(fillOrder == 1, fillOrder, "Only fill order 1 supported: %s"); // TODO: Implement fillOrder == 2 + + this.changes = new int[columns]; + } + + // IDEA: Would it be faster to keep all bit combos of each length (>=2) that is NOT a code, to find bit length, then look up value in table? + // -- If white run, start at 4 bits to determine length, if black, start at 2 bits + + private void fetch() throws IOException { + if (decodedPos >= decodedLength) { + decodedLength = 0; + try { + decodeRow(); + } + catch (EOFException e) { + // TODO: Rewrite to avoid throw/catch for normal flow... + if (decodedLength != 0) { + throw e; + } + + // ..otherwise, just client code trying to read past the end of stream + decodedLength = -1; + } + + decodedPos = 0; + } + } + + private void decodeRow() throws IOException { + resetBuffer(); + + boolean literalRun = true; + + /* + if (type == TIFFExtension.COMPRESSION_CCITT_T4) { + int eol = readBits(12); + System.err.println("eol: " + eol); + while (eol != EOL_CODE) { + eol = readBits(1); + System.err.println("eol: " + eol); +// throw new IOException("Missing EOL"); + } + + literalRun = readBits(1) == 1; + } + + System.err.println("literalRun: " + literalRun); + */ + int index = 0; + + if (literalRun) { + changesCount = 0; + boolean white = true; + + do { + int completeRun = 0; + + int run; + do { + if (white) { + run = decodeRun(WHITE_CODES, WHITE_RUN_LENGTHS, 4); + } + else { + run = decodeRun(BLACK_CODES, BLACK_RUN_LENGTHS, 2); + } + + completeRun += run; + } + while (run >= 64); // Additional makeup codes are packed into both b/w codes, terminating codes are < 64 bytes + + changes[changesCount++] = index + completeRun; + +// System.err.printf("%s run: %d\n", white ? "white" : "black", run); + + // TODO: Optimize with lookup for 0-7 bits? + // Fill bits to byte boundary... + while (index % 8 != 0 && completeRun-- > 0) { + decodedRow[index++ / 8] |= (white ? 1 << 8 - (index % 8) : 0); + } + + // ...then fill complete bytes to either 0xff or 0x00... + if (index % 8 == 0) { + final byte value = (byte) (white ? 0xff : 0x00); + + while (completeRun > 7) { + decodedRow[index / 8] = value; + completeRun -= 8; + index += 8; + } + } + + // ...finally fill any remaining bits + while (completeRun-- > 0) { + decodedRow[index++ / 8] |= (white ? 1 << 8 - (index % 8) : 0); + } + + // Flip color for next run + white = !white; + } + while (index < columns); + } + else { + // non-literal run + } + + if (type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE && index != columns) { + throw new IOException("Sum of run-lengths does not equal scan line width: " + index + " > " + columns); + } + + decodedLength = (index / 8) + 1; + } + + private int decodeRun(short[][] codes, short[][] runLengths, int minCodeSize) throws IOException { + // TODO: Optimize... + // Looping and comparing is the most straight-forward, but probably not the most effective way... + int code = readBits(minCodeSize); + + for (int bits = 0; bits < codes.length; bits++) { + short[] bitCodes = codes[bits]; + + for (int i = 0; i < bitCodes.length; i++) { + if (bitCodes[i] == code) { +// System.err.println("code: " + code); + + // Code found, return matching run length + return runLengths[bits][i]; + } + } + + // No code found, read one more bit and try again + code = fillOrder == 1 ? (code << 1) | readBits(1) : readBits(1) << (bits + minCodeSize) | code; + } + + throw new IOException("Unknown code in Huffman RLE stream"); + } + + private void resetBuffer() { + for (int i = 0; i < decodedRow.length; i++) { + decodedRow[i] = 0; + } + + bitBuffer = 0; + bitBufferLength = 0; + } + + private int readBits(int bitCount) throws IOException { + while (bitBufferLength < bitCount) { + int read = in.read(); + if (read == -1) { + throw new EOFException("Unexpected end of Huffman RLE stream"); + } + + int bits = read & 0xff; + bitBuffer = (bitBuffer << 8) | bits; + bitBufferLength += 8; + } + + // TODO: Take fill order into account + bitBufferLength -= bitCount; + int result = bitBuffer >> bitBufferLength; + bitBuffer &= (1 << bitBufferLength) - 1; + + return result; + } + + @Override + public int read() throws IOException { + if (decodedLength < 0) { + return -1; + } + + if (decodedPos >= decodedLength) { + fetch(); + + if (decodedLength < 0) { + return -1; + } + } + + return decodedRow[decodedPos++] & 0xff; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (decodedLength < 0) { + return -1; + } + + if (decodedPos >= decodedLength) { + fetch(); + + if (decodedLength < 0) { + return -1; + } + } + + int read = Math.min(decodedLength - decodedPos, len); + System.arraycopy(decodedRow, decodedPos, b, off, read); + decodedPos += read; + + return read; + } + + @Override + public long skip(long n) throws IOException { + if (decodedLength < 0) { + return -1; + } + + if (decodedPos >= decodedLength) { + fetch(); + + if (decodedLength < 0) { + return -1; + } + } + + int skipped = (int) Math.min(decodedLength - decodedPos, n); + decodedPos += skipped; + + return skipped; + } + + @Override + public boolean markSupported() { + return false; + } + + @Override + public synchronized void reset() throws IOException { + throw new IOException("mark/reset not supported"); + } + + static final short[][] BLACK_CODES = { + { // 2 bits + 0x2, 0x3, + }, + { // 3 bits + 0x2, 0x3, + }, + { // 4 bits + 0x2, 0x3, + }, + { // 5 bits + 0x3, + }, + { // 6 bits + 0x4, 0x5, + }, + { // 7 bits + 0x4, 0x5, 0x7, + }, + { // 8 bits + 0x4, 0x7, + }, + { // 9 bits + 0x18, + }, + { // 10 bits + 0x17, 0x18, 0x37, 0x8, 0xf, + }, + { // 11 bits + 0x17, 0x18, 0x28, 0x37, 0x67, 0x68, 0x6c, 0x8, 0xc, 0xd, + }, + { // 12 bits + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1c, 0x1d, 0x1e, 0x1f, 0x24, 0x27, 0x28, 0x2b, 0x2c, 0x33, + 0x34, 0x35, 0x37, 0x38, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x64, 0x65, + 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xd2, 0xd3, + 0xd4, 0xd5, 0xd6, 0xd7, 0xda, 0xdb, + }, + { // 13 bits + 0x4a, 0x4b, 0x4c, 0x4d, 0x52, 0x53, 0x54, 0x55, 0x5a, 0x5b, 0x64, 0x65, 0x6c, 0x6d, 0x72, 0x73, + 0x74, 0x75, 0x76, 0x77, + } + }; + static final short[][] BLACK_RUN_LENGTHS = { + { // 2 bits + 3, 2, + }, + { // 3 bits + 1, 4, + }, + { // 4 bits + 6, 5, + }, + { // 5 bits + 7, + }, + { // 6 bits + 9, 8, + }, + { // 7 bits + 10, 11, 12, + }, + { // 8 bits + 13, 14, + }, + { // 9 bits + 15, + }, + { // 10 bits + 16, 17, 0, 18, 64, + }, + { // 11 bits + 24, 25, 23, 22, 19, 20, 21, 1792, 1856, 1920, + }, + { // 12 bits + 1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560, 52, 55, 56, 59, 60, 320, + 384, 448, 53, 54, 50, 51, 44, 45, 46, 47, 57, 58, 61, 256, 48, 49, + 62, 63, 30, 31, 32, 33, 40, 41, 128, 192, 26, 27, 28, 29, 34, 35, + 36, 37, 38, 39, 42, 43, + }, + { // 13 bits + 640, 704, 768, 832, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 512, 576, 896, 960, + 1024, 1088, 1152, 1216, + } + }; + + public static final short[][] WHITE_CODES = { + { // 4 bits + 0x7, 0x8, 0xb, 0xc, 0xe, 0xf, + }, + { // 5 bits + 0x12, 0x13, 0x14, 0x1b, 0x7, 0x8, + }, + { // 6 bits + 0x17, 0x18, 0x2a, 0x2b, 0x3, 0x34, 0x35, 0x7, 0x8, + }, + { // 7 bits + 0x13, 0x17, 0x18, 0x24, 0x27, 0x28, 0x2b, 0x3, 0x37, 0x4, 0x8, 0xc, + }, + { // 8 bits + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1a, 0x1b, 0x2, 0x24, 0x25, 0x28, 0x29, 0x2a, 0x2b, 0x2c, + 0x2d, 0x3, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x4, 0x4a, 0x4b, 0x5, 0x52, 0x53, 0x54, 0x55, + 0x58, 0x59, 0x5a, 0x5b, 0x64, 0x65, 0x67, 0x68, 0xa, 0xb, + }, + { // 9 bits + 0x98, 0x99, 0x9a, 0x9b, 0xcc, 0xcd, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, + }, + { // 10 bits + }, + { // 11 bits + 0x8, 0xc, 0xd, + }, + { // 12 bits + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1c, 0x1d, 0x1e, 0x1f, + } + }; + + public static final short[][] WHITE_RUN_LENGTHS = { + { // 4 bits + 2, 3, 4, 5, 6, 7, + }, + { // 5 bits + 128, 8, 9, 64, 10, 11, + }, + { // 6 bits + 192, 1664, 16, 17, 13, 14, 15, 1, 12, + }, + { // 7 bits + 26, 21, 28, 27, 18, 24, 25, 22, 256, 23, 20, 19, + }, + { // 8 bits + 33, 34, 35, 36, 37, 38, 31, 32, 29, 53, 54, 39, 40, 41, 42, 43, + 44, 30, 61, 62, 63, 0, 320, 384, 45, 59, 60, 46, 49, 50, 51, + 52, 55, 56, 57, 58, 448, 512, 640, 576, 47, 48, + }, + { // 9 bits + 1472, 1536, 1600, 1728, 704, 768, 832, 896, 960, 1024, 1088, 1152, 1216, 1280, 1344, 1408, + }, + { // 10 bits + }, + { // 11 bits + 1792, 1856, 1920, + }, + { // 12 bits + 1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560, + } + }; +} diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/G31DDecoder.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/G31DDecoder.java deleted file mode 100644 index cb02dd9b..00000000 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/G31DDecoder.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.Decoder; - -import java.io.IOException; -import java.io.InputStream; - -/** - * CCITT Group 3 One-Dimensional (G31D) "No EOLs" Decoder. - * - * @author Harald Kuhr - * @author last modified by $Author: haraldk$ - * @version $Id: G31DDecoder.java,v 1.0 23.05.12 15:55 haraldk Exp$ - */ -final class G31DDecoder implements Decoder { - public int decode(final InputStream stream, final byte[] buffer) throws IOException { - throw new UnsupportedOperationException("Method decode not implemented"); // TODO: Implement - } -} diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFBaseline.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFBaseline.java index 3cc3e5f6..1f76d655 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFBaseline.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFBaseline.java @@ -37,7 +37,7 @@ package com.twelvemonkeys.imageio.plugins.tiff; */ interface TIFFBaseline { int COMPRESSION_NONE = 1; - int COMPRESSION_CCITT_HUFFMAN = 2; + int COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE = 2; int COMPRESSION_PACKBITS = 32773; int PHOTOMETRIC_WHITE_IS_ZERO = 0; diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFCustom.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFCustom.java index 5f452b57..d72b489d 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFCustom.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFCustom.java @@ -49,7 +49,7 @@ interface TIFFCustom { int COMPRESSION_JBIG = 34661; int COMPRESSION_SGILOG = 34676; int COMPRESSION_SGILOG24 = 34677; - int COMPRESSION_JP2000 = 34712; + int COMPRESSION_JPEG2000 = 34712; int PHOTOMETRIC_LOGL = 32844; int PHOTOMETRIC_LOGLUV = 32845; 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 index b6f57a39..87a6efaf 100644 --- 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 @@ -106,16 +106,16 @@ public class TIFFImageReader extends ImageReaderBase { // TODO: Source region (*tests should be failing*) // TODO: TIFFImageWriter + Spi + // TODOs Full BaseLine support: + // TODO: Support ExtraSamples (an array, if multiple extra samples!) + // (0: Unspecified (not alpha), 1: Associated Alpha (pre-multiplied), 2: Unassociated Alpha (non-multiplied) + // TODOs ImageIO advanced functionality: // TODO: Implement readAsRenderedImage to allow tiled renderImage? // For some layouts, we could do reads super-fast with a memory mapped buffer. // TODO: Implement readAsRaster directly // TODO: IIOMetadata (stay close to Sun's TIFF metadata) - - // TODOs Full BaseLine support: - // TODO: Support ExtraSamples (an array, if multiple extra samples!) - // (0: Unspecified (not alpha), 1: Associated Alpha (pre-multiplied), 2: Unassociated Alpha (non-multiplied) - // TODO: Support Compression 2 (CCITT Modified Huffman) for bi-level images + // http://download.java.net/media/jai-imageio/javadoc/1.1/com/sun/media/imageio/plugins/tiff/package-summary.html#ImageMetadata // TODOs Extension support // TODO: Support PlanarConfiguration 2 @@ -127,6 +127,7 @@ public class TIFFImageReader extends ImageReaderBase { // DONE: // Handle SampleFormat (and give up if not == 1) // Support Compression 6 ('Old-style' JPEG) + // Support Compression 2 (CCITT Modified Huffman RLE) for bi-level images final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.tiff.debug")); @@ -175,7 +176,7 @@ public class TIFFImageReader extends ImageReaderBase { return IFDs.directoryCount(); } - private int getValueAsIntWithDefault(final int tag, String tagName, Integer defaultValue) throws IIOException { + private Number getValueAsNumberWithDefault(final int tag, final String tagName, final Number defaultValue) throws IIOException { Entry entry = currentIFD.getEntryById(tag); if (entry == null) { @@ -186,7 +187,19 @@ public class TIFFImageReader extends ImageReaderBase { throw new IIOException("Missing TIFF tag: " + (tagName != null ? tagName : tag)); } - return ((Number) entry.getValue()).intValue(); + return (Number) entry.getValue(); + } + + private long getValueAsLongWithDefault(final int tag, final String tagName, final Long defaultValue) throws IIOException { + return getValueAsNumberWithDefault(tag, tagName, defaultValue).longValue(); + } + + private long getValueAsLongWithDefault(final int tag, final Long defaultValue) throws IIOException { + return getValueAsLongWithDefault(tag, null, defaultValue); + } + + private int getValueAsIntWithDefault(final int tag, final String tagName, final Integer defaultValue) throws IIOException { + return getValueAsNumberWithDefault(tag, tagName, defaultValue).intValue(); } private int getValueAsIntWithDefault(final int tag, Integer defaultValue) throws IIOException { @@ -364,7 +377,6 @@ public class TIFFImageReader extends ImageReaderBase { 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); @@ -464,7 +476,9 @@ public class TIFFImageReader extends ImageReaderBase { // 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 rowsPerStrip = getValueAsLongWithDefault(TIFF.TAG_ROWS_PER_STRIP, (1l << 32) - 1); + int stripTileHeight = rowsPerStrip < height ? (int) rowsPerStrip : height; + long[] stripTileOffsets = getValueAsLongArray(TIFF.TAG_TILE_OFFSETS, "TileOffsets", false); long[] stripTileByteCounts; @@ -507,6 +521,13 @@ public class TIFFImageReader extends ImageReaderBase { // LZW case TIFFExtension.COMPRESSION_ZLIB: // 'Adobe-style' Deflate + case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE: + // 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 int[] yCbCrSubsampling = null; int yCbCrPos = 1; @@ -585,7 +606,8 @@ public class TIFFImageReader extends ImageReaderBase { ? IIOUtil.createStreamAdapter(imageInput, stripTileByteCounts[i]) : IIOUtil.createStreamAdapter(imageInput); - adapter = createDecoderInputStream(compression, adapter); + adapter = createDecompressorStream(compression, width, adapter); + adapter = createUnpredictorStream(predictor, width, planarConfiguration == 2 ? 1 : raster.getNumBands(), getBitsPerSample(), adapter, imageInput.getByteOrder()); if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR) { adapter = new YCbCrUpsamplerStream(adapter, yCbCrSubsampling, yCbCrPos, colsInTile, yCbCrCoefficients); @@ -598,7 +620,7 @@ public class TIFFImageReader extends ImageReaderBase { } // Read a full strip/tile - readStripTileData(rowRaster, interpretation, predictor, raster, numBands, col, row, colsInTile, rowsInTile, input); + readStripTileData(rowRaster, interpretation, raster, col, row, colsInTile, rowsInTile, input); if (abortRequested()) { break; @@ -917,14 +939,28 @@ public class TIFFImageReader extends ImageReaderBase { 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 + // Known, but unsupported compression types + case TIFFCustom.COMPRESSION_NEXT: + case TIFFCustom.COMPRESSION_CCITTRLEW: + case TIFFCustom.COMPRESSION_THUNDERSCAN: + case TIFFCustom.COMPRESSION_IT8CTPAD: + case TIFFCustom.COMPRESSION_IT8LW: + case TIFFCustom.COMPRESSION_IT8MP: + case TIFFCustom.COMPRESSION_IT8BL: + case TIFFCustom.COMPRESSION_PIXARFILM: + case TIFFCustom.COMPRESSION_PIXARLOG: + case TIFFCustom.COMPRESSION_DCS: + case TIFFCustom.COMPRESSION_JBIG: // Doable with JBIG plugin? + case TIFFCustom.COMPRESSION_SGILOG: + case TIFFCustom.COMPRESSION_SGILOG24: + case TIFFCustom.COMPRESSION_JPEG2000: // Doable with JPEG2000 plugin? + throw new IIOException("Unsupported TIFF Compression value: " + compression); default: throw new IIOException("Unknown TIFF Compression value: " + compression); @@ -1004,8 +1040,8 @@ public class TIFFImageReader extends ImageReaderBase { return stream.createInputStream(); } - private void readStripTileData(final WritableRaster rowRaster, final int interpretation, final int predictor, - final WritableRaster raster, final int numBands, final int col, final int startRow, + private void readStripTileData(final WritableRaster rowRaster, final int interpretation, + final WritableRaster raster, final int col, final int startRow, final int colsInStrip, final int rowsInStrip, final DataInput input) throws IOException { switch (rowRaster.getTransferType()) { @@ -1020,8 +1056,6 @@ public class TIFFImageReader extends ImageReaderBase { } input.readFully(rowData); - - unPredict(predictor, colsInStrip, 1, numBands, rowData); normalizeBlack(interpretation, rowData); if (colsInStrip == rowRaster.getWidth() && col + colsInStrip <= raster.getWidth()) { @@ -1048,7 +1082,6 @@ public class TIFFImageReader extends ImageReaderBase { rowDataShort[k] = input.readShort(); } - unPredict(predictor, colsInStrip, 1, numBands, rowDataShort); normalizeBlack(interpretation, rowDataShort); if (colsInStrip == rowRaster.getWidth() && col + colsInStrip <= raster.getWidth()) { @@ -1075,7 +1108,6 @@ public class TIFFImageReader extends ImageReaderBase { rowDataInt[k] = input.readInt(); } - unPredict(predictor, colsInStrip, 1, numBands, rowDataInt); normalizeBlack(interpretation, rowDataInt); if (colsInStrip == rowRaster.getWidth() && col + colsInStrip <= raster.getWidth()) { @@ -1118,61 +1150,7 @@ public class TIFFImageReader extends ImageReaderBase { } } - @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 TIFFBaseline.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 TIFFBaseline.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 TIFFBaseline.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 { + private InputStream createDecompressorStream(final int compression, final int width, final InputStream stream) throws IOException { switch (compression) { case TIFFBaseline.COMPRESSION_NONE: return stream; @@ -1181,14 +1159,31 @@ public class TIFFImageReader extends ImageReaderBase { case TIFFExtension.COMPRESSION_LZW: return new DecoderStream(stream, LZWDecoder.create(LZWDecoder.isOldBitReversedStream(stream)), 1024); case TIFFExtension.COMPRESSION_ZLIB: - case TIFFExtension.COMPRESSION_DEFLATE: // TIFFphotoshop.pdf (aka TIFF specification, supplement 2) says ZLIB (8) and DEFLATE (32946) algorithms are identical + case TIFFExtension.COMPRESSION_DEFLATE: return new InflaterInputStream(stream, new Inflater(), 1024); + case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE: + case TIFFExtension.COMPRESSION_CCITT_T4: + case TIFFExtension.COMPRESSION_CCITT_T6: + return new CCITTFaxDecoderStream(stream, width, compression, getValueAsIntWithDefault(TIFF.TAG_FILL_ORDER, 1)); default: throw new IllegalArgumentException("Unsupported TIFF compression: " + compression); } } + private InputStream createUnpredictorStream(final int predictor, final int width, final int samplesPerPixel, final int bitsPerSample, final InputStream stream, final ByteOrder byteOrder) throws IOException { + switch (predictor) { + case TIFFBaseline.PREDICTOR_NONE: + return stream; + case TIFFExtension.PREDICTOR_HORIZONTAL_DIFFERENCING: + return new HorizontalDeDifferencingStream(stream, width, samplesPerPixel, bitsPerSample, byteOrder); + case TIFFExtension.PREDICTOR_HORIZONTAL_FLOATINGPOINT: + throw new IIOException("Unsupported TIFF Predictor value: " + predictor); + default: + throw new IIOException("Unknown TIFF Predictor value: " + predictor); + } + } + private long[] getValueAsLongArray(final int tag, final String tagName, boolean required) throws IIOException { Entry entry = currentIFD.getEntryById(tag); if (entry == null) { diff --git a/imageio/imageio-tiff/src/main/resources/tiff-image-metadata-sun.dtd b/imageio/imageio-tiff/src/main/resources/tiff-image-metadata-sun.dtd new file mode 100644 index 00000000..74451957 --- /dev/null +++ b/imageio/imageio-tiff/src/main/resources/tiff-image-metadata-sun.dtd @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]> \ No newline at end of file diff --git a/imageio/imageio-tiff/src/main/resources/tiff-stream-metadata-sun.dtd b/imageio/imageio-tiff/src/main/resources/tiff-stream-metadata-sun.dtd new file mode 100644 index 00000000..862a0fc0 --- /dev/null +++ b/imageio/imageio-tiff/src/main/resources/tiff-stream-metadata-sun.dtd @@ -0,0 +1,9 @@ + + + + + + + +]>