mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-04 12:05:29 -04:00
Work in progress: CCITT Encoder
This commit is contained in:
parent
6facef3142
commit
bad9aebdee
@ -0,0 +1,213 @@
|
|||||||
|
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.lang.Validate;
|
||||||
|
|
||||||
|
public class CCITTFaxEncoderStream extends OutputStream {
|
||||||
|
|
||||||
|
private int currentBufferLength = 0;
|
||||||
|
private final byte[] inputBuffer;
|
||||||
|
private final int inputBufferLength;
|
||||||
|
private int columns;
|
||||||
|
|
||||||
|
private int[] changesCurrentRow;
|
||||||
|
private int[] changesReferenceRow;
|
||||||
|
private int changesCurrentRowLength = 0;
|
||||||
|
private int changesReferenceRowLength = 0;
|
||||||
|
private byte outputBuffer = 0;
|
||||||
|
private byte outputBufferBitLength = 0;
|
||||||
|
private int type;
|
||||||
|
private int fillOrder;
|
||||||
|
private boolean optionG32D;
|
||||||
|
private boolean optionG3Fill;
|
||||||
|
private boolean optionUncompressed;
|
||||||
|
private OutputStream stream;
|
||||||
|
|
||||||
|
public CCITTFaxEncoderStream(final OutputStream stream, final int columns, final int type, final int fillOrder,
|
||||||
|
final long options) {
|
||||||
|
|
||||||
|
this.stream = stream;
|
||||||
|
this.type = type;
|
||||||
|
this.columns = columns;
|
||||||
|
this.fillOrder = fillOrder;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
inputBufferLength = (columns + 7) / 8;
|
||||||
|
inputBuffer = new byte[inputBufferLength];
|
||||||
|
|
||||||
|
Validate.isTrue(!optionUncompressed, optionUncompressed,
|
||||||
|
"CCITT GROUP 3/4 OPTION UNCOMPRESSED is not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(int b) throws IOException {
|
||||||
|
inputBuffer[currentBufferLength] = (byte) b;
|
||||||
|
currentBufferLength++;
|
||||||
|
|
||||||
|
if (currentBufferLength == inputBufferLength) {
|
||||||
|
encodeRow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: when to write end EOLs, half filled buffer bytes etc. on end?
|
||||||
|
|
||||||
|
private void encodeRow() throws IOException {
|
||||||
|
changesReferenceRow = changesCurrentRow;
|
||||||
|
changesReferenceRowLength = changesCurrentRowLength;
|
||||||
|
changesCurrentRowLength = 0;
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
boolean white = true;
|
||||||
|
while (index < columns) {
|
||||||
|
int byteIndex = index / 8;
|
||||||
|
int bit = index % 8;
|
||||||
|
if ((((inputBuffer[byteIndex] >> (7 - bit)) & 1) == 1) != (!white)) {
|
||||||
|
changesCurrentRow[changesCurrentRowLength] = index;
|
||||||
|
changesCurrentRowLength++;
|
||||||
|
white = !white;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE:
|
||||||
|
encodeRowType2();
|
||||||
|
break;
|
||||||
|
case TIFFExtension.COMPRESSION_CCITT_T4:
|
||||||
|
encodeRowType4();
|
||||||
|
break;
|
||||||
|
case TIFFExtension.COMPRESSION_CCITT_T6:
|
||||||
|
encodeRowType6();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void encodeRowType2() throws IOException {
|
||||||
|
encode1D();
|
||||||
|
fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void encodeRowType4() throws IOException {
|
||||||
|
writeEOL();
|
||||||
|
if (optionG32D) {
|
||||||
|
// TODO decide whether 1d or 2d row, write k, encode
|
||||||
|
} else {
|
||||||
|
// TODO encode1d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void encodeRowType6() {
|
||||||
|
encode2D();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void encode1D() throws IOException {
|
||||||
|
int index = 0;
|
||||||
|
boolean white = true;
|
||||||
|
while (index < columns) {
|
||||||
|
int nextChange = columns;
|
||||||
|
for (int i = 0; i < changesCurrentRowLength; i++) {
|
||||||
|
if (index < changesCurrentRow[i]) {
|
||||||
|
nextChange = changesCurrentRow[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int runLength = nextChange - index;
|
||||||
|
|
||||||
|
int nonterm = runLength / 64;
|
||||||
|
Code[] codes = white ? WHITE_NONTERMINATING_CODES : BLACK_NONTERMINATING_CODES;
|
||||||
|
while (nonterm > 0) {
|
||||||
|
if (nonterm >= codes.length) {
|
||||||
|
write(codes[codes.length-1].code,codes[codes.length-1].length);
|
||||||
|
nonterm -= codes.length - 1;
|
||||||
|
} else {
|
||||||
|
write(codes[nonterm - 1].code,codes[nonterm - 1].length);
|
||||||
|
nonterm = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Code c = white ? WHITE_TERMINATING_CODES[runLength % 64] : BLACK_TERMINATING_CODES[runLength % 64];
|
||||||
|
write(c.code, c.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void encode2D() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void write(int code, int codeLength) throws IOException {
|
||||||
|
|
||||||
|
for (int i = 0; i < codeLength; i++) {
|
||||||
|
boolean codeBit = ((code >> (i)) & 1) == 1;
|
||||||
|
|
||||||
|
if (fillOrder == TIFFBaseline.FILL_LEFT_TO_RIGHT) {
|
||||||
|
outputBuffer |= (codeBit ? 1 << (7 - ((outputBufferBitLength) % 8)) : 0);
|
||||||
|
} else {
|
||||||
|
outputBuffer |= (codeBit ? 1 << (((outputBufferBitLength) % 8)) : 0);
|
||||||
|
}
|
||||||
|
outputBufferBitLength++;
|
||||||
|
|
||||||
|
if (outputBufferBitLength == 8) {
|
||||||
|
stream.write(outputBuffer);
|
||||||
|
clearOutputBuffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeEOL() throws IOException {
|
||||||
|
if (optionG3Fill) {
|
||||||
|
// Fill up so EOL ends on a byte-boundary
|
||||||
|
while (outputBufferBitLength != 4) {
|
||||||
|
write(0, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write(1, 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fill() throws IOException {
|
||||||
|
stream.write(outputBuffer);
|
||||||
|
clearOutputBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearOutputBuffer() {
|
||||||
|
outputBuffer = 0;
|
||||||
|
outputBufferBitLength = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Code {
|
||||||
|
public static Code create(int code, int length) {
|
||||||
|
Code c = new Code(code, length);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Code(int code, int length) {
|
||||||
|
this.code = code;
|
||||||
|
this.length = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int code;
|
||||||
|
final int length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Code[] WHITE_TERMINATING_CODES = {};
|
||||||
|
|
||||||
|
private Code[] WHITE_NONTERMINATING_CODES = {};
|
||||||
|
|
||||||
|
private Code[] BLACK_TERMINATING_CODES = {};
|
||||||
|
|
||||||
|
private Code[] BLACK_NONTERMINATING_CODES = {};
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user