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 @@
+
+
+
+
+
+
+
+]>