diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxEncoderStream.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxEncoderStream.java index 29823188..02c5eafc 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxEncoderStream.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxEncoderStream.java @@ -36,7 +36,7 @@ import com.twelvemonkeys.lang.Validate; /** * CCITT Modified Huffman RLE, Group 3 (T4) and Group 4 (T6) fax compression. * - * @author Harald Kuhr + * @author Oliver Schmidtmer * @author last modified by $Author$ * @version $Id$ */ @@ -96,6 +96,7 @@ public class CCITTFaxEncoderStream extends OutputStream { if (currentBufferLength == inputBufferLength) { encodeRow(); + currentBufferLength = 0; } } @@ -106,14 +107,18 @@ public class CCITTFaxEncoderStream extends OutputStream { @Override public void close() throws IOException { + if (type == TIFFExtension.COMPRESSION_CCITT_T6) { + writeEOL(); + writeEOL(); + } fill(); stream.close(); } - // TODO: when to write end EOLs, half filled buffer bytes etc. on end? - private void encodeRow() throws IOException { + int[] tmp = changesReferenceRow; changesReferenceRow = changesCurrentRow; + changesCurrentRow = tmp; changesReferenceRowLength = changesCurrentRowLength; changesCurrentRowLength = 0; @@ -151,7 +156,15 @@ public class CCITTFaxEncoderStream extends OutputStream { private void encodeRowType4() throws IOException { writeEOL(); if (optionG32D) { - // TODO decide whether 1d or 2d row, write k, encode + // do k=1 only on first line. Detect first line by missing reference + // line. + if (changesReferenceRowLength == 0) { + write(1, 1); + encode1D(); + } else { + write(0, 1); + encode2D(); + } } else { encode1D(); } @@ -168,53 +181,66 @@ public class CCITTFaxEncoderStream extends OutputStream { 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); + int[] nextChanges = getNextChanges(index); + int runLength = nextChanges[0] - index; + writeRun(runLength, white); + index += runLength; + white = !white; } } + private int[] getNextChanges(int pos) { + int[] result = new int[] { columns, columns }; + for (int i = 0; i < changesCurrentRowLength; i++) { + if (pos < changesCurrentRow[i]) { + result[0] = changesCurrentRow[i]; + if ((i + 1) < changesCurrentRowLength) { + result[1] = changesCurrentRow[i + 1]; + } + break; + } + } + + return result; + } + + private void writeRun(int runLength, boolean white) throws IOException { + 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() throws IOException { boolean white = true; - int lastChange = -1; - for (int i = 0; i < changesCurrentRowLength; i++) { // TODO - // columns-Basiert - // statt references - int nextChange = changesCurrentRow[i]; + int index = 0; // a0 + while (index < columns) { + int[] nextChanges = getNextChanges(index); // a1, a2 - int nextRef = getNextRefChange(lastChange); + int[] nextRefs = getNextRefChanges(index, white); // b1, b2 - int difference = nextRef - nextChange; - if (difference < -3) { - // next change nearer than nextRef, PMODE + int difference = nextChanges[0] - nextRefs[0]; + if (nextChanges[0] > nextRefs[1]) { + // PMODE write(1, 4); - lastChange = nextRef; - } else if (difference > 3) { - // next change farer than nextRef, hmode + index = nextRefs[1]; + } else if (difference > 3 || difference < -3) { + // HMODE write(1, 3); - // write runLength(white) - // write runLength(!white) - // i++; - // lastChange = ... + writeRun(nextChanges[0] - index, white); + writeRun(nextChanges[1] - nextChanges[0], !white); + index = nextChanges[1]; + } else { // VMODE switch (difference) { @@ -241,24 +267,29 @@ public class CCITTFaxEncoderStream extends OutputStream { break; } white = !white; + index = nextRefs[0] + difference; } } } - private int getNextRefChange(int currentRowChange) { - for (int i = 0; i < changesReferenceRowLength; i++) { - if (changesReferenceRow[i] > currentRowChange) { - return changesReferenceRow[i]; + private int[] getNextRefChanges(int a0, boolean white) { + int[] result = new int[] { columns, columns }; + for (int i = (white ? 0 : 1); i < changesReferenceRowLength; i += 2) { + if (changesReferenceRow[i] > a0) { + result[0] = changesReferenceRow[i]; + if ((i + 1) < changesReferenceRowLength) { + result[1] = changesReferenceRow[i + 1]; + } + break; } } - return columns; + return result; } private void write(int code, int codeLength) throws IOException { for (int i = 0; i < codeLength; i++) { - boolean codeBit = ((code >> (i)) & 1) == 1; - + boolean codeBit = ((code >> (codeLength - i - 1)) & 1) == 1; if (fillOrder == TIFFBaseline.FILL_LEFT_TO_RIGHT) { outputBuffer |= (codeBit ? 1 << (7 - ((outputBufferBitLength) % 8)) : 0); } else { @@ -271,6 +302,7 @@ public class CCITTFaxEncoderStream extends OutputStream { clearOutputBuffer(); } } + System.err.println(""); } private void writeEOL() throws IOException { diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxEncoderStreamTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxEncoderStreamTest.java index b609b765..63ea4c99 100644 --- a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxEncoderStreamTest.java +++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxEncoderStreamTest.java @@ -28,22 +28,51 @@ package com.twelvemonkeys.imageio.plugins.tiff; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferByte; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.OutputStream; + +import org.junit.Before; import org.junit.Test; import com.twelvemonkeys.imageio.plugins.tiff.CCITTFaxEncoderStream.Code; -import java.io.IOException; -import static org.junit.Assert.*; - /** * CCITTFaxEncoderStreamTest * - * @author Harald Kuhr + * @author Oliver Schmidtmer * @author last modified by $Author$ * @version $Id$ */ public class CCITTFaxEncoderStreamTest { + // Image should be (6 x 4): + // 1 1 1 0 1 1 x x + // 1 1 1 0 1 1 x x + // 1 1 1 0 1 1 x x + // 1 1 0 0 1 1 x x + BufferedImage image; + + @Before + public void init() { + image = new BufferedImage(6, 4, BufferedImage.TYPE_BYTE_BINARY); + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 6; x++) { + image.setRGB(x, y, x != 3 ? 0xff000000 : 0xffffffff); + } + } + + image.setRGB(2, 3, 0xffffffff); + } + @Test public void testBuildCodes() throws IOException { assertTrue(CCITTFaxEncoderStream.WHITE_TERMINATING_CODES.length == 64); @@ -63,4 +92,46 @@ public class CCITTFaxEncoderStreamTest { assertNotNull(code); } } + + @Test + public void testType2() throws IOException { + testImage(TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 1, 0L); + } + + @Test + public void testType4() throws IOException { + testImage(TIFFExtension.COMPRESSION_CCITT_T4, 1, 0L); + testImage(TIFFExtension.COMPRESSION_CCITT_T4, 1, TIFFExtension.GROUP3OPT_FILLBITS); + testImage(TIFFExtension.COMPRESSION_CCITT_T4, 1, TIFFExtension.GROUP3OPT_2DENCODING); + testImage(TIFFExtension.COMPRESSION_CCITT_T4, 1, + TIFFExtension.GROUP3OPT_FILLBITS | TIFFExtension.GROUP3OPT_2DENCODING); + } + + @Test + public void testType6() throws IOException { + testImage(TIFFExtension.COMPRESSION_CCITT_T6, 1, 0L); + } + + @Test + public void restReversedFillOrder() throws IOException { + testImage(TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 2, 0L); + testImage(TIFFExtension.COMPRESSION_CCITT_T6, 2, 0L); + } + + private void testImage(int type, int fillOrder, long options) throws IOException { + byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData(); + byte[] redecodedData = new byte[imageData.length]; + ByteArrayOutputStream imageOutput = new ByteArrayOutputStream(); + OutputStream outputSteam = new CCITTFaxEncoderStream(imageOutput, 6, type, fillOrder, options); + outputSteam.write(imageData); + outputSteam.close(); + byte[] encodedData = imageOutput.toByteArray(); + + CCITTFaxDecoderStream inputStream = new CCITTFaxDecoderStream(new ByteArrayInputStream(encodedData), 6, type, + fillOrder, options); + new DataInputStream(inputStream).readFully(redecodedData); + inputStream.close(); + + assertArrayEquals(imageData, redecodedData); + } }