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);
+ }
}