From a2042e75bf46a0337a90da177eb5507b3680eb61 Mon Sep 17 00:00:00 2001 From: Schmidor Date: Wed, 1 Jul 2015 01:10:56 +0200 Subject: [PATCH 1/6] CITT Group 3/4 Support - Pass Mode / 2D Change referencing in work --- .../imageio/metadata/exif/TIFF.java | 3 + .../plugins/tiff/CCITTFaxDecoderStream.java | 893 +++++++++++------- .../imageio/plugins/tiff/TIFFExtension.java | 6 +- .../imageio/plugins/tiff/TIFFImageReader.java | 14 +- .../tiff/CCITTFaxDecoderStreamTest.java | 219 +++-- 5 files changed, 687 insertions(+), 448 deletions(-) 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 e7215b1f..606b0849 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 @@ -139,6 +139,9 @@ public interface TIFF { // "Old-style" JPEG (still used as EXIF thumbnail) int TAG_JPEG_INTERCHANGE_FORMAT = 513; int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = 514; + + int TAG_GROUP3OPTIONS = 292; + int TAG_GROUP4OPTIONS = 293; /// C. Tags relating to image data characteristics 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 index 929cc8da..5eb9479c 100644 --- 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 @@ -28,426 +28,623 @@ 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; +import com.twelvemonkeys.lang.Validate; + /** - * CCITT Modified Huffman RLE. - * + * 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. + // See TIFF 6.0 Specification, Section 10: "Modified Huffman Compression", + // page 43. - private final int columns; - private final byte[] decodedRow; + private final int columns; + private final byte[] decodedRow; - private int decodedLength; - private int decodedPos; + 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; - // Need to take fill order into account (?) (use flip table?) - private final int fillOrder; - private final int type; + private final int[] changesReferenceRow; + private final int[] changesCurrentRow; + private int changesReferenceRowCount; + private int changesCurrentRowCount; - private final int[] changes; - private int changesCount; + private static final int EOL_CODE = 0x01; // 12 bit - private static final int EOL_CODE = 0x01; // 12 bit + private boolean optionG32D = false; - public CCITTFaxDecoderStream(final InputStream stream, final int columns, final int type, final int fillOrder) { - super(Validate.notNull(stream, "stream")); + @SuppressWarnings("unused") // Leading zeros for aligning EOL + private boolean optionG3Fill = false; - 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 + private boolean optionUncompressed = false; - this.changes = new int[columns]; - } + public CCITTFaxDecoderStream(final InputStream stream, final int columns, final int type, final int fillOrder, + final long options) { + super(Validate.notNull(stream, "stream")); - // 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 + 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 = type; + this.fillOrder = fillOrder;// Validate.isTrue(fillOrder == 1, fillOrder, + // "Only fill order 1 supported: %s"); // + // TODO: Implement fillOrder == 2 - private void fetch() throws IOException { - if (decodedPos >= decodedLength) { - decodedLength = 0; + this.changesReferenceRow = new int[columns]; + this.changesCurrentRow = new int[columns]; - try { - decodeRow(); - } - catch (EOFException e) { - // TODO: Rewrite to avoid throw/catch for normal flow... - if (decodedLength != 0) { - throw e; - } + switch (type) { + case TIFFExtension.COMPRESSION_CCITT_T4: + optionG32D = (options & TIFFExtension.GROUP3OPT_2DENCODING) != 0; + optionG3Fill = (options & TIFFExtension.GROUP3OPT_FILLBITS) != 0; + optionUncompressed = (options & TIFFExtension.GROUP3OPT_UNCOMPRESSED) != 0; + break; + case TIFFExtension.COMPRESSION_CCITT_T6: + optionUncompressed = (options & TIFFExtension.GROUP4OPT_UNCOMPRESSED) != 0; + break; + } - // ..otherwise, just client code trying to read past the end of stream - decodedLength = -1; - } + Validate.isTrue(!optionUncompressed, optionUncompressed, + "CCITT GROUP 3/4 OPTION UNCOMPRESSED is not supported"); + } - decodedPos = 0; - } - } + private void fetch() throws IOException { + if (decodedPos >= decodedLength) { + decodedLength = 0; - private void decodeRow() throws IOException { - resetBuffer(); + try { + decodeRow(); + } catch (EOFException e) { + // TODO: Rewrite to avoid throw/catch for normal flow... + if (decodedLength != 0) { + throw e; + } - boolean literalRun = true; + // ..otherwise, just client code trying to read past the end of + // stream + decodedLength = -1; + } - /* - 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"); - } + decodedPos = 0; + } + } - literalRun = readBits(1) == 1; - } + private void decode1D() throws IOException { + int index = 0; + boolean white = true; + changesCurrentRowCount = 0; + do { + int completeRun = 0; + if (white) { + completeRun = decodeRun(whiteRunTree); + } else { + completeRun = decodeRun(blackRunTree); + } - System.err.println("literalRun: " + literalRun); - */ - int index = 0; + index += completeRun; + changesCurrentRow[changesCurrentRowCount++] = index; + // Flip color for next run + white = !white; + } while (index < columns); + } - if (literalRun) { - changesCount = 0; - boolean white = true; + private void decode2D() throws IOException { + boolean white = true; + int index = 0; + changesCurrentRowCount = 0; + int ref = 0; + mode: while (index < columns) { + // read mode + N n = codeTree.root; + while (true) { + n = n.walk(readBit()); + if (n == null) { + continue mode; + } else if (n.isLeaf) { + switch (n.value) { + case VALUE_HMODE: + System.out.print("|H="); + int runLength = 0; + runLength = decodeRun(white ? whiteRunTree : blackRunTree); + changesCurrentRow[changesCurrentRowCount++] = index; + index += runLength; + System.out.print(runLength + (white? "W" : "B")); + runLength = decodeRun(white ? blackRunTree : whiteRunTree); + changesCurrentRow[changesCurrentRowCount++] = index; + index += runLength; + System.out.print(runLength + (!white? "W" : "B")); + break; + case VALUE_PASSMODE: + System.out.print("|P"); + ref++; + // TODO + break; + default: + System.out.print("|V" + n.value); + index = changesReferenceRow[ref] + n.value; + changesCurrentRow[ref] = index; + if(changesCurrentRow[ref] <= index) ref++; //TODO + changesCurrentRowCount++; + white = !white; + break; + } + continue mode; + } + } + } + } - do { - int completeRun = 0; + private void decodeRowType2() throws IOException { + resetBuffer(); + decode1D(); + } - int run; - do { - if (white) { - run = decodeRun(WHITE_CODES, WHITE_RUN_LENGTHS, 4); - } - else { - run = decodeRun(BLACK_CODES, BLACK_RUN_LENGTHS, 2); - } + private void decodeRowType4() throws IOException { + eof: while (true) { + // read till next EOL code + N n = eolOnlyTree.root; + while (true) { + N tmp = n; + n = n.walk(readBit()); + if (n == null) + continue eof; + if (n.isLeaf) { + System.out.print("|EOL"); + break eof; + } + if(tmp == n) System.out.print("F"); + } + } + boolean k = optionG32D ? readBit() : true; + System.out.print("|k=" + k); + if (k) { + decode1D(); + changesReferenceRowCount = changesCurrentRowCount; + System.arraycopy(changesCurrentRow, 0, changesReferenceRow, 0, changesCurrentRowCount); + } else { + decode2D(); + } + } - completeRun += run; - } - while (run >= 64); // Additional makeup codes are packed into both b/w codes, terminating codes are < 64 bytes + private void decodeRowType6() throws IOException { + changesReferenceRowCount = 1; + changesReferenceRow[0] = columns; + decode2D(); + } - changes[changesCount++] = index + completeRun; + private void decodeRow() throws IOException { + switch (type) { + case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE: + decodeRowType2(); + break; + case TIFFExtension.COMPRESSION_CCITT_T4: + decodeRowType4(); + break; + case TIFFExtension.COMPRESSION_CCITT_T6: + decodeRowType6(); + break; + } + int index = 0; + boolean white = true; + for (int i = 0; i <= changesCurrentRowCount; i++) { + int nextChange = columns; + if (i != changesCurrentRowCount) { + nextChange = changesCurrentRow[i]; + } -// System.err.printf("%s run: %d\n", white ? "white" : "black", run); + while (index % 8 != 0 && (nextChange - index) > 0) { + decodedRow[index++ / 8] |= (white ? 1 << 8 - (index % 8) : 0); + } - // 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); - } + if (index % 8 == 0) { + final byte value = (byte) (white ? 0xff : 0x00); - // ...then fill complete bytes to either 0xff or 0x00... - if (index % 8 == 0) { - final byte value = (byte) (white ? 0xff : 0x00); + while ((nextChange - index) > 7) { + decodedRow[index / 8] = value; + index += 8; + } + } + while ((nextChange - index) > 0) { + if (index % 8 == 0) + decodedRow[(index + 1) / 8] = 0; - while (completeRun > 7) { - decodedRow[index / 8] = value; - completeRun -= 8; - index += 8; - } - } + decodedRow[index++ / 8] |= (white ? 1 << 8 - (index % 8) : 0); + } - // ...finally fill any remaining bits - while (completeRun-- > 0) { - decodedRow[index++ / 8] |= (white ? 1 << 8 - (index % 8) : 0); - } + white = !white; + } - // Flip color for next run - white = !white; - } - while (index < columns); - } - else { - // non-literal run - } + if (index != columns) { + throw new IOException("Sum of run-lengths does not equal scan line width: " + index + " > " + columns); + } - 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 + 7) / 8; + } - decodedLength = (index + 7) / 8; - } + private int decodeRun(Tree tree) throws IOException { + int total = 0; - 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); + N n = tree.root; + while (true) { + boolean bit = readBit(); + n = n.walk(bit); + if (n == null) + throw new IOException("Unknown code in Huffman RLE stream"); - for (int bits = 0; bits < codes.length; bits++) { - short[] bitCodes = codes[bits]; + if (n.isLeaf) { + total += n.value; + if (n.value < 64) { + return total; + } else { + n = tree.root; + continue; + } + } + } + } - for (int i = 0; i < bitCodes.length; i++) { - if (bitCodes[i] == code) { -// System.err.println("code: " + code); + private void resetBuffer() { + for (int i = 0; i < decodedRow.length; i++) { + decodedRow[i] = 0; + } + while (true) { + if (bufferPos == -1) { + return; + } - // Code found, return matching run length - return runLengths[bits][i]; - } - } + try { + boolean skip = readBit(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } - // No code found, read one more bit and try again - code = fillOrder == 1 ? (code << 1) | readBits(1) : readBits(1) << (bits + minCodeSize) | code; - } + int buffer = -1; + int bufferPos = -1; - throw new IOException("Unknown code in Huffman RLE stream"); - } + private boolean readBit() throws IOException { + if (bufferPos < 0 || bufferPos > 7) { + buffer = in.read(); + if (buffer == -1) { + throw new EOFException("Unexpected end of Huffman RLE stream"); + } + bufferPos = 0; + } - private void resetBuffer() { - for (int i = 0; i < decodedRow.length; i++) { - decodedRow[i] = 0; - } + boolean isSet = ((buffer >> (7 - bufferPos)) & 1) == 1; + bufferPos++; + if (bufferPos > 7) + bufferPos = -1; + return isSet; + } - bitBuffer = 0; - bitBufferLength = 0; - } + @Override + public int read() throws IOException { + if (decodedLength < 0) { + return -1; + } - 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"); - } + if (decodedPos >= decodedLength) { + fetch(); - int bits = read & 0xff; - bitBuffer = (bitBuffer << 8) | bits; - bitBufferLength += 8; - } + if (decodedLength < 0) { + return -1; + } + } - // TODO: Take fill order into account - bitBufferLength -= bitCount; - int result = bitBuffer >> bitBufferLength; - bitBuffer &= (1 << bitBufferLength) - 1; + return decodedRow[decodedPos++] & 0xff; + } - return result; - } + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (decodedLength < 0) { + return -1; + } - @Override - public int read() throws IOException { - if (decodedLength < 0) { - return -1; - } + if (decodedPos >= decodedLength) { + fetch(); - if (decodedPos >= decodedLength) { - fetch(); + if (decodedLength < 0) { + return -1; + } + } - if (decodedLength < 0) { - return -1; - } - } + int read = Math.min(decodedLength - decodedPos, len); + System.arraycopy(decodedRow, decodedPos, b, off, read); + decodedPos += read; - return decodedRow[decodedPos++] & 0xff; - } + return read; + } - @Override - public int read(byte[] b, int off, int len) throws IOException { - if (decodedLength < 0) { - return -1; - } + @Override + public long skip(long n) throws IOException { + if (decodedLength < 0) { + return -1; + } - if (decodedPos >= decodedLength) { - fetch(); + if (decodedPos >= decodedLength) { + fetch(); - if (decodedLength < 0) { - return -1; - } - } + if (decodedLength < 0) { + return -1; + } + } - int read = Math.min(decodedLength - decodedPos, len); - System.arraycopy(decodedRow, decodedPos, b, off, read); - decodedPos += read; + int skipped = (int) Math.min(decodedLength - decodedPos, n); + decodedPos += skipped; - return read; - } + return skipped; + } - @Override - public long skip(long n) throws IOException { - if (decodedLength < 0) { - return -1; - } + @Override + public boolean markSupported() { + return false; + } - if (decodedPos >= decodedLength) { - fetch(); + @Override + public synchronized void reset() throws IOException { + throw new IOException("mark/reset not supported"); + } - if (decodedLength < 0) { - return -1; - } - } + static class N { + N left; + N right; - int skipped = (int) Math.min(decodedLength - decodedPos, n); - decodedPos += skipped; + int value; // > 63 non term. + boolean canBeFill = false; + boolean isLeaf = false; - return skipped; - } + void set(boolean next, N node) { + if (!next) { + left = node; + } else { + right = node; + } + } - @Override - public boolean markSupported() { - return false; - } + N walk(boolean next) { + return next ? right : left; + } - @Override - public synchronized void reset() throws IOException { - throw new IOException("mark/reset not supported"); - } + @Override + public String toString() { + return "[leaf=" + isLeaf + ", value=" + value + ", canBeFill=" + canBeFill + "]"; + } + } - 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, - } - }; + static class Tree { + N root = new N(); - 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, - } - }; + void fill(int depth, int path, int value) throws IOException { + N current = root; + for (int i = 0; i < depth; i++) { + int bitPos = depth - 1 - i; + boolean isSet = ((path >> bitPos) & 1) == 1; + N next = current.walk(isSet); + if (next == null) { + next = new N(); + if (i == depth - 1) { + next.value = value; + next.isLeaf = true; + } + if (path == 0) + next.canBeFill = true; + current.set(isSet, next); + } else { + if (next.isLeaf) + throw new IOException("node is leaf, no other following"); + } + current = next; + } + } - 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, - } - }; + void fill(int depth, int path, N node) throws IOException { + N current = root; + for (int i = 0; i < depth; i++) { + int bitPos = depth - 1 - i; + boolean isSet = ((path >> bitPos) & 1) == 1; + N next = current.walk(isSet); + if (next == null) { + if (i == depth - 1) { + next = node; + } else { + next = new N(); + } + if (path == 0) + next.canBeFill = true; + current.set(isSet, next); + } else { + if (next.isLeaf) + throw new IOException("node is leaf, no other following"); + } + current = next; + } + } + } + + 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, } }; + + final static N EOL; + final static N FILL; + final static Tree blackRunTree; + final static Tree whiteRunTree; + final static Tree eolOnlyTree; + final static Tree codeTree; + + final static int VALUE_EOL = -2000; + final static int VALUE_FILL = -1000; + final static int VALUE_PASSMODE = -3000; + final static int VALUE_HMODE = -4000; + + static { + EOL = new N(); + EOL.isLeaf = true; + EOL.value = VALUE_EOL; + FILL = new N(); + FILL.value = VALUE_FILL; + FILL.left = FILL; + FILL.right = EOL; + + eolOnlyTree = new Tree(); + try { + eolOnlyTree.fill(12, 0, FILL); + eolOnlyTree.fill(12, 1, EOL); + } catch (Exception e) { + e.printStackTrace(); + } + + blackRunTree = new Tree(); + try { + for (int i = 0; i < BLACK_CODES.length; i++) { + for (int j = 0; j < BLACK_CODES[i].length; j++) { + blackRunTree.fill(i + 2, BLACK_CODES[i][j], BLACK_RUN_LENGTHS[i][j]); + } + } + blackRunTree.fill(12, 0, FILL); + blackRunTree.fill(12, 1, EOL); + } catch (Exception e) { + e.printStackTrace(); + } + whiteRunTree = new Tree(); + try { + for (int i = 0; i < WHITE_CODES.length; i++) { + for (int j = 0; j < WHITE_CODES[i].length; j++) { + whiteRunTree.fill(i + 4, WHITE_CODES[i][j], WHITE_RUN_LENGTHS[i][j]); + } + } + whiteRunTree.fill(12, 0, FILL); + whiteRunTree.fill(12, 1, EOL); + } catch (Exception e) { + e.printStackTrace(); + } + + codeTree = new Tree(); + try { + codeTree.fill(4, 1, VALUE_PASSMODE); // pass mode + codeTree.fill(3, 1, VALUE_HMODE); // H mode + codeTree.fill(1, 1, 0); // V(0) + codeTree.fill(3, 3, 1); // V_R(1) + codeTree.fill(6, 3, 2); // V_R(2) + codeTree.fill(7, 3, 3); // V_R(3) + codeTree.fill(3, 2, -1); // V_L(1) + codeTree.fill(6, 2, -2); // V_L(2) + codeTree.fill(7, 2, -3); // V_L(3) + } catch (Exception e) { + e.printStackTrace(); + } + } } 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 index 994ffd4f..ca849a74 100644 --- 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 @@ -91,5 +91,9 @@ interface TIFFExtension { int ORIENTATION_RIGHTTOP = 6; int ORIENTATION_RIGHTBOT = 7; int ORIENTATION_LEFTBOT = 8; - + + int GROUP3OPT_2DENCODING = 1; + int GROUP3OPT_UNCOMPRESSED = 2; + int GROUP3OPT_FILLBITS = 4; + int GROUP4OPT_UNCOMPRESSED = 2; } 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 cd9a0564..b5052f3e 100755 --- 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 @@ -604,9 +604,9 @@ public class TIFFImageReader extends ImageReaderBase { 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: + case TIFFExtension.COMPRESSION_CCITT_T4: // CCITT Group 3 fax encoding -// case TIFFExtension.COMPRESSION_CCITT_T6: + case TIFFExtension.COMPRESSION_CCITT_T6: // CCITT Group 4 fax encoding int[] yCbCrSubsampling = null; @@ -1028,10 +1028,6 @@ public class TIFFImageReader extends ImageReaderBase { break; // 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: @@ -1320,7 +1316,7 @@ public class TIFFImageReader extends ImageReaderBase { } private InputStream createDecompressorStream(final int compression, final int width, final int bands, final InputStream stream) throws IOException { - switch (compression) { + switch (compression) { case TIFFBaseline.COMPRESSION_NONE: return stream; case TIFFBaseline.COMPRESSION_PACKBITS: @@ -1332,9 +1328,11 @@ public class TIFFImageReader extends ImageReaderBase { case TIFFExtension.COMPRESSION_DEFLATE: return new InflaterInputStream(stream, new Inflater(), 1024); case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE: + return new CCITTFaxDecoderStream(stream, width, compression, getValueAsIntWithDefault(TIFF.TAG_FILL_ORDER, 1),0L); case TIFFExtension.COMPRESSION_CCITT_T4: + return new CCITTFaxDecoderStream(stream, width, compression, getValueAsIntWithDefault(TIFF.TAG_FILL_ORDER, 1),getValueAsLongWithDefault(TIFF.TAG_GROUP3OPTIONS, 0L)); case TIFFExtension.COMPRESSION_CCITT_T6: - return new CCITTFaxDecoderStream(stream, width, compression, getValueAsIntWithDefault(TIFF.TAG_FILL_ORDER, 1)); + return new CCITTFaxDecoderStream(stream, width, compression, getValueAsIntWithDefault(TIFF.TAG_FILL_ORDER, 1),getValueAsLongWithDefault(TIFF.TAG_GROUP4OPTIONS, 0L)); default: throw new IllegalArgumentException("Unsupported TIFF compression: " + compression); } diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStreamTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStreamTest.java index 8c735d82..ce817631 100644 --- a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStreamTest.java +++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStreamTest.java @@ -45,122 +45,159 @@ import static org.junit.Assert.*; * * @author Harald Kuhr * @author last modified by $Author: haraldk$ - * @version $Id: CCITTFaxDecoderStreamTest.java,v 1.0 09.03.13 14:44 haraldk Exp$ + * @version $Id: CCITTFaxDecoderStreamTest.java,v 1.0 09.03.13 14:44 haraldk + * Exp$ */ public class CCITTFaxDecoderStreamTest { - // TODO: Better tests (full A4 width scan lines?) + static final byte[] DATA_G3_1D = { 0x00, 0x18, // 000000000001|1000| EOL|3W| + 0x4E, 0x00, // 010|0111|000000000 1B|2W|EOL + 0x30, (byte) 0x9C, // 001|1000|010|0111|00 |3W|1B|2W|EOL + 0x00, 0x61, // 0000000001|1000|01 |3W|1B + 0x38, 0x00, // 0|0111|00000000000 |2W|EOL + (byte) 0xBE, (byte) 0xE0 }; // 1|0111|11|0111|00000 |2W|2B|2W|5F - // From http://www.mikekohn.net/file_formats/tiff.php - static final byte[] DATA_TYPE_2 = { - (byte) 0x84, (byte) 0xe0, // 10000100 11100000 - (byte) 0x84, (byte) 0xe0, // 10000100 11100000 - (byte) 0x84, (byte) 0xe0, // 10000100 11100000 - (byte) 0x7d, (byte) 0xc0, // 01111101 11000000 - }; + static final byte[] DATA_G3_1D_FILL = { 0x00, 0x01, (byte) 0x84, (byte) 0xE0, 0x01, (byte) 0x84, (byte) 0xE0, 0x01, + (byte) 0x84, (byte) 0xE0, 0x1, 0x7D, (byte) 0xC0 }; - static final byte[] DATA_TYPE_3 = { - 0x00, 0x01, (byte) 0xc2, 0x70, - 0x00, 0x01, 0x70, - 0x01, + static final byte[] DATA_G3_2D = { + 0x00, 0x1C, // 000000000001|1|100 EOL|1|3W + 0x27, 0x00, // 0|010|0111|00000000 |1B|2W|EOL + 0x17, 0x00, // 0001|0|1|1|1|00000000 |0|V|V|V|EOL + 0x1C, 0x27, // 0001|1|1000|010|0111| |1|3W|1B|2W| + 0x00, 0x12, // 000000000001|0|010| EOL|0|V-1| + (byte) 0xC0 }; // 1|1|000000 V|V|6F - }; + static final byte[] DATA_G3_2D_FILL = { 0x00, 0x01, (byte) 0xC2, 0x70, 0x01, 0x70, 0x01, (byte) 0xC2, 0x70, 0x01, + 0x2C }; - static final byte[] DATA_TYPE_4 = { - 0x26, (byte) 0xb0, 95, (byte) 0xfa, (byte) 0xc0 - }; + // EOF exception, not sure + static final byte[] DATA_G3_2D_lsb2msb = { 0x00, 0x38, (byte) 0xE4, 0x00, (byte) 0xE8, 0x00, 0x38, (byte) 0xE4, + 0x00, 0x48, 0x03 }; - // Image should be (6 x 4): - // 1 1 1 0 1 1 x x - // 1 1 1 0 1 1 x x - // 1 1 1 0 1 1 x x - // 1 1 0 0 1 1 x x - BufferedImage image; + static final byte[] DATA_G4 = { + 0x04, 0x17, // 0000 0100 0001 01|11 + (byte) 0xF5, (byte) 0x80, // 1|111| 0101 1000 0000 + 0x08, 0x00, // 0000 1000 0000 0000 + (byte) 0x80 }; // 1000 0000 + // Line 1: V-3, V-2, V0 + // Line 2: V0 V0 V0 + // Line 3: V0 V0 V0 + // Line 4: V-1, V0, V0 EOL EOL - @Before - public void init() { - image = new BufferedImage(6, 4, BufferedImage.TYPE_BYTE_BINARY); - for (int y = 0; y < 4; y++) { - for (int x = 0; x < 6; x++) { - image.setRGB(x, y, x == 3 ? 0xff000000 : 0xffffffff); - } - } + // TODO: Better tests (full A4 width scan lines?) - image.setRGB(2, 3, 0xff000000); - } + // From http://www.mikekohn.net/file_formats/tiff.php + static final byte[] DATA_TYPE_2 = { (byte) 0x84, (byte) 0xe0, // 10000100 + // 11100000 + (byte) 0x84, (byte) 0xe0, // 10000100 11100000 + (byte) 0x84, (byte) 0xe0, // 10000100 11100000 + (byte) 0x7d, (byte) 0xc0, // 01111101 11000000 + }; - @Test - public void testReadCountType2() throws IOException { - InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_TYPE_2), 6, TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 1); + static final byte[] DATA_TYPE_3 = { 0x00, 0x01, (byte) 0xc2, 0x70, // 00000000 + // 00000001 + // 11000010 + // 01110000 + 0x00, 0x01, 0x78, // 00000000 00000001 01111000 + 0x00, 0x01, 0x78, // 00000000 00000001 01110000 + 0x00, 0x01, 0x56, // 00000000 00000001 01010110 + // 0x01, // 00000001 - int count = 0; - int read; - while ((read = stream.read()) >= 0) { - count++; - } + }; - // Just make sure we'll have 4 bytes - assertEquals(4, count); + // 001 00110101 10 000010 1 1 1 1 1 1 1 1 1 1 010 11 (000000 padding) + static final byte[] DATA_TYPE_4 = { 0x26, // 001 00110 + (byte) 0xb0, // 101 10 000 + 0x5f, // 010 1 1 1 1 1 + (byte) 0xfa, // 1 1 1 1 1 010 + (byte) 0xc0 // 11 (000000 padding) + }; - // Verify that we don't return arbitrary values - assertEquals(-1, read); - } + // Image should be (6 x 4): + // 1 1 1 0 1 1 x x + // 1 1 1 0 1 1 x x + // 1 1 1 0 1 1 x x + // 1 1 0 0 1 1 x x + BufferedImage image; - @Test - public void testDecodeType2() throws IOException { - InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_TYPE_2), 6, TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 1); + @Before + public void init() { + image = new BufferedImage(6, 4, BufferedImage.TYPE_BYTE_BINARY); + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 6; x++) { + image.setRGB(x, y, x == 3 ? 0xff000000 : 0xffffffff); + } + } - byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData(); - byte[] bytes = new byte[imageData.length]; - new DataInputStream(stream).readFully(bytes); + image.setRGB(2, 3, 0xff000000); + } -// JPanel panel = new JPanel(); -// panel.add(new JLabel("Expected", new BufferedImageIcon(image, 300, 300, true), JLabel.CENTER)); -// panel.add(new JLabel("Actual", new BufferedImageIcon(new BufferedImage(image.getColorModel(), Raster.createPackedRaster(new DataBufferByte(bytes, bytes.length), 6, 4, 1, null), false, null), 300, 300, true), JLabel.CENTER)); -// JOptionPane.showConfirmDialog(null, panel); + @Test + public void testDecodeType2() throws IOException { + InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_TYPE_2), 6, + TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 1, 0L); - assertArrayEquals(imageData, bytes); - } + byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData(); + byte[] bytes = new byte[imageData.length]; + new DataInputStream(stream).readFully(bytes); + assertArrayEquals(imageData, bytes); + } - @Test(expected = IllegalArgumentException.class) - public void testDecodeType3() throws IOException { - InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_TYPE_3), 6, TIFFExtension.COMPRESSION_CCITT_T4, 1); + @Test + public void testDecodeType3_1D() throws IOException { + InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_1D), 6, + TIFFExtension.COMPRESSION_CCITT_T4, 1, 0L); - byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData(); - byte[] bytes = new byte[imageData.length]; - DataInputStream dataInput = new DataInputStream(stream); + byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData(); + byte[] bytes = new byte[imageData.length]; + new DataInputStream(stream).readFully(bytes); + assertArrayEquals(imageData, bytes); + } - for (int y = 0; y < image.getHeight(); y++) { - System.err.println("y: " + y); - dataInput.readFully(bytes, y * image.getWidth(), image.getWidth()); - } + @Test + public void testDecodeType3_1D_FILL() throws IOException { + InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_1D_FILL), 6, + TIFFExtension.COMPRESSION_CCITT_T4, 1, TIFFExtension.GROUP3OPT_FILLBITS); -// JPanel panel = new JPanel(); -// panel.add(new JLabel("Expected", new BufferedImageIcon(image, 300, 300, true), JLabel.CENTER)); -// panel.add(new JLabel("Actual", new BufferedImageIcon(new BufferedImage(image.getColorModel(), Raster.createPackedRaster(new DataBufferByte(bytes, bytes.length), 6, 4, 1, null), false, null), 300, 300, true), JLabel.CENTER)); -// JOptionPane.showConfirmDialog(null, panel); + byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData(); + byte[] bytes = new byte[imageData.length]; + new DataInputStream(stream).readFully(bytes); + assertArrayEquals(imageData, bytes); + } - assertArrayEquals(imageData, bytes); - } + @Test + public void testDecodeType3_2D() throws IOException { + InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_2D), 6, + TIFFExtension.COMPRESSION_CCITT_T4, 1, TIFFExtension.GROUP3OPT_2DENCODING); - @Test(expected = IllegalArgumentException.class) - public void testDecodeType4() throws IOException { - InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_TYPE_4), 6, TIFFExtension.COMPRESSION_CCITT_T6, 1); + byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData(); + byte[] bytes = new byte[imageData.length]; + new DataInputStream(stream).readFully(bytes); + assertArrayEquals(imageData, bytes); + } - byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData(); - byte[] bytes = new byte[imageData.length]; - DataInputStream dataInput = new DataInputStream(stream); + @Test + public void testDecodeType3_2D_FILL() throws IOException { + InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_2D_FILL), 6, + TIFFExtension.COMPRESSION_CCITT_T4, 1, + TIFFExtension.GROUP3OPT_2DENCODING | TIFFExtension.GROUP3OPT_FILLBITS); - for (int y = 0; y < image.getHeight(); y++) { - System.err.println("y: " + y); - dataInput.readFully(bytes, y * image.getWidth(), image.getWidth()); - } + byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData(); + byte[] bytes = new byte[imageData.length]; + new DataInputStream(stream).readFully(bytes); + assertArrayEquals(imageData, bytes); + } + + @Test + public void testDecodeType4() throws IOException { + InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G4), 6, + TIFFExtension.COMPRESSION_CCITT_T6, 1, + 0L); -// JPanel panel = new JPanel(); -// panel.add(new JLabel("Expected", new BufferedImageIcon(image, 300, 300, true), JLabel.CENTER)); -// panel.add(new JLabel("Actual", new BufferedImageIcon(new BufferedImage(image.getColorModel(), Raster.createPackedRaster(new DataBufferByte(bytes, bytes.length), 6, 4, 1, null), false, null), 300, 300, true), JLabel.CENTER)); -// JOptionPane.showConfirmDialog(null, panel); - - assertArrayEquals(imageData, bytes); - } + byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData(); + byte[] bytes = new byte[imageData.length]; + new DataInputStream(stream).readFully(bytes); + assertArrayEquals(imageData, bytes); + } } From 2b22b0252348b617dad414d7084423f24116cf10 Mon Sep 17 00:00:00 2001 From: Schmidor Date: Thu, 2 Jul 2015 23:35:19 +0200 Subject: [PATCH 2/6] CITT Group 3/4 Support - some cleanup, group 3 /T.4 should work now --- .../plugins/tiff/CCITTFaxDecoderStream.java | 1229 +++++++++-------- .../imageio/plugins/tiff/TIFFExtension.java | 10 +- .../tiff/CCITTFaxDecoderStreamTest.java | 241 ++-- 3 files changed, 745 insertions(+), 735 deletions(-) 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 index 5eb9479c..ae5eb26e 100644 --- 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 @@ -43,608 +43,629 @@ import com.twelvemonkeys.lang.Validate; * @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; - - // Need to take fill order into account (?) (use flip table?) - private final int fillOrder; - private final int type; - - private final int[] changesReferenceRow; - private final int[] changesCurrentRow; - private int changesReferenceRowCount; - private int changesCurrentRowCount; - - private static final int EOL_CODE = 0x01; // 12 bit - - private boolean optionG32D = false; - - @SuppressWarnings("unused") // Leading zeros for aligning EOL - private boolean optionG3Fill = false; - - private boolean optionUncompressed = false; - - public CCITTFaxDecoderStream(final InputStream stream, final int columns, final int type, final int fillOrder, - final long options) { - 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 = type; - this.fillOrder = fillOrder;// Validate.isTrue(fillOrder == 1, fillOrder, - // "Only fill order 1 supported: %s"); // - // TODO: Implement fillOrder == 2 - - this.changesReferenceRow = new int[columns]; - this.changesCurrentRow = new int[columns]; - - switch (type) { - case TIFFExtension.COMPRESSION_CCITT_T4: - optionG32D = (options & TIFFExtension.GROUP3OPT_2DENCODING) != 0; - optionG3Fill = (options & TIFFExtension.GROUP3OPT_FILLBITS) != 0; - optionUncompressed = (options & TIFFExtension.GROUP3OPT_UNCOMPRESSED) != 0; - break; - case TIFFExtension.COMPRESSION_CCITT_T6: - optionUncompressed = (options & TIFFExtension.GROUP4OPT_UNCOMPRESSED) != 0; - break; - } - - Validate.isTrue(!optionUncompressed, optionUncompressed, - "CCITT GROUP 3/4 OPTION UNCOMPRESSED is not supported"); - } - - 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 decode1D() throws IOException { - int index = 0; - boolean white = true; - changesCurrentRowCount = 0; - do { - int completeRun = 0; - if (white) { - completeRun = decodeRun(whiteRunTree); - } else { - completeRun = decodeRun(blackRunTree); - } - - index += completeRun; - changesCurrentRow[changesCurrentRowCount++] = index; - // Flip color for next run - white = !white; - } while (index < columns); - } - - private void decode2D() throws IOException { - boolean white = true; - int index = 0; - changesCurrentRowCount = 0; - int ref = 0; - mode: while (index < columns) { - // read mode - N n = codeTree.root; - while (true) { - n = n.walk(readBit()); - if (n == null) { - continue mode; - } else if (n.isLeaf) { - switch (n.value) { - case VALUE_HMODE: - System.out.print("|H="); - int runLength = 0; - runLength = decodeRun(white ? whiteRunTree : blackRunTree); - changesCurrentRow[changesCurrentRowCount++] = index; - index += runLength; - System.out.print(runLength + (white? "W" : "B")); - runLength = decodeRun(white ? blackRunTree : whiteRunTree); - changesCurrentRow[changesCurrentRowCount++] = index; - index += runLength; - System.out.print(runLength + (!white? "W" : "B")); - break; - case VALUE_PASSMODE: - System.out.print("|P"); - ref++; - // TODO - break; - default: - System.out.print("|V" + n.value); - index = changesReferenceRow[ref] + n.value; - changesCurrentRow[ref] = index; - if(changesCurrentRow[ref] <= index) ref++; //TODO - changesCurrentRowCount++; - white = !white; - break; - } - continue mode; - } - } - } - } - - private void decodeRowType2() throws IOException { - resetBuffer(); - decode1D(); - } - - private void decodeRowType4() throws IOException { - eof: while (true) { - // read till next EOL code - N n = eolOnlyTree.root; - while (true) { - N tmp = n; - n = n.walk(readBit()); - if (n == null) - continue eof; - if (n.isLeaf) { - System.out.print("|EOL"); - break eof; - } - if(tmp == n) System.out.print("F"); - } - } - boolean k = optionG32D ? readBit() : true; - System.out.print("|k=" + k); - if (k) { - decode1D(); - changesReferenceRowCount = changesCurrentRowCount; - System.arraycopy(changesCurrentRow, 0, changesReferenceRow, 0, changesCurrentRowCount); - } else { - decode2D(); - } - } - - private void decodeRowType6() throws IOException { - changesReferenceRowCount = 1; - changesReferenceRow[0] = columns; - decode2D(); - } - - private void decodeRow() throws IOException { - switch (type) { - case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE: - decodeRowType2(); - break; - case TIFFExtension.COMPRESSION_CCITT_T4: - decodeRowType4(); - break; - case TIFFExtension.COMPRESSION_CCITT_T6: - decodeRowType6(); - break; - } - int index = 0; - boolean white = true; - for (int i = 0; i <= changesCurrentRowCount; i++) { - int nextChange = columns; - if (i != changesCurrentRowCount) { - nextChange = changesCurrentRow[i]; - } - - while (index % 8 != 0 && (nextChange - index) > 0) { - decodedRow[index++ / 8] |= (white ? 1 << 8 - (index % 8) : 0); - } - - if (index % 8 == 0) { - final byte value = (byte) (white ? 0xff : 0x00); - - while ((nextChange - index) > 7) { - decodedRow[index / 8] = value; - index += 8; - } - } - while ((nextChange - index) > 0) { - if (index % 8 == 0) - decodedRow[(index + 1) / 8] = 0; - - decodedRow[index++ / 8] |= (white ? 1 << 8 - (index % 8) : 0); - } - - white = !white; - } - - if (index != columns) { - throw new IOException("Sum of run-lengths does not equal scan line width: " + index + " > " + columns); - } - - decodedLength = (index + 7) / 8; - } - - private int decodeRun(Tree tree) throws IOException { - int total = 0; - - N n = tree.root; - while (true) { - boolean bit = readBit(); - n = n.walk(bit); - if (n == null) - throw new IOException("Unknown code in Huffman RLE stream"); - - if (n.isLeaf) { - total += n.value; - if (n.value < 64) { - return total; - } else { - n = tree.root; - continue; - } - } - } - } - - private void resetBuffer() { - for (int i = 0; i < decodedRow.length; i++) { - decodedRow[i] = 0; - } - while (true) { - if (bufferPos == -1) { - return; - } - - try { - boolean skip = readBit(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - } - - int buffer = -1; - int bufferPos = -1; - - private boolean readBit() throws IOException { - if (bufferPos < 0 || bufferPos > 7) { - buffer = in.read(); - if (buffer == -1) { - throw new EOFException("Unexpected end of Huffman RLE stream"); - } - bufferPos = 0; - } - - boolean isSet = ((buffer >> (7 - bufferPos)) & 1) == 1; - bufferPos++; - if (bufferPos > 7) - bufferPos = -1; - return isSet; - } - - @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 class N { - N left; - N right; - - int value; // > 63 non term. - boolean canBeFill = false; - boolean isLeaf = false; - - void set(boolean next, N node) { - if (!next) { - left = node; - } else { - right = node; - } - } - - N walk(boolean next) { - return next ? right : left; - } - - @Override - public String toString() { - return "[leaf=" + isLeaf + ", value=" + value + ", canBeFill=" + canBeFill + "]"; - } - } - - static class Tree { - N root = new N(); - - void fill(int depth, int path, int value) throws IOException { - N current = root; - for (int i = 0; i < depth; i++) { - int bitPos = depth - 1 - i; - boolean isSet = ((path >> bitPos) & 1) == 1; - N next = current.walk(isSet); - if (next == null) { - next = new N(); - if (i == depth - 1) { - next.value = value; - next.isLeaf = true; - } - if (path == 0) - next.canBeFill = true; - current.set(isSet, next); - } else { - if (next.isLeaf) - throw new IOException("node is leaf, no other following"); - } - current = next; - } - } - - void fill(int depth, int path, N node) throws IOException { - N current = root; - for (int i = 0; i < depth; i++) { - int bitPos = depth - 1 - i; - boolean isSet = ((path >> bitPos) & 1) == 1; - N next = current.walk(isSet); - if (next == null) { - if (i == depth - 1) { - next = node; - } else { - next = new N(); - } - if (path == 0) - next.canBeFill = true; - current.set(isSet, next); - } else { - if (next.isLeaf) - throw new IOException("node is leaf, no other following"); - } - current = next; - } - } - } - - 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, } }; - - final static N EOL; - final static N FILL; - final static Tree blackRunTree; - final static Tree whiteRunTree; - final static Tree eolOnlyTree; - final static Tree codeTree; - - final static int VALUE_EOL = -2000; - final static int VALUE_FILL = -1000; - final static int VALUE_PASSMODE = -3000; - final static int VALUE_HMODE = -4000; - - static { - EOL = new N(); - EOL.isLeaf = true; - EOL.value = VALUE_EOL; - FILL = new N(); - FILL.value = VALUE_FILL; - FILL.left = FILL; - FILL.right = EOL; - - eolOnlyTree = new Tree(); - try { - eolOnlyTree.fill(12, 0, FILL); - eolOnlyTree.fill(12, 1, EOL); - } catch (Exception e) { - e.printStackTrace(); - } - - blackRunTree = new Tree(); - try { - for (int i = 0; i < BLACK_CODES.length; i++) { - for (int j = 0; j < BLACK_CODES[i].length; j++) { - blackRunTree.fill(i + 2, BLACK_CODES[i][j], BLACK_RUN_LENGTHS[i][j]); - } - } - blackRunTree.fill(12, 0, FILL); - blackRunTree.fill(12, 1, EOL); - } catch (Exception e) { - e.printStackTrace(); - } - whiteRunTree = new Tree(); - try { - for (int i = 0; i < WHITE_CODES.length; i++) { - for (int j = 0; j < WHITE_CODES[i].length; j++) { - whiteRunTree.fill(i + 4, WHITE_CODES[i][j], WHITE_RUN_LENGTHS[i][j]); - } - } - whiteRunTree.fill(12, 0, FILL); - whiteRunTree.fill(12, 1, EOL); - } catch (Exception e) { - e.printStackTrace(); - } - - codeTree = new Tree(); - try { - codeTree.fill(4, 1, VALUE_PASSMODE); // pass mode - codeTree.fill(3, 1, VALUE_HMODE); // H mode - codeTree.fill(1, 1, 0); // V(0) - codeTree.fill(3, 3, 1); // V_R(1) - codeTree.fill(6, 3, 2); // V_R(2) - codeTree.fill(7, 3, 3); // V_R(3) - codeTree.fill(3, 2, -1); // V_L(1) - codeTree.fill(6, 2, -2); // V_L(2) - codeTree.fill(7, 2, -3); // V_L(3) - } catch (Exception e) { - e.printStackTrace(); - } - } + // 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; + + // Need to take fill order into account (?) (use flip table?) + private final int fillOrder; + private final int type; + + private int[] changesReferenceRow; + private int[] changesCurrentRow; + private int changesReferenceRowCount; + private int changesCurrentRowCount; + + private static final int EOL_CODE = 0x01; // 12 bit + + private boolean optionG32D = false; + + @SuppressWarnings("unused") // Leading zeros for aligning EOL + private boolean optionG3Fill = false; + + private boolean optionUncompressed = false; + + public CCITTFaxDecoderStream(final InputStream stream, final int columns, final int type, final int fillOrder, + final long options) { + 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 = type; + this.fillOrder = fillOrder;// Validate.isTrue(fillOrder == 1, fillOrder, + // "Only fill order 1 supported: %s"); // + // TODO: Implement fillOrder == 2 + + this.changesReferenceRow = new int[columns]; + this.changesCurrentRow = new int[columns]; + + switch (type) { + case TIFFExtension.COMPRESSION_CCITT_T4: + optionG32D = (options & TIFFExtension.GROUP3OPT_2DENCODING) != 0; + optionG3Fill = (options & TIFFExtension.GROUP3OPT_FILLBITS) != 0; + optionUncompressed = (options & TIFFExtension.GROUP3OPT_UNCOMPRESSED) != 0; + break; + case TIFFExtension.COMPRESSION_CCITT_T6: + optionUncompressed = (options & TIFFExtension.GROUP4OPT_UNCOMPRESSED) != 0; + break; + } + + Validate.isTrue(!optionUncompressed, optionUncompressed, + "CCITT GROUP 3/4 OPTION UNCOMPRESSED is not supported"); + } + + 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 decode1D() throws IOException { + int index = 0; + boolean white = true; + changesCurrentRowCount = 0; + do { + int completeRun = 0; + if (white) { + completeRun = decodeRun(whiteRunTree); + } else { + completeRun = decodeRun(blackRunTree); + } + + index += completeRun; + changesCurrentRow[changesCurrentRowCount++] = index; + // Flip color for next run + white = !white; + } while (index < columns); + } + + private void decode2D() throws IOException { + changesReferenceRowCount = changesCurrentRowCount; + int[] tmp = changesCurrentRow; + changesCurrentRow = changesReferenceRow; + changesReferenceRow = tmp; + + if (changesReferenceRowCount == 0) { + changesReferenceRowCount = 3; + changesReferenceRow[0] = columns; + changesReferenceRow[1] = columns; + changesReferenceRow[2] = columns; + } + + boolean white = true; + int index = -1; + changesCurrentRowCount = 0; + mode: while (index < columns) { + // read mode + Node n = codeTree.root; + while (true) { + n = n.walk(readBit()); + if (n == null) { + continue mode; + } else if (n.isLeaf) { + + switch (n.value) { + case VALUE_HMODE: + int runLength = 0; + runLength = decodeRun(white ? whiteRunTree : blackRunTree); + index += runLength; + changesCurrentRow[changesCurrentRowCount++] = index; + + runLength = decodeRun(white ? blackRunTree : whiteRunTree); + index += runLength; + changesCurrentRow[changesCurrentRowCount++] = index; + break; + case VALUE_PASSMODE: + index = changesReferenceRow[getNextChangingElement(index, white) + 1]; + break; + default: + // Vertical mode (-3 to 3) + index = changesReferenceRow[getNextChangingElement(index, white)] + n.value; + changesCurrentRow[changesCurrentRowCount] = index; + changesCurrentRowCount++; + white = !white; + break; + } + continue mode; + } + } + } + } + + private int getNextChangingElement(int a0, boolean white) { + int start = white ? 0 : 1; + for (int i = start; i < changesReferenceRowCount; i += 2) { + if (a0 < changesReferenceRow[i]) { + return i; + } + } + + return 0; + } + + private void decodeRowType2() throws IOException { + resetBuffer(); + decode1D(); + } + + private void decodeRowType4() throws IOException { + eof: while (true) { + // read till next EOL code + Node n = eolOnlyTree.root; + while (true) { + Node tmp = n; + n = n.walk(readBit()); + if (n == null) + continue eof; + if (n.isLeaf) { + break eof; + } + } + } + boolean k = optionG32D ? readBit() : true; + if (k) { + decode1D(); + } else { + decode2D(); + } + } + + private void decodeRowType6() throws IOException { + decode2D(); + } + + private void decodeRow() throws IOException { + switch (type) { + case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE: + decodeRowType2(); + break; + case TIFFExtension.COMPRESSION_CCITT_T4: + decodeRowType4(); + break; + case TIFFExtension.COMPRESSION_CCITT_T6: + decodeRowType6(); + break; + } + int index = 0; + boolean white = true; + for (int i = 0; i <= changesCurrentRowCount; i++) { + int nextChange = columns; + if (i != changesCurrentRowCount) { + nextChange = changesCurrentRow[i]; + } + if (nextChange > columns) { + nextChange = columns; + } + + int byteIndex = index / 8; + + while (index % 8 != 0 && (nextChange - index) > 0) { + decodedRow[byteIndex] |= (white ? 1 << (7 - ((index) % 8)) : 0); + index++; + } + + if (index % 8 == 0) { + byteIndex = index / 8; + final byte value = (byte) (white ? 0xff : 0x00); + + while ((nextChange - index) > 7) { + decodedRow[byteIndex] = value; + index += 8; + ++byteIndex; + } + } + + while ((nextChange - index) > 0) { + if (index % 8 == 0) { + decodedRow[byteIndex] = 0; + } + decodedRow[byteIndex] |= (white ? 1 << (7 - ((index) % 8)) : 0); + index++; + } + + white = !white; + } + + if (index != columns) { + throw new IOException("Sum of run-lengths does not equal scan line width: " + index + " > " + columns); + } + + decodedLength = (index + 7) / 8; + } + + private int decodeRun(Tree tree) throws IOException { + int total = 0; + + Node n = tree.root; + while (true) { + boolean bit = readBit(); + n = n.walk(bit); + if (n == null) + throw new IOException("Unknown code in Huffman RLE stream"); + + if (n.isLeaf) { + total += n.value; + if (n.value < 64) { + return total; + } else { + n = tree.root; + continue; + } + } + } + } + + private void resetBuffer() { + for (int i = 0; i < decodedRow.length; i++) { + decodedRow[i] = 0; + } + while (true) { + if (bufferPos == -1) { + return; + } + + try { + boolean skip = readBit(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + + int buffer = -1; + int bufferPos = -1; + + private boolean readBit() throws IOException { + if (bufferPos < 0 || bufferPos > 7) { + buffer = in.read(); + if (buffer == -1) { + throw new EOFException("Unexpected end of Huffman RLE stream"); + } + bufferPos = 0; + } + + boolean isSet = ((buffer >> (7 - bufferPos)) & 1) == 1; + bufferPos++; + if (bufferPos > 7) + bufferPos = -1; + return isSet; + } + + @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 class Node { + Node left; + Node right; + + int value; // > 63 non term. + boolean canBeFill = false; + boolean isLeaf = false; + + void set(boolean next, Node node) { + if (!next) { + left = node; + } else { + right = node; + } + } + + Node walk(boolean next) { + return next ? right : left; + } + + @Override + public String toString() { + return "[leaf=" + isLeaf + ", value=" + value + ", canBeFill=" + canBeFill + "]"; + } + } + + static class Tree { + Node root = new Node(); + + void fill(int depth, int path, int value) throws IOException { + Node current = root; + for (int i = 0; i < depth; i++) { + int bitPos = depth - 1 - i; + boolean isSet = ((path >> bitPos) & 1) == 1; + Node next = current.walk(isSet); + if (next == null) { + next = new Node(); + if (i == depth - 1) { + next.value = value; + next.isLeaf = true; + } + if (path == 0) + next.canBeFill = true; + current.set(isSet, next); + } else { + if (next.isLeaf) + throw new IOException("node is leaf, no other following"); + } + current = next; + } + } + + void fill(int depth, int path, Node node) throws IOException { + Node current = root; + for (int i = 0; i < depth; i++) { + int bitPos = depth - 1 - i; + boolean isSet = ((path >> bitPos) & 1) == 1; + Node next = current.walk(isSet); + if (next == null) { + if (i == depth - 1) { + next = node; + } else { + next = new Node(); + } + if (path == 0) + next.canBeFill = true; + current.set(isSet, next); + } else { + if (next.isLeaf) + throw new IOException("node is leaf, no other following"); + } + current = next; + } + } + } + + 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, } }; + + final static Node EOL; + final static Node FILL; + final static Tree blackRunTree; + final static Tree whiteRunTree; + final static Tree eolOnlyTree; + final static Tree codeTree; + + final static int VALUE_EOL = -2000; + final static int VALUE_FILL = -1000; + final static int VALUE_PASSMODE = -3000; + final static int VALUE_HMODE = -4000; + + static { + EOL = new Node(); + EOL.isLeaf = true; + EOL.value = VALUE_EOL; + FILL = new Node(); + FILL.value = VALUE_FILL; + FILL.left = FILL; + FILL.right = EOL; + + eolOnlyTree = new Tree(); + try { + eolOnlyTree.fill(12, 0, FILL); + eolOnlyTree.fill(12, 1, EOL); + } catch (Exception e) { + e.printStackTrace(); + } + + blackRunTree = new Tree(); + try { + for (int i = 0; i < BLACK_CODES.length; i++) { + for (int j = 0; j < BLACK_CODES[i].length; j++) { + blackRunTree.fill(i + 2, BLACK_CODES[i][j], BLACK_RUN_LENGTHS[i][j]); + } + } + blackRunTree.fill(12, 0, FILL); + blackRunTree.fill(12, 1, EOL); + } catch (Exception e) { + e.printStackTrace(); + } + whiteRunTree = new Tree(); + try { + for (int i = 0; i < WHITE_CODES.length; i++) { + for (int j = 0; j < WHITE_CODES[i].length; j++) { + whiteRunTree.fill(i + 4, WHITE_CODES[i][j], WHITE_RUN_LENGTHS[i][j]); + } + } + whiteRunTree.fill(12, 0, FILL); + whiteRunTree.fill(12, 1, EOL); + } catch (Exception e) { + e.printStackTrace(); + } + + codeTree = new Tree(); + try { + codeTree.fill(4, 1, VALUE_PASSMODE); // pass mode + codeTree.fill(3, 1, VALUE_HMODE); // H mode + codeTree.fill(1, 1, 0); // V(0) + codeTree.fill(3, 3, 1); // V_R(1) + codeTree.fill(6, 3, 2); // V_R(2) + codeTree.fill(7, 3, 3); // V_R(3) + codeTree.fill(3, 2, -1); // V_L(1) + codeTree.fill(6, 2, -2); // V_L(2) + codeTree.fill(7, 2, -3); // V_L(3) + } catch (Exception e) { + e.printStackTrace(); + } + } } 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 index ca849a74..c799fc6f 100644 --- 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 @@ -91,9 +91,9 @@ interface TIFFExtension { int ORIENTATION_RIGHTTOP = 6; int ORIENTATION_RIGHTBOT = 7; int ORIENTATION_LEFTBOT = 8; - - int GROUP3OPT_2DENCODING = 1; - int GROUP3OPT_UNCOMPRESSED = 2; - int GROUP3OPT_FILLBITS = 4; - int GROUP4OPT_UNCOMPRESSED = 2; + + int GROUP3OPT_2DENCODING = 1; + int GROUP3OPT_UNCOMPRESSED = 2; + int GROUP3OPT_FILLBITS = 4; + int GROUP4OPT_UNCOMPRESSED = 2; } diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStreamTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStreamTest.java index ce817631..0139f9d1 100644 --- a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStreamTest.java +++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStreamTest.java @@ -50,154 +50,143 @@ import static org.junit.Assert.*; */ public class CCITTFaxDecoderStreamTest { - static final byte[] DATA_G3_1D = { 0x00, 0x18, // 000000000001|1000| EOL|3W| - 0x4E, 0x00, // 010|0111|000000000 1B|2W|EOL - 0x30, (byte) 0x9C, // 001|1000|010|0111|00 |3W|1B|2W|EOL - 0x00, 0x61, // 0000000001|1000|01 |3W|1B - 0x38, 0x00, // 0|0111|00000000000 |2W|EOL - (byte) 0xBE, (byte) 0xE0 }; // 1|0111|11|0111|00000 |2W|2B|2W|5F + // group3_1d.tif: EOL|3W|1B|2W|EOL|3W|1B|2W|EOL|3W|1B|2W|EOL|2W|2B|2W|5*F + static final byte[] DATA_G3_1D = { 0x00, 0x18, 0x4E, 0x00, 0x30, (byte) 0x9C, 0x00, 0x61, 0x38, 0x00, (byte) 0xBE, + (byte) 0xE0 }; - static final byte[] DATA_G3_1D_FILL = { 0x00, 0x01, (byte) 0x84, (byte) 0xE0, 0x01, (byte) 0x84, (byte) 0xE0, 0x01, - (byte) 0x84, (byte) 0xE0, 0x1, 0x7D, (byte) 0xC0 }; + // group3_1d_fill.tif + static final byte[] DATA_G3_1D_FILL = { 0x00, 0x01, (byte) 0x84, (byte) 0xE0, 0x01, (byte) 0x84, (byte) 0xE0, 0x01, + (byte) 0x84, (byte) 0xE0, 0x1, 0x7D, (byte) 0xC0 }; - static final byte[] DATA_G3_2D = { - 0x00, 0x1C, // 000000000001|1|100 EOL|1|3W - 0x27, 0x00, // 0|010|0111|00000000 |1B|2W|EOL - 0x17, 0x00, // 0001|0|1|1|1|00000000 |0|V|V|V|EOL - 0x1C, 0x27, // 0001|1|1000|010|0111| |1|3W|1B|2W| - 0x00, 0x12, // 000000000001|0|010| EOL|0|V-1| - (byte) 0xC0 }; // 1|1|000000 V|V|6F + // group3_2d.tif: EOL|k=1|3W|1B|2W|EOL|k=0|V|V|V|EOL|k=1|3W|1B|2W|EOL|k=0|V-1|V|V|6*F + static final byte[] DATA_G3_2D = { 0x00, 0x1C, 0x27, 0x00, 0x17, 0x00, 0x1C, 0x27, 0x00, 0x12, (byte) 0xC0 }; - static final byte[] DATA_G3_2D_FILL = { 0x00, 0x01, (byte) 0xC2, 0x70, 0x01, 0x70, 0x01, (byte) 0xC2, 0x70, 0x01, - 0x2C }; + // group3_2d_fill.tif + static final byte[] DATA_G3_2D_FILL = { 0x00, 0x01, (byte) 0xC2, 0x70, 0x01, 0x70, 0x01, (byte) 0xC2, 0x70, 0x01, + 0x2C }; - // EOF exception, not sure - static final byte[] DATA_G3_2D_lsb2msb = { 0x00, 0x38, (byte) 0xE4, 0x00, (byte) 0xE8, 0x00, 0x38, (byte) 0xE4, - 0x00, 0x48, 0x03 }; + static final byte[] DATA_G3_2D_lsb2msb = { 0x00, 0x38, (byte) 0xE4, 0x00, (byte) 0xE8, 0x00, 0x38, (byte) 0xE4, + 0x00, 0x48, 0x03 }; - static final byte[] DATA_G4 = { - 0x04, 0x17, // 0000 0100 0001 01|11 - (byte) 0xF5, (byte) 0x80, // 1|111| 0101 1000 0000 - 0x08, 0x00, // 0000 1000 0000 0000 - (byte) 0x80 }; // 1000 0000 - // Line 1: V-3, V-2, V0 - // Line 2: V0 V0 V0 - // Line 3: V0 V0 V0 - // Line 4: V-1, V0, V0 EOL EOL + // group4.tif: + // Line 1: V-3, V-2, V0 + // Line 2: V0 V0 V0 + // Line 3: V0 V0 V0 + // Line 4: V-1, V0, V0 EOL EOL + static final byte[] DATA_G4 = { 0x04, 0x17, (byte) 0xF5, (byte) 0x80, 0x08, 0x00, (byte) 0x80 }; - // TODO: Better tests (full A4 width scan lines?) + // TODO: Better tests (full A4 width scan lines?) - // From http://www.mikekohn.net/file_formats/tiff.php - static final byte[] DATA_TYPE_2 = { (byte) 0x84, (byte) 0xe0, // 10000100 - // 11100000 - (byte) 0x84, (byte) 0xe0, // 10000100 11100000 - (byte) 0x84, (byte) 0xe0, // 10000100 11100000 - (byte) 0x7d, (byte) 0xc0, // 01111101 11000000 - }; + // From http://www.mikekohn.net/file_formats/tiff.php + static final byte[] DATA_TYPE_2 = { (byte) 0x84, (byte) 0xe0, // 10000100 + // 11100000 + (byte) 0x84, (byte) 0xe0, // 10000100 11100000 + (byte) 0x84, (byte) 0xe0, // 10000100 11100000 + (byte) 0x7d, (byte) 0xc0, // 01111101 11000000 + }; - static final byte[] DATA_TYPE_3 = { 0x00, 0x01, (byte) 0xc2, 0x70, // 00000000 - // 00000001 - // 11000010 - // 01110000 - 0x00, 0x01, 0x78, // 00000000 00000001 01111000 - 0x00, 0x01, 0x78, // 00000000 00000001 01110000 - 0x00, 0x01, 0x56, // 00000000 00000001 01010110 - // 0x01, // 00000001 + static final byte[] DATA_TYPE_3 = { 0x00, 0x01, (byte) 0xc2, 0x70, // 00000000 + // 00000001 + // 11000010 + // 01110000 + 0x00, 0x01, 0x78, // 00000000 00000001 01111000 + 0x00, 0x01, 0x78, // 00000000 00000001 01110000 + 0x00, 0x01, 0x56, // 00000000 00000001 01010110 + // 0x01, // 00000001 - }; + }; - // 001 00110101 10 000010 1 1 1 1 1 1 1 1 1 1 010 11 (000000 padding) - static final byte[] DATA_TYPE_4 = { 0x26, // 001 00110 - (byte) 0xb0, // 101 10 000 - 0x5f, // 010 1 1 1 1 1 - (byte) 0xfa, // 1 1 1 1 1 010 - (byte) 0xc0 // 11 (000000 padding) - }; + // 001 00110101 10 000010 1 1 1 1 1 1 1 1 1 1 010 11 (000000 padding) + static final byte[] DATA_TYPE_4 = { 0x26, // 001 00110 + (byte) 0xb0, // 101 10 000 + 0x5f, // 010 1 1 1 1 1 + (byte) 0xfa, // 1 1 1 1 1 010 + (byte) 0xc0 // 11 (000000 padding) + }; - // Image should be (6 x 4): - // 1 1 1 0 1 1 x x - // 1 1 1 0 1 1 x x - // 1 1 1 0 1 1 x x - // 1 1 0 0 1 1 x x - BufferedImage image; + // Image should be (6 x 4): + // 1 1 1 0 1 1 x x + // 1 1 1 0 1 1 x x + // 1 1 1 0 1 1 x x + // 1 1 0 0 1 1 x x + BufferedImage image; - @Before - public void init() { - image = new BufferedImage(6, 4, BufferedImage.TYPE_BYTE_BINARY); - for (int y = 0; y < 4; y++) { - for (int x = 0; x < 6; x++) { - image.setRGB(x, y, x == 3 ? 0xff000000 : 0xffffffff); - } - } + @Before + public void init() { + image = new BufferedImage(6, 4, BufferedImage.TYPE_BYTE_BINARY); + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 6; x++) { + image.setRGB(x, y, x == 3 ? 0xff000000 : 0xffffffff); + } + } - image.setRGB(2, 3, 0xff000000); - } + image.setRGB(2, 3, 0xff000000); + } - @Test - public void testDecodeType2() throws IOException { - InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_TYPE_2), 6, - TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 1, 0L); + @Test + public void testDecodeType2() throws IOException { + InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_TYPE_2), 6, + TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 1, 0L); - byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData(); - byte[] bytes = new byte[imageData.length]; - new DataInputStream(stream).readFully(bytes); - assertArrayEquals(imageData, bytes); - } + byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData(); + byte[] bytes = new byte[imageData.length]; + new DataInputStream(stream).readFully(bytes); + assertArrayEquals(imageData, bytes); + } - @Test - public void testDecodeType3_1D() throws IOException { - InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_1D), 6, - TIFFExtension.COMPRESSION_CCITT_T4, 1, 0L); + @Test + public void testDecodeType3_1D() throws IOException { + InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_1D), 6, + TIFFExtension.COMPRESSION_CCITT_T4, 1, 0L); - byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData(); - byte[] bytes = new byte[imageData.length]; - new DataInputStream(stream).readFully(bytes); - assertArrayEquals(imageData, bytes); - } + byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData(); + byte[] bytes = new byte[imageData.length]; + new DataInputStream(stream).readFully(bytes); + assertArrayEquals(imageData, bytes); + } - @Test - public void testDecodeType3_1D_FILL() throws IOException { - InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_1D_FILL), 6, - TIFFExtension.COMPRESSION_CCITT_T4, 1, TIFFExtension.GROUP3OPT_FILLBITS); + @Test + public void testDecodeType3_1D_FILL() throws IOException { + InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_1D_FILL), 6, + TIFFExtension.COMPRESSION_CCITT_T4, 1, TIFFExtension.GROUP3OPT_FILLBITS); - byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData(); - byte[] bytes = new byte[imageData.length]; - new DataInputStream(stream).readFully(bytes); - assertArrayEquals(imageData, bytes); - } + byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData(); + byte[] bytes = new byte[imageData.length]; + new DataInputStream(stream).readFully(bytes); + assertArrayEquals(imageData, bytes); + } - @Test - public void testDecodeType3_2D() throws IOException { - InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_2D), 6, - TIFFExtension.COMPRESSION_CCITT_T4, 1, TIFFExtension.GROUP3OPT_2DENCODING); + @Test + public void testDecodeType3_2D() throws IOException { + InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_2D), 6, + TIFFExtension.COMPRESSION_CCITT_T4, 1, TIFFExtension.GROUP3OPT_2DENCODING); - byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData(); - byte[] bytes = new byte[imageData.length]; - new DataInputStream(stream).readFully(bytes); - assertArrayEquals(imageData, bytes); - } + byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData(); + byte[] bytes = new byte[imageData.length]; + new DataInputStream(stream).readFully(bytes); + assertArrayEquals(imageData, bytes); + } - @Test - public void testDecodeType3_2D_FILL() throws IOException { - InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_2D_FILL), 6, - TIFFExtension.COMPRESSION_CCITT_T4, 1, - TIFFExtension.GROUP3OPT_2DENCODING | TIFFExtension.GROUP3OPT_FILLBITS); + @Test + public void testDecodeType3_2D_FILL() throws IOException { + InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_2D_FILL), 6, + TIFFExtension.COMPRESSION_CCITT_T4, 1, + TIFFExtension.GROUP3OPT_2DENCODING | TIFFExtension.GROUP3OPT_FILLBITS); - byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData(); - byte[] bytes = new byte[imageData.length]; - new DataInputStream(stream).readFully(bytes); - assertArrayEquals(imageData, bytes); - } - - @Test - public void testDecodeType4() throws IOException { - InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G4), 6, - TIFFExtension.COMPRESSION_CCITT_T6, 1, - 0L); + byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData(); + byte[] bytes = new byte[imageData.length]; + new DataInputStream(stream).readFully(bytes); + assertArrayEquals(imageData, bytes); + } - byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData(); - byte[] bytes = new byte[imageData.length]; - new DataInputStream(stream).readFully(bytes); - assertArrayEquals(imageData, bytes); - } + @Test + public void testDecodeType4() throws IOException { + InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G4), 6, + TIFFExtension.COMPRESSION_CCITT_T6, 1, 0L); + + byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData(); + byte[] bytes = new byte[imageData.length]; + new DataInputStream(stream).readFully(bytes); + assertArrayEquals(imageData, bytes); + } } From 37e1723891aefcf2c34bab75f9f2cf561af342b7 Mon Sep 17 00:00:00 2001 From: Schmidor Date: Thu, 2 Jul 2015 23:37:06 +0200 Subject: [PATCH 3/6] CITT Group 3/4 Support - libTiff generated files which are the test datastream sources --- .../src/test/resources/tiff/ccitt/group3_1d.tif | Bin 0 -> 282 bytes .../test/resources/tiff/ccitt/group3_1d_fill.tif | Bin 0 -> 284 bytes .../src/test/resources/tiff/ccitt/group3_2d.tif | Bin 0 -> 282 bytes .../test/resources/tiff/ccitt/group3_2d_fill.tif | Bin 0 -> 282 bytes .../resources/tiff/ccitt/group3_2d_lsb2msb.tif | Bin 0 -> 282 bytes .../src/test/resources/tiff/ccitt/group4.tif | Bin 0 -> 266 bytes 6 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_1d.tif create mode 100644 imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_1d_fill.tif create mode 100644 imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_2d.tif create mode 100644 imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_2d_fill.tif create mode 100644 imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_2d_lsb2msb.tif create mode 100644 imageio/imageio-tiff/src/test/resources/tiff/ccitt/group4.tif diff --git a/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_1d.tif b/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_1d.tif new file mode 100644 index 0000000000000000000000000000000000000000..5a87ee3d79892cf272eaf990188e6824802e4093 GIT binary patch literal 282 zcmebD)M5}}00Id=27@^ai53j|9tbcnFfubR0tMNCm=Vfm0kWB(Y>*r?l+6rM2W119 z3|vTR1Q}Vt=5PSTg^@#tib9149^}D2Q%2^i=~yC+v^|^Bs(hg~8%!X=)&P!OWSQAlioO II><{70IawaegFUf literal 0 HcmV?d00001 diff --git a/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_1d_fill.tif b/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_1d_fill.tif new file mode 100644 index 0000000000000000000000000000000000000000..e1ce7e062c5f51dc95cbb9680f0b27e289761e82 GIT binary patch literal 284 zcmebD)M5~000PFA2T)XdfI$GrXJ%jo3bTQvpllW(n+eJW$uUFO%pi48Hjv4{g``H1 zkp*lH2T)uHNn8|(Ee2(S%om5M=LNE5kkqI^#X)K`f#OU+8t5_xJ4RM8`woz24`hSf z`vxQh1P|PpMSzq8Ll~bZh;BIaRRcsP?2rTV9gK~I!QyFYY9M;S%$b}Z+J@^o$V(0Y D+c*}D literal 0 HcmV?d00001 diff --git a/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_2d.tif b/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_2d.tif new file mode 100644 index 0000000000000000000000000000000000000000..9d0a4ff95f0cb14d1509b695c2bf6c771ac262db GIT binary patch literal 282 zcmebD)M5}}00J3x25}G}bbvvCfq{{kfe|Rk2E>d|HVcr=1Z9Kdn4xTDkUA(E$YkI` zQX|O70yc*OC@zE~E{en!gR(*9i$m3O1KBc2YE+=&AbT}|;!Hpq=rRU7MpiKU7LaET zWP{xM3M2&t58RkR9&=y_;}ZqZ4TrvJfaruBa$vrLv9T~%JS|NPL@$^*lM_VSa9sy^ G$pHYKKoW!i literal 0 HcmV?d00001 diff --git a/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_2d_fill.tif b/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_2d_fill.tif new file mode 100644 index 0000000000000000000000000000000000000000..70518f645ca29757a7993fa88b97c9f2906189eb GIT binary patch literal 282 zcmebD)M5}}00PEC1&jqCLWe;B$YW+;1PZc&B%o{-Ae#xw2FWo)*~}nyP&Sasz=fnn zkdXy!4hK+N2uWNNi7f_YgUlC)s^WsuaUK*d>s;+jBlCLj%T8G{`oE0}!?$g>Bs zLGFD8k^+JUZp02Qz~J0|O&710zt74Tu?`Y!)D!3Cae^F+G8^bJ20KPpF#8se zXAfk9-1`b71q2V=m_Z(MU0J)_UfdBvi literal 0 HcmV?d00001 diff --git a/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group4.tif b/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group4.tif new file mode 100644 index 0000000000000000000000000000000000000000..aa06b4321f0daa44e0b9fdba4d7bddf8bbc8b0c0 GIT binary patch literal 266 zcmebD)M5}|U|?Vo|JuO8(7?dYz`)4Nzz7s!17b!fn+3>bg0ew!%t+#FP&QDQfeT5E zAR`Od91ftk5R$kk5?c()2AMAoRnHD&%OI)I1hSccVnEk0*fFw#*{6U!dmtO+##HsIQv=ZpX3pdU(KcMynL#uF_?;5k literal 0 HcmV?d00001 From 62862d835a1db490af12dbadd8748378d5db0776 Mon Sep 17 00:00:00 2001 From: Schmidor Date: Sat, 4 Jul 2015 00:13:27 +0200 Subject: [PATCH 4/6] CITT Group 3/4 Support - corrected start index in 2d decoding --- .../imageio/plugins/tiff/CCITTFaxDecoderStream.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index ae5eb26e..34a0e9e3 100644 --- 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 @@ -154,7 +154,7 @@ final class CCITTFaxDecoderStream extends FilterInputStream { } boolean white = true; - int index = -1; + int index = 0; changesCurrentRowCount = 0; mode: while (index < columns) { // read mode From 1e6227bee59781a8180a0929cda7ce9e7fb96da3 Mon Sep 17 00:00:00 2001 From: Schmidor Date: Sat, 4 Jul 2015 00:42:09 +0200 Subject: [PATCH 5/6] CCITT FAX Encoding: Support FillOrder Reversed --- .../imageio/plugins/tiff/CCITTFaxDecoderStream.java | 11 +++++++++-- .../plugins/tiff/CCITTFaxDecoderStreamTest.java | 11 +++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) 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 index 34a0e9e3..cd149a31 100644 --- 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 @@ -33,6 +33,7 @@ import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; +import com.twelvemonkeys.imageio.metadata.exif.TIFF; import com.twelvemonkeys.lang.Validate; /** @@ -345,8 +346,14 @@ final class CCITTFaxDecoderStream extends FilterInputStream { } bufferPos = 0; } - - boolean isSet = ((buffer >> (7 - bufferPos)) & 1) == 1; + + boolean isSet; + if(fillOrder == TIFFBaseline.FILL_LEFT_TO_RIGHT){ + isSet = ((buffer >> (7 - bufferPos)) & 1) == 1; + }else{ + isSet = ((buffer >> (bufferPos)) & 1) == 1; + } + bufferPos++; if (bufferPos > 7) bufferPos = -1; diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStreamTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStreamTest.java index 0139f9d1..b7c9ac87 100644 --- a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStreamTest.java +++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStreamTest.java @@ -179,6 +179,17 @@ public class CCITTFaxDecoderStreamTest { assertArrayEquals(imageData, bytes); } + @Test + public void testDecodeType3_2D_REVERSED() throws IOException { + InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_2D_lsb2msb), 6, + TIFFExtension.COMPRESSION_CCITT_T4, 2, TIFFExtension.GROUP3OPT_2DENCODING); + + byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData(); + byte[] bytes = new byte[imageData.length]; + new DataInputStream(stream).readFully(bytes); + assertArrayEquals(imageData, bytes); + } + @Test public void testDecodeType4() throws IOException { InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G4), 6, From 6facef3142c4fc541ca4e37d31a880aab4568958 Mon Sep 17 00:00:00 2001 From: Schmidor Date: Sun, 5 Jul 2015 17:57:04 +0200 Subject: [PATCH 6/6] CCITTFax: Output Zero for White --- .../plugins/tiff/CCITTFaxDecoderStream.java | 14 +++++++------- .../plugins/tiff/CCITTFaxDecoderStreamTest.java | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) 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 index cd149a31..a495c240 100644 --- 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 @@ -262,13 +262,13 @@ final class CCITTFaxDecoderStream extends FilterInputStream { int byteIndex = index / 8; while (index % 8 != 0 && (nextChange - index) > 0) { - decodedRow[byteIndex] |= (white ? 1 << (7 - ((index) % 8)) : 0); + decodedRow[byteIndex] |= (white ? 0 : 1 << (7 - ((index) % 8))); index++; } if (index % 8 == 0) { byteIndex = index / 8; - final byte value = (byte) (white ? 0xff : 0x00); + final byte value = (byte) (white ? 0x00 : 0xff); while ((nextChange - index) > 7) { decodedRow[byteIndex] = value; @@ -281,7 +281,7 @@ final class CCITTFaxDecoderStream extends FilterInputStream { if (index % 8 == 0) { decodedRow[byteIndex] = 0; } - decodedRow[byteIndex] |= (white ? 1 << (7 - ((index) % 8)) : 0); + decodedRow[byteIndex] |= (white ? 0 : 1 << (7 - ((index) % 8))); index++; } @@ -346,14 +346,14 @@ final class CCITTFaxDecoderStream extends FilterInputStream { } bufferPos = 0; } - + boolean isSet; - if(fillOrder == TIFFBaseline.FILL_LEFT_TO_RIGHT){ + if (fillOrder == TIFFBaseline.FILL_LEFT_TO_RIGHT) { isSet = ((buffer >> (7 - bufferPos)) & 1) == 1; - }else{ + } else { isSet = ((buffer >> (bufferPos)) & 1) == 1; } - + bufferPos++; if (bufferPos > 7) bufferPos = -1; diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStreamTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStreamTest.java index b7c9ac87..31189ff0 100644 --- a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStreamTest.java +++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStreamTest.java @@ -116,11 +116,11 @@ public class CCITTFaxDecoderStreamTest { image = new BufferedImage(6, 4, BufferedImage.TYPE_BYTE_BINARY); for (int y = 0; y < 4; y++) { for (int x = 0; x < 6; x++) { - image.setRGB(x, y, x == 3 ? 0xff000000 : 0xffffffff); + image.setRGB(x, y, x != 3 ? 0xff000000 : 0xffffffff); } } - image.setRGB(2, 3, 0xff000000); + image.setRGB(2, 3, 0xffffffff); } @Test