mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-04 20:15:28 -04:00
Merge pull request #148 from Schmidor/master
TMI-61: CCITT Group 3/4 Fax Decoder
This commit is contained in:
commit
94f2b81d2a
@ -140,6 +140,9 @@ public interface TIFF {
|
|||||||
int TAG_JPEG_INTERCHANGE_FORMAT = 513;
|
int TAG_JPEG_INTERCHANGE_FORMAT = 513;
|
||||||
int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = 514;
|
int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = 514;
|
||||||
|
|
||||||
|
int TAG_GROUP3OPTIONS = 292;
|
||||||
|
int TAG_GROUP4OPTIONS = 293;
|
||||||
|
|
||||||
/// C. Tags relating to image data characteristics
|
/// C. Tags relating to image data characteristics
|
||||||
|
|
||||||
int TAG_TRANSFER_FUNCTION = 301;
|
int TAG_TRANSFER_FUNCTION = 301;
|
||||||
|
@ -28,22 +28,24 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.tiff;
|
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||||
|
|
||||||
import com.twelvemonkeys.lang.Validate;
|
|
||||||
|
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.FilterInputStream;
|
import java.io.FilterInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||||
|
import com.twelvemonkeys.lang.Validate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CCITT Modified Huffman RLE<!--, and hopefully soon: Group 3 (T4) and Group 4 (T6) fax compression-->.
|
* CCITT Modified Huffman RLE, Group 3 (T4) and Group 4 (T6) fax compression.
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author last modified by $Author: haraldk$
|
* @author last modified by $Author: haraldk$
|
||||||
* @version $Id: CCITTFaxDecoderStream.java,v 1.0 23.05.12 15:55 haraldk Exp$
|
* @version $Id: CCITTFaxDecoderStream.java,v 1.0 23.05.12 15:55 haraldk Exp$
|
||||||
*/
|
*/
|
||||||
final class CCITTFaxDecoderStream extends FilterInputStream {
|
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 int columns;
|
||||||
private final byte[] decodedRow;
|
private final byte[] decodedRow;
|
||||||
@ -51,32 +53,53 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
private int decodedLength;
|
private int decodedLength;
|
||||||
private int decodedPos;
|
private int decodedPos;
|
||||||
|
|
||||||
private int bitBuffer;
|
|
||||||
private int bitBufferLength;
|
|
||||||
|
|
||||||
// Need to take fill order into account (?) (use flip table?)
|
// Need to take fill order into account (?) (use flip table?)
|
||||||
private final int fillOrder;
|
private final int fillOrder;
|
||||||
private final int type;
|
private final int type;
|
||||||
|
|
||||||
private final int[] changes;
|
private int[] changesReferenceRow;
|
||||||
private int changesCount;
|
private int[] changesCurrentRow;
|
||||||
|
private int changesReferenceRowCount;
|
||||||
|
private int changesCurrentRowCount;
|
||||||
|
|
||||||
private static final int EOL_CODE = 0x01; // 12 bit
|
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"));
|
super(Validate.notNull(stream, "stream"));
|
||||||
|
|
||||||
this.columns = Validate.isTrue(columns > 0, columns, "width must be greater than 0");
|
this.columns = Validate.isTrue(columns > 0, columns, "width must be greater than 0");
|
||||||
// We know this is only used for b/w (1 bit)
|
// We know this is only used for b/w (1 bit)
|
||||||
this.decodedRow = new byte[(columns + 7) / 8];
|
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.type = type;
|
||||||
this.fillOrder = Validate.isTrue(fillOrder == 1, fillOrder, "Only fill order 1 supported: %s"); // TODO: Implement fillOrder == 2
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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?
|
Validate.isTrue(!optionUncompressed, optionUncompressed,
|
||||||
// -- If white run, start at 4 bits to determine length, if black, start at 2 bits
|
"CCITT GROUP 3/4 OPTION UNCOMPRESSED is not supported");
|
||||||
|
}
|
||||||
|
|
||||||
private void fetch() throws IOException {
|
private void fetch() throws IOException {
|
||||||
if (decodedPos >= decodedLength) {
|
if (decodedPos >= decodedLength) {
|
||||||
@ -84,14 +107,14 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
decodeRow();
|
decodeRow();
|
||||||
}
|
} catch (EOFException e) {
|
||||||
catch (EOFException e) {
|
|
||||||
// TODO: Rewrite to avoid throw/catch for normal flow...
|
// TODO: Rewrite to avoid throw/catch for normal flow...
|
||||||
if (decodedLength != 0) {
|
if (decodedLength != 0) {
|
||||||
throw e;
|
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;
|
decodedLength = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,141 +122,242 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void decodeRow() throws IOException {
|
private void decode1D() throws IOException {
|
||||||
resetBuffer();
|
|
||||||
|
|
||||||
boolean literalRun = true;
|
|
||||||
|
|
||||||
/*
|
|
||||||
if (type == TIFFExtension.COMPRESSION_CCITT_T4) {
|
|
||||||
int eol = readBits(12);
|
|
||||||
System.err.println("eol: " + eol);
|
|
||||||
while (eol != EOL_CODE) {
|
|
||||||
eol = readBits(1);
|
|
||||||
System.err.println("eol: " + eol);
|
|
||||||
// throw new IOException("Missing EOL");
|
|
||||||
}
|
|
||||||
|
|
||||||
literalRun = readBits(1) == 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
System.err.println("literalRun: " + literalRun);
|
|
||||||
*/
|
|
||||||
int index = 0;
|
int index = 0;
|
||||||
|
|
||||||
if (literalRun) {
|
|
||||||
changesCount = 0;
|
|
||||||
boolean white = true;
|
boolean white = true;
|
||||||
|
changesCurrentRowCount = 0;
|
||||||
do {
|
do {
|
||||||
int completeRun = 0;
|
int completeRun = 0;
|
||||||
|
|
||||||
int run;
|
|
||||||
do {
|
|
||||||
if (white) {
|
if (white) {
|
||||||
run = decodeRun(WHITE_CODES, WHITE_RUN_LENGTHS, 4);
|
completeRun = decodeRun(whiteRunTree);
|
||||||
}
|
} else {
|
||||||
else {
|
completeRun = decodeRun(blackRunTree);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
index += completeRun;
|
||||||
|
changesCurrentRow[changesCurrentRowCount++] = index;
|
||||||
// Flip color for next run
|
// Flip color for next run
|
||||||
white = !white;
|
white = !white;
|
||||||
}
|
} while (index < columns);
|
||||||
while (index < columns);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// non-literal run
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE && 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 = 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);
|
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(short[][] codes, short[][] runLengths, int minCodeSize) throws IOException {
|
private int decodeRun(Tree tree) throws IOException {
|
||||||
// TODO: Optimize...
|
int total = 0;
|
||||||
// Looping and comparing is the most straight-forward, but probably not the most effective way...
|
|
||||||
int code = readBits(minCodeSize);
|
|
||||||
|
|
||||||
for (int bits = 0; bits < codes.length; bits++) {
|
|
||||||
short[] bitCodes = codes[bits];
|
|
||||||
|
|
||||||
for (int i = 0; i < bitCodes.length; i++) {
|
|
||||||
if (bitCodes[i] == code) {
|
|
||||||
// System.err.println("code: " + code);
|
|
||||||
|
|
||||||
// Code found, return matching run length
|
|
||||||
return runLengths[bits][i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No code found, read one more bit and try again
|
|
||||||
code = fillOrder == 1 ? (code << 1) | readBits(1) : readBits(1) << (bits + minCodeSize) | code;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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");
|
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() {
|
private void resetBuffer() {
|
||||||
for (int i = 0; i < decodedRow.length; i++) {
|
for (int i = 0; i < decodedRow.length; i++) {
|
||||||
decodedRow[i] = 0;
|
decodedRow[i] = 0;
|
||||||
}
|
}
|
||||||
|
while (true) {
|
||||||
bitBuffer = 0;
|
if (bufferPos == -1) {
|
||||||
bitBufferLength = 0;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int readBits(int bitCount) throws IOException {
|
try {
|
||||||
while (bitBufferLength < bitCount) {
|
boolean skip = readBit();
|
||||||
int read = in.read();
|
} catch (IOException e) {
|
||||||
if (read == -1) {
|
// 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");
|
throw new EOFException("Unexpected end of Huffman RLE stream");
|
||||||
}
|
}
|
||||||
|
bufferPos = 0;
|
||||||
int bits = read & 0xff;
|
|
||||||
bitBuffer = (bitBuffer << 8) | bits;
|
|
||||||
bitBufferLength += 8;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Take fill order into account
|
boolean isSet;
|
||||||
bitBufferLength -= bitCount;
|
if (fillOrder == TIFFBaseline.FILL_LEFT_TO_RIGHT) {
|
||||||
int result = bitBuffer >> bitBufferLength;
|
isSet = ((buffer >> (7 - bufferPos)) & 1) == 1;
|
||||||
bitBuffer &= (1 << bitBufferLength) - 1;
|
} else {
|
||||||
|
isSet = ((buffer >> (bufferPos)) & 1) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
bufferPos++;
|
||||||
|
if (bufferPos > 7)
|
||||||
|
bufferPos = -1;
|
||||||
|
return isSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -304,150 +428,251 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
throw new IOException("mark/reset not supported");
|
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 = {
|
static final short[][] BLACK_CODES = {
|
||||||
{ // 2 bits
|
{ // 2 bits
|
||||||
0x2, 0x3,
|
0x2, 0x3, },
|
||||||
},
|
|
||||||
{ // 3 bits
|
{ // 3 bits
|
||||||
0x2, 0x3,
|
0x2, 0x3, },
|
||||||
},
|
|
||||||
{ // 4 bits
|
{ // 4 bits
|
||||||
0x2, 0x3,
|
0x2, 0x3, },
|
||||||
},
|
|
||||||
{ // 5 bits
|
{ // 5 bits
|
||||||
0x3,
|
0x3, },
|
||||||
},
|
|
||||||
{ // 6 bits
|
{ // 6 bits
|
||||||
0x4, 0x5,
|
0x4, 0x5, },
|
||||||
},
|
|
||||||
{ // 7 bits
|
{ // 7 bits
|
||||||
0x4, 0x5, 0x7,
|
0x4, 0x5, 0x7, },
|
||||||
},
|
|
||||||
{ // 8 bits
|
{ // 8 bits
|
||||||
0x4, 0x7,
|
0x4, 0x7, },
|
||||||
},
|
|
||||||
{ // 9 bits
|
{ // 9 bits
|
||||||
0x18,
|
0x18, },
|
||||||
},
|
|
||||||
{ // 10 bits
|
{ // 10 bits
|
||||||
0x17, 0x18, 0x37, 0x8, 0xf,
|
0x17, 0x18, 0x37, 0x8, 0xf, },
|
||||||
},
|
|
||||||
{ // 11 bits
|
{ // 11 bits
|
||||||
0x17, 0x18, 0x28, 0x37, 0x67, 0x68, 0x6c, 0x8, 0xc, 0xd,
|
0x17, 0x18, 0x28, 0x37, 0x67, 0x68, 0x6c, 0x8, 0xc, 0xd, },
|
||||||
},
|
|
||||||
{ // 12 bits
|
{ // 12 bits
|
||||||
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1c, 0x1d, 0x1e, 0x1f, 0x24, 0x27, 0x28, 0x2b, 0x2c, 0x33,
|
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,
|
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,
|
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
|
{ // 13 bits
|
||||||
0x4a, 0x4b, 0x4c, 0x4d, 0x52, 0x53, 0x54, 0x55, 0x5a, 0x5b, 0x64, 0x65, 0x6c, 0x6d, 0x72, 0x73,
|
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 = {
|
static final short[][] BLACK_RUN_LENGTHS = {
|
||||||
{ // 2 bits
|
{ // 2 bits
|
||||||
3, 2,
|
3, 2, },
|
||||||
},
|
|
||||||
{ // 3 bits
|
{ // 3 bits
|
||||||
1, 4,
|
1, 4, },
|
||||||
},
|
|
||||||
{ // 4 bits
|
{ // 4 bits
|
||||||
6, 5,
|
6, 5, },
|
||||||
},
|
|
||||||
{ // 5 bits
|
{ // 5 bits
|
||||||
7,
|
7, },
|
||||||
},
|
|
||||||
{ // 6 bits
|
{ // 6 bits
|
||||||
9, 8,
|
9, 8, },
|
||||||
},
|
|
||||||
{ // 7 bits
|
{ // 7 bits
|
||||||
10, 11, 12,
|
10, 11, 12, },
|
||||||
},
|
|
||||||
{ // 8 bits
|
{ // 8 bits
|
||||||
13, 14,
|
13, 14, },
|
||||||
},
|
|
||||||
{ // 9 bits
|
{ // 9 bits
|
||||||
15,
|
15, },
|
||||||
},
|
|
||||||
{ // 10 bits
|
{ // 10 bits
|
||||||
16, 17, 0, 18, 64,
|
16, 17, 0, 18, 64, },
|
||||||
},
|
|
||||||
{ // 11 bits
|
{ // 11 bits
|
||||||
24, 25, 23, 22, 19, 20, 21, 1792, 1856, 1920,
|
24, 25, 23, 22, 19, 20, 21, 1792, 1856, 1920, },
|
||||||
},
|
|
||||||
{ // 12 bits
|
{ // 12 bits
|
||||||
1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560, 52, 55, 56, 59, 60, 320,
|
1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560, 52, 55, 56, 59, 60, 320, 384, 448, 53,
|
||||||
384, 448, 53, 54, 50, 51, 44, 45, 46, 47, 57, 58, 61, 256, 48, 49,
|
54, 50, 51, 44, 45, 46, 47, 57, 58, 61, 256, 48, 49, 62, 63, 30, 31, 32, 33, 40, 41, 128, 192, 26,
|
||||||
62, 63, 30, 31, 32, 33, 40, 41, 128, 192, 26, 27, 28, 29, 34, 35,
|
27, 28, 29, 34, 35, 36, 37, 38, 39, 42, 43, },
|
||||||
36, 37, 38, 39, 42, 43,
|
|
||||||
},
|
|
||||||
{ // 13 bits
|
{ // 13 bits
|
||||||
640, 704, 768, 832, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 512, 576, 896, 960,
|
640, 704, 768, 832, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 512, 576, 896, 960, 1024, 1088,
|
||||||
1024, 1088, 1152, 1216,
|
1152, 1216, } };
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public static final short[][] WHITE_CODES = {
|
public static final short[][] WHITE_CODES = {
|
||||||
{ // 4 bits
|
{ // 4 bits
|
||||||
0x7, 0x8, 0xb, 0xc, 0xe, 0xf,
|
0x7, 0x8, 0xb, 0xc, 0xe, 0xf, },
|
||||||
},
|
|
||||||
{ // 5 bits
|
{ // 5 bits
|
||||||
0x12, 0x13, 0x14, 0x1b, 0x7, 0x8,
|
0x12, 0x13, 0x14, 0x1b, 0x7, 0x8, },
|
||||||
},
|
|
||||||
{ // 6 bits
|
{ // 6 bits
|
||||||
0x17, 0x18, 0x2a, 0x2b, 0x3, 0x34, 0x35, 0x7, 0x8,
|
0x17, 0x18, 0x2a, 0x2b, 0x3, 0x34, 0x35, 0x7, 0x8, },
|
||||||
},
|
|
||||||
{ // 7 bits
|
{ // 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
|
{ // 8 bits
|
||||||
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1a, 0x1b, 0x2, 0x24, 0x25, 0x28, 0x29, 0x2a, 0x2b, 0x2c,
|
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1a, 0x1b, 0x2, 0x24, 0x25, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d,
|
||||||
0x2d, 0x3, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x4, 0x4a, 0x4b, 0x5, 0x52, 0x53, 0x54, 0x55,
|
0x3, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x4, 0x4a, 0x4b, 0x5, 0x52, 0x53, 0x54, 0x55, 0x58, 0x59,
|
||||||
0x58, 0x59, 0x5a, 0x5b, 0x64, 0x65, 0x67, 0x68, 0xa, 0xb,
|
0x5a, 0x5b, 0x64, 0x65, 0x67, 0x68, 0xa, 0xb, },
|
||||||
},
|
|
||||||
{ // 9 bits
|
{ // 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
|
{ // 10 bits
|
||||||
},
|
},
|
||||||
{ // 11 bits
|
{ // 11 bits
|
||||||
0x8, 0xc, 0xd,
|
0x8, 0xc, 0xd, },
|
||||||
},
|
|
||||||
{ // 12 bits
|
{ // 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 = {
|
public static final short[][] WHITE_RUN_LENGTHS = {
|
||||||
{ // 4 bits
|
{ // 4 bits
|
||||||
2, 3, 4, 5, 6, 7,
|
2, 3, 4, 5, 6, 7, },
|
||||||
},
|
|
||||||
{ // 5 bits
|
{ // 5 bits
|
||||||
128, 8, 9, 64, 10, 11,
|
128, 8, 9, 64, 10, 11, },
|
||||||
},
|
|
||||||
{ // 6 bits
|
{ // 6 bits
|
||||||
192, 1664, 16, 17, 13, 14, 15, 1, 12,
|
192, 1664, 16, 17, 13, 14, 15, 1, 12, },
|
||||||
},
|
|
||||||
{ // 7 bits
|
{ // 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
|
{ // 8 bits
|
||||||
33, 34, 35, 36, 37, 38, 31, 32, 29, 53, 54, 39, 40, 41, 42, 43,
|
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,
|
||||||
44, 30, 61, 62, 63, 0, 320, 384, 45, 59, 60, 46, 49, 50, 51,
|
59, 60, 46, 49, 50, 51, 52, 55, 56, 57, 58, 448, 512, 640, 576, 47, 48, },
|
||||||
52, 55, 56, 57, 58, 448, 512, 640, 576, 47, 48,
|
{ // 9
|
||||||
},
|
// bits
|
||||||
{ // 9 bits
|
1472, 1536, 1600, 1728, 704, 768, 832, 896, 960, 1024, 1088, 1152, 1216, 1280, 1344, 1408, },
|
||||||
1472, 1536, 1600, 1728, 704, 768, 832, 896, 960, 1024, 1088, 1152, 1216, 1280, 1344, 1408,
|
|
||||||
},
|
|
||||||
{ // 10 bits
|
{ // 10 bits
|
||||||
},
|
},
|
||||||
{ // 11 bits
|
{ // 11 bits
|
||||||
1792, 1856, 1920,
|
1792, 1856, 1920, },
|
||||||
},
|
|
||||||
{ // 12 bits
|
{ // 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@ -92,4 +92,8 @@ interface TIFFExtension {
|
|||||||
int ORIENTATION_RIGHTBOT = 7;
|
int ORIENTATION_RIGHTBOT = 7;
|
||||||
int ORIENTATION_LEFTBOT = 8;
|
int ORIENTATION_LEFTBOT = 8;
|
||||||
|
|
||||||
|
int GROUP3OPT_2DENCODING = 1;
|
||||||
|
int GROUP3OPT_UNCOMPRESSED = 2;
|
||||||
|
int GROUP3OPT_FILLBITS = 4;
|
||||||
|
int GROUP4OPT_UNCOMPRESSED = 2;
|
||||||
}
|
}
|
||||||
|
@ -604,9 +604,9 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE:
|
case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE:
|
||||||
// CCITT modified Huffman
|
// CCITT modified Huffman
|
||||||
// Additionally, the specification defines these values as part of the TIFF extensions:
|
// 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
|
// CCITT Group 3 fax encoding
|
||||||
// case TIFFExtension.COMPRESSION_CCITT_T6:
|
case TIFFExtension.COMPRESSION_CCITT_T6:
|
||||||
// CCITT Group 4 fax encoding
|
// CCITT Group 4 fax encoding
|
||||||
|
|
||||||
int[] yCbCrSubsampling = null;
|
int[] yCbCrSubsampling = null;
|
||||||
@ -1028,10 +1028,6 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
// Additionally, the specification defines these values as part of the TIFF extensions:
|
// 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
|
// Known, but unsupported compression types
|
||||||
case TIFFCustom.COMPRESSION_NEXT:
|
case TIFFCustom.COMPRESSION_NEXT:
|
||||||
@ -1332,9 +1328,11 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
case TIFFExtension.COMPRESSION_DEFLATE:
|
case TIFFExtension.COMPRESSION_DEFLATE:
|
||||||
return new InflaterInputStream(stream, new Inflater(), 1024);
|
return new InflaterInputStream(stream, new Inflater(), 1024);
|
||||||
case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE:
|
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:
|
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:
|
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:
|
default:
|
||||||
throw new IllegalArgumentException("Unsupported TIFF compression: " + compression);
|
throw new IllegalArgumentException("Unsupported TIFF compression: " + compression);
|
||||||
}
|
}
|
||||||
|
@ -45,29 +45,63 @@ import static org.junit.Assert.*;
|
|||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author last modified by $Author: haraldk$
|
* @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 {
|
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?)
|
// TODO: Better tests (full A4 width scan lines?)
|
||||||
|
|
||||||
// From http://www.mikekohn.net/file_formats/tiff.php
|
// From http://www.mikekohn.net/file_formats/tiff.php
|
||||||
static final byte[] DATA_TYPE_2 = {
|
static final byte[] DATA_TYPE_2 = { (byte) 0x84, (byte) 0xe0, // 10000100
|
||||||
(byte) 0x84, (byte) 0xe0, // 10000100 11100000
|
// 11100000
|
||||||
(byte) 0x84, (byte) 0xe0, // 10000100 11100000
|
(byte) 0x84, (byte) 0xe0, // 10000100 11100000
|
||||||
(byte) 0x84, (byte) 0xe0, // 10000100 11100000
|
(byte) 0x84, (byte) 0xe0, // 10000100 11100000
|
||||||
(byte) 0x7d, (byte) 0xc0, // 01111101 11000000
|
(byte) 0x7d, (byte) 0xc0, // 01111101 11000000
|
||||||
};
|
};
|
||||||
|
|
||||||
static final byte[] DATA_TYPE_3 = {
|
static final byte[] DATA_TYPE_3 = { 0x00, 0x01, (byte) 0xc2, 0x70, // 00000000
|
||||||
0x00, 0x01, (byte) 0xc2, 0x70,
|
// 00000001
|
||||||
0x00, 0x01, 0x70,
|
// 11000010
|
||||||
0x01,
|
// 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 = {
|
// 001 00110101 10 000010 1 1 1 1 1 1 1 1 1 1 010 11 (000000 padding)
|
||||||
0x26, (byte) 0xb0, 95, (byte) 0xfa, (byte) 0xc0
|
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):
|
// Image should be (6 x 4):
|
||||||
@ -82,85 +116,88 @@ public class CCITTFaxDecoderStreamTest {
|
|||||||
image = new BufferedImage(6, 4, BufferedImage.TYPE_BYTE_BINARY);
|
image = new BufferedImage(6, 4, BufferedImage.TYPE_BYTE_BINARY);
|
||||||
for (int y = 0; y < 4; y++) {
|
for (int y = 0; y < 4; y++) {
|
||||||
for (int x = 0; x < 6; x++) {
|
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
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDecodeType2() throws IOException {
|
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[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
|
||||||
byte[] bytes = new byte[imageData.length];
|
byte[] bytes = new byte[imageData.length];
|
||||||
new DataInputStream(stream).readFully(bytes);
|
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);
|
assertArrayEquals(imageData, bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test
|
||||||
public void testDecodeType3() throws IOException {
|
public void testDecodeType3_1D() throws IOException {
|
||||||
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_TYPE_3), 6, TIFFExtension.COMPRESSION_CCITT_T4, 1);
|
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_1D), 6,
|
||||||
|
TIFFExtension.COMPRESSION_CCITT_T4, 1, 0L);
|
||||||
|
|
||||||
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
|
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
|
||||||
byte[] bytes = new byte[imageData.length];
|
byte[] bytes = new byte[imageData.length];
|
||||||
DataInputStream dataInput = new DataInputStream(stream);
|
new DataInputStream(stream).readFully(bytes);
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
assertArrayEquals(imageData, 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 {
|
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[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
|
||||||
byte[] bytes = new byte[imageData.length];
|
byte[] bytes = new byte[imageData.length];
|
||||||
DataInputStream dataInput = new DataInputStream(stream);
|
new DataInputStream(stream).readFully(bytes);
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
assertArrayEquals(imageData, bytes);
|
assertArrayEquals(imageData, bytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BIN
imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_1d.tif
Normal file
BIN
imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_1d.tif
Normal file
Binary file not shown.
Binary file not shown.
BIN
imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_2d.tif
Normal file
BIN
imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_2d.tif
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
imageio/imageio-tiff/src/test/resources/tiff/ccitt/group4.tif
Normal file
BIN
imageio/imageio-tiff/src/test/resources/tiff/ccitt/group4.tif
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user