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..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
@@ -28,22 +28,24 @@
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.imageio.metadata.exif.TIFF;
+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;
@@ -51,47 +53,68 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
private int decodedLength;
private int decodedPos;
- private int bitBuffer;
- private int bitBufferLength;
-
// Need to take fill order into account (?) (use flip table?)
private final int fillOrder;
private final int type;
- private final int[] changes;
- private int changesCount;
+ private int[] changesReferenceRow;
+ private int[] changesCurrentRow;
+ private int changesReferenceRowCount;
+ private int changesCurrentRowCount;
private static final int EOL_CODE = 0x01; // 12 bit
- public CCITTFaxDecoderStream(final InputStream stream, final int columns, final int type, final int fillOrder) {
+ 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 = Validate.isTrue(type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, type, "Only CCITT Modified Huffman RLE compression (2) supported: %s"); // TODO: Implement group 3 and 4
- this.fillOrder = Validate.isTrue(fillOrder == 1, fillOrder, "Only fill order 1 supported: %s"); // TODO: Implement fillOrder == 2
+ this.type = type;
+ this.fillOrder = fillOrder;// Validate.isTrue(fillOrder == 1, fillOrder,
+ // "Only fill order 1 supported: %s"); //
+ // TODO: Implement fillOrder == 2
- this.changes = new int[columns];
+ 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");
}
- // IDEA: Would it be faster to keep all bit combos of each length (>=2) that is NOT a code, to find bit length, then look up value in table?
- // -- If white run, start at 4 bits to determine length, if black, start at 2 bits
-
private void fetch() throws IOException {
if (decodedPos >= decodedLength) {
decodedLength = 0;
try {
decodeRow();
- }
- catch (EOFException e) {
+ } 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
+ // ..otherwise, just client code trying to read past the end of
+ // stream
decodedLength = -1;
}
@@ -99,141 +122,242 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
}
}
- private void decodeRow() throws IOException {
- resetBuffer();
-
- boolean literalRun = true;
-
- /*
- if (type == TIFFExtension.COMPRESSION_CCITT_T4) {
- int eol = readBits(12);
- System.err.println("eol: " + eol);
- while (eol != EOL_CODE) {
- eol = readBits(1);
- System.err.println("eol: " + eol);
-// throw new IOException("Missing EOL");
- }
-
- literalRun = readBits(1) == 1;
- }
-
- System.err.println("literalRun: " + literalRun);
- */
+ private void decode1D() throws IOException {
int index = 0;
-
- if (literalRun) {
- changesCount = 0;
- boolean white = true;
-
- do {
- int completeRun = 0;
-
- int run;
- do {
- if (white) {
- run = decodeRun(WHITE_CODES, WHITE_RUN_LENGTHS, 4);
- }
- else {
- run = decodeRun(BLACK_CODES, BLACK_RUN_LENGTHS, 2);
- }
-
- completeRun += run;
- }
- while (run >= 64); // Additional makeup codes are packed into both b/w codes, terminating codes are < 64 bytes
-
- changes[changesCount++] = index + completeRun;
-
-// System.err.printf("%s run: %d\n", white ? "white" : "black", run);
-
- // TODO: Optimize with lookup for 0-7 bits?
- // Fill bits to byte boundary...
- while (index % 8 != 0 && completeRun-- > 0) {
- decodedRow[index++ / 8] |= (white ? 1 << 8 - (index % 8) : 0);
- }
-
- // ...then fill complete bytes to either 0xff or 0x00...
- if (index % 8 == 0) {
- final byte value = (byte) (white ? 0xff : 0x00);
-
- while (completeRun > 7) {
- decodedRow[index / 8] = value;
- completeRun -= 8;
- index += 8;
- }
- }
-
- // ...finally fill any remaining bits
- while (completeRun-- > 0) {
- decodedRow[index++ / 8] |= (white ? 1 << 8 - (index % 8) : 0);
- }
-
- // Flip color for next run
- white = !white;
+ boolean white = true;
+ changesCurrentRowCount = 0;
+ do {
+ int completeRun = 0;
+ if (white) {
+ completeRun = decodeRun(whiteRunTree);
+ } else {
+ completeRun = decodeRun(blackRunTree);
}
- while (index < columns);
- }
- else {
- // non-literal run
+
+ 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;
}
- if (type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE && index != columns) {
+ boolean white = true;
+ int index = 0;
+ 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 ? 0 : 1 << (7 - ((index) % 8)));
+ index++;
+ }
+
+ if (index % 8 == 0) {
+ byteIndex = index / 8;
+ final byte value = (byte) (white ? 0x00 : 0xff);
+
+ while ((nextChange - index) > 7) {
+ decodedRow[byteIndex] = value;
+ index += 8;
+ ++byteIndex;
+ }
+ }
+
+ while ((nextChange - index) > 0) {
+ if (index % 8 == 0) {
+ decodedRow[byteIndex] = 0;
+ }
+ decodedRow[byteIndex] |= (white ? 0 : 1 << (7 - ((index) % 8)));
+ 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(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);
+ private int decodeRun(Tree tree) throws IOException {
+ int total = 0;
- for (int bits = 0; bits < codes.length; bits++) {
- short[] bitCodes = codes[bits];
+ 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");
- for (int i = 0; i < bitCodes.length; i++) {
- if (bitCodes[i] == code) {
-// System.err.println("code: " + code);
-
- // Code found, return matching run length
- return runLengths[bits][i];
+ if (n.isLeaf) {
+ total += n.value;
+ if (n.value < 64) {
+ return total;
+ } else {
+ n = tree.root;
+ continue;
}
}
-
- // No code found, read one more bit and try again
- code = fillOrder == 1 ? (code << 1) | readBits(1) : readBits(1) << (bits + minCodeSize) | code;
}
-
- throw new IOException("Unknown code in Huffman RLE stream");
}
private void resetBuffer() {
for (int i = 0; i < decodedRow.length; i++) {
decodedRow[i] = 0;
}
-
- bitBuffer = 0;
- bitBufferLength = 0;
- }
-
- private int readBits(int bitCount) throws IOException {
- while (bitBufferLength < bitCount) {
- int read = in.read();
- if (read == -1) {
- throw new EOFException("Unexpected end of Huffman RLE stream");
+ while (true) {
+ if (bufferPos == -1) {
+ return;
}
- int bits = read & 0xff;
- bitBuffer = (bitBuffer << 8) | bits;
- bitBufferLength += 8;
+ 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;
}
- // TODO: Take fill order into account
- bitBufferLength -= bitCount;
- int result = bitBuffer >> bitBufferLength;
- bitBuffer &= (1 << bitBufferLength) - 1;
+ boolean isSet;
+ if (fillOrder == TIFFBaseline.FILL_LEFT_TO_RIGHT) {
+ isSet = ((buffer >> (7 - bufferPos)) & 1) == 1;
+ } else {
+ isSet = ((buffer >> (bufferPos)) & 1) == 1;
+ }
- return result;
+ bufferPos++;
+ if (bufferPos > 7)
+ bufferPos = -1;
+ return isSet;
}
@Override
@@ -304,150 +428,251 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
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,
- },
+ 0x2, 0x3, },
{ // 3 bits
- 0x2, 0x3,
- },
+ 0x2, 0x3, },
{ // 4 bits
- 0x2, 0x3,
- },
+ 0x2, 0x3, },
{ // 5 bits
- 0x3,
- },
+ 0x3, },
{ // 6 bits
- 0x4, 0x5,
- },
+ 0x4, 0x5, },
{ // 7 bits
- 0x4, 0x5, 0x7,
- },
+ 0x4, 0x5, 0x7, },
{ // 8 bits
- 0x4, 0x7,
- },
+ 0x4, 0x7, },
{ // 9 bits
- 0x18,
- },
+ 0x18, },
{ // 10 bits
- 0x17, 0x18, 0x37, 0x8, 0xf,
- },
+ 0x17, 0x18, 0x37, 0x8, 0xf, },
{ // 11 bits
- 0x17, 0x18, 0x28, 0x37, 0x67, 0x68, 0x6c, 0x8, 0xc, 0xd,
- },
+ 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,
- },
+ 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,
- }
- };
+ 0x74, 0x75, 0x76, 0x77, } };
static final short[][] BLACK_RUN_LENGTHS = {
{ // 2 bits
- 3, 2,
- },
+ 3, 2, },
{ // 3 bits
- 1, 4,
- },
+ 1, 4, },
{ // 4 bits
- 6, 5,
- },
+ 6, 5, },
{ // 5 bits
- 7,
- },
+ 7, },
{ // 6 bits
- 9, 8,
- },
+ 9, 8, },
{ // 7 bits
- 10, 11, 12,
- },
+ 10, 11, 12, },
{ // 8 bits
- 13, 14,
- },
+ 13, 14, },
{ // 9 bits
- 15,
- },
+ 15, },
{ // 10 bits
- 16, 17, 0, 18, 64,
- },
+ 16, 17, 0, 18, 64, },
{ // 11 bits
- 24, 25, 23, 22, 19, 20, 21, 1792, 1856, 1920,
- },
+ 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,
- },
+ 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,
- }
- };
+ 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,
- },
+ 0x7, 0x8, 0xb, 0xc, 0xe, 0xf, },
{ // 5 bits
- 0x12, 0x13, 0x14, 0x1b, 0x7, 0x8,
- },
+ 0x12, 0x13, 0x14, 0x1b, 0x7, 0x8, },
{ // 6 bits
- 0x17, 0x18, 0x2a, 0x2b, 0x3, 0x34, 0x35, 0x7, 0x8,
- },
+ 0x17, 0x18, 0x2a, 0x2b, 0x3, 0x34, 0x35, 0x7, 0x8, },
{ // 7 bits
- 0x13, 0x17, 0x18, 0x24, 0x27, 0x28, 0x2b, 0x3, 0x37, 0x4, 0x8, 0xc,
- },
+ 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,
- },
+ 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,
- },
+ 0x98, 0x99, 0x9a, 0x9b, 0xcc, 0xcd, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, },
{ // 10 bits
},
{ // 11 bits
- 0x8, 0xc, 0xd,
- },
+ 0x8, 0xc, 0xd, },
{ // 12 bits
- 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1c, 0x1d, 0x1e, 0x1f,
- }
- };
+ 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,
- },
+ 2, 3, 4, 5, 6, 7, },
{ // 5 bits
- 128, 8, 9, 64, 10, 11,
- },
+ 128, 8, 9, 64, 10, 11, },
{ // 6 bits
- 192, 1664, 16, 17, 13, 14, 15, 1, 12,
- },
+ 192, 1664, 16, 17, 13, 14, 15, 1, 12, },
{ // 7 bits
- 26, 21, 28, 27, 18, 24, 25, 22, 256, 23, 20, 19,
- },
+ 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,
- },
+ 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,
- },
+ 1792, 1856, 1920, },
{ // 12 bits
- 1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560,
+ 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 994ffd4f..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
@@ -92,4 +92,8 @@ interface TIFFExtension {
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..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
@@ -45,29 +45,63 @@ 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 {
+ // 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 };
+
+ // 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 };
+
+ // 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 };
+
+ // group3_2d_fill.tif
+ static final byte[] DATA_G3_2D_FILL = { 0x00, 0x01, (byte) 0xC2, 0x70, 0x01, 0x70, 0x01, (byte) 0xC2, 0x70, 0x01,
+ 0x2C };
+
+ static final byte[] DATA_G3_2D_lsb2msb = { 0x00, 0x38, (byte) 0xE4, 0x00, (byte) 0xE8, 0x00, 0x38, (byte) 0xE4,
+ 0x00, 0x48, 0x03 };
+
+ // 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?)
// From http://www.mikekohn.net/file_formats/tiff.php
- static final byte[] DATA_TYPE_2 = {
- (byte) 0x84, (byte) 0xe0, // 10000100 11100000
+ 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,
- 0x00, 0x01, 0x70,
- 0x01,
+ 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_4 = {
- 0x26, (byte) 0xb0, 95, (byte) 0xfa, (byte) 0xc0
+ // 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):
@@ -82,85 +116,88 @@ 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);
- }
-
- @Test
- public void testReadCountType2() throws IOException {
- InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_TYPE_2), 6, TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 1);
-
- int count = 0;
- int read;
- while ((read = stream.read()) >= 0) {
- count++;
- }
-
- // Just make sure we'll have 4 bytes
- assertEquals(4, count);
-
- // Verify that we don't return arbitrary values
- assertEquals(-1, read);
+ image.setRGB(2, 3, 0xffffffff);
}
@Test
public void testDecodeType2() throws IOException {
- InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_TYPE_2), 6, TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 1);
+ 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);
-
-// 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);
}
- @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);
-
- for (int y = 0; y < image.getHeight(); y++) {
- System.err.println("y: " + y);
- dataInput.readFully(bytes, y * image.getWidth(), image.getWidth());
- }
-
-// 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);
-
+ new DataInputStream(stream).readFully(bytes);
assertArrayEquals(imageData, bytes);
}
- @Test(expected = IllegalArgumentException.class)
+ @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);
+ }
+
+ @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);
+ }
+
+ @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 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_TYPE_4), 6, TIFFExtension.COMPRESSION_CCITT_T6, 1);
+ 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];
- DataInputStream dataInput = new DataInputStream(stream);
-
- for (int y = 0; y < image.getHeight(); y++) {
- System.err.println("y: " + y);
- dataInput.readFully(bytes, y * image.getWidth(), image.getWidth());
- }
-
-// 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);
-
+ new DataInputStream(stream).readFully(bytes);
assertArrayEquals(imageData, bytes);
}
}
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 00000000..5a87ee3d
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_1d.tif differ
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 00000000..e1ce7e06
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_1d_fill.tif differ
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 00000000..9d0a4ff9
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_2d.tif differ
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 00000000..70518f64
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_2d_fill.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_2d_lsb2msb.tif b/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_2d_lsb2msb.tif
new file mode 100644
index 00000000..ff1dd6dc
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_2d_lsb2msb.tif differ
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 00000000..aa06b432
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group4.tif differ