From e5c0fead384b18bd1f0751862d6c1a730ec5267d Mon Sep 17 00:00:00 2001 From: Schmidor Date: Sun, 11 Oct 2015 17:59:01 +0200 Subject: [PATCH] Fix BlackIsZero handling in CCITTFaxEncoderStream --- .../plugins/tiff/CCITTFaxEncoderStream.java | 10 +-- .../imageio/plugins/tiff/TIFFImageWriter.java | 14 +--- .../tiff/CCITTFaxEncoderStreamTest.java | 67 ++++++++++++++++--- 3 files changed, 65 insertions(+), 26 deletions(-) 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 b8b3e096..05d40bc4 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 @@ -190,7 +190,7 @@ public class CCITTFaxEncoderStream extends OutputStream { int index = 0; boolean white = true; while (index < columns) { - int[] nextChanges = getNextChanges(index); + int[] nextChanges = getNextChanges(index, white); int runLength = nextChanges[0] - index; writeRun(runLength, white); index += runLength; @@ -198,10 +198,10 @@ public class CCITTFaxEncoderStream extends OutputStream { } } - private int[] getNextChanges(int pos) { + private int[] getNextChanges(int pos, boolean white) { int[] result = new int[] {columns, columns}; for (int i = 0; i < changesCurrentRowLength; i++) { - if (pos < changesCurrentRow[i]) { + if (pos < changesCurrentRow[i] || (pos == 0 && white)) { result[0] = changesCurrentRow[i]; if ((i + 1) < changesCurrentRowLength) { result[1] = changesCurrentRow[i + 1]; @@ -235,7 +235,7 @@ public class CCITTFaxEncoderStream extends OutputStream { boolean white = true; int index = 0; // a0 while (index < columns) { - int[] nextChanges = getNextChanges(index); // a1, a2 + int[] nextChanges = getNextChanges(index, white); // a1, a2 int[] nextRefs = getNextRefChanges(index, white); // b1, b2 @@ -287,7 +287,7 @@ public class CCITTFaxEncoderStream extends OutputStream { 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) { + if (changesReferenceRow[i] > a0 || (a0 == 0 && i == 0)) { result[0] = changesReferenceRow[i]; if ((i + 1) < changesReferenceRowLength) { result[1] = changesReferenceRow[i + 1]; diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriter.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriter.java index 9fdb3dd9..a5cfa189 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriter.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriter.java @@ -377,7 +377,7 @@ public final class TIFFImageWriter extends ImageWriterBase { else { // Write image data writeImageData(createCompressorStream(renderedImage, param, entries), renderedImage, numComponents, bandOffsets, - bitOffsets, photometric); + bitOffsets); } // Update IFD0-pointer, and write IFD @@ -587,7 +587,7 @@ public final class TIFFImageWriter extends ImageWriterBase { return shorts; } - private void writeImageData(DataOutput stream, RenderedImage renderedImage, int numComponents, int[] bandOffsets, int[] bitOffsets, int photometric) throws IOException { + private void writeImageData(DataOutput stream, RenderedImage renderedImage, int numComponents, int[] bandOffsets, int[] bitOffsets) throws IOException { // Store 3BYTE, 4BYTE as is (possibly need to re-arrange to RGB order) // Store INT_RGB as 3BYTE, INT_ARGB as 4BYTE?, INT_ABGR must be re-arranged // Store IndexColorModel as is @@ -635,7 +635,7 @@ public final class TIFFImageWriter extends ImageWriterBase { final int xOff = yOff + x * numBands; for (int s = 0; s < numBands; s++) { - buffer.put(normalizeBlack(photometric, (byte) (dataBuffer.getElem(b, xOff + bandOffsets[s]) & 0xff))); + buffer.put((byte) (dataBuffer.getElem(b, xOff + bandOffsets[s]) & 0xff)); } } @@ -748,14 +748,6 @@ public final class TIFFImageWriter extends ImageWriterBase { processImageComplete(); } - private byte normalizeBlack(int photometricInterpretation, byte data) { - if (photometricInterpretation == TIFFBaseline.PHOTOMETRIC_BLACK_IS_ZERO) { - // Inverse values - return (byte) (0xff - data & 0xff); - } - return data; - } - // TODO: Would be better to solve this on stream level... But writers would then have to explicitly flush the buffer before done. private void flushBuffer(final ByteBuffer buffer, final DataOutput stream) throws IOException { buffer.flip(); 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 c251d687..9a6f12a2 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 @@ -29,12 +29,20 @@ package com.twelvemonkeys.imageio.plugins.tiff; import com.twelvemonkeys.imageio.plugins.tiff.CCITTFaxEncoderStream.Code; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.ImageWriter; +import javax.imageio.stream.ImageInputStream; +import javax.imageio.stream.ImageOutputStream; import java.awt.image.BufferedImage; import java.awt.image.DataBufferByte; import java.io.*; +import java.net.URL; import static org.junit.Assert.*; @@ -88,30 +96,40 @@ public class CCITTFaxEncoderStreamTest { @Test public void testType2() throws IOException { - testImage(TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 1, 0L); + testStreamEncodeDecode(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, + testStreamEncodeDecode(TIFFExtension.COMPRESSION_CCITT_T4, 1, 0L); + testStreamEncodeDecode(TIFFExtension.COMPRESSION_CCITT_T4, 1, TIFFExtension.GROUP3OPT_FILLBITS); + testStreamEncodeDecode(TIFFExtension.COMPRESSION_CCITT_T4, 1, TIFFExtension.GROUP3OPT_2DENCODING); + testStreamEncodeDecode(TIFFExtension.COMPRESSION_CCITT_T4, 1, TIFFExtension.GROUP3OPT_FILLBITS | TIFFExtension.GROUP3OPT_2DENCODING); } @Test public void testType6() throws IOException { - testImage(TIFFExtension.COMPRESSION_CCITT_T6, 1, 0L); + testStreamEncodeDecode(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); + public void testReversedFillOrder() throws IOException { + testStreamEncodeDecode(TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 2, 0L); + testStreamEncodeDecode(TIFFExtension.COMPRESSION_CCITT_T6, 2, 0L); } - private void testImage(int type, int fillOrder, long options) throws IOException { + @Test + public void testReencodeImages() throws IOException { + testImage(getClassLoaderResource("/tiff/fivepages-scan-causingerrors.tif")); + // testImage(getClassLoaderResource("/tiff/test-single-gray-compression-type-2.tiff")); + } + + protected URL getClassLoaderResource(final String pName) { + return getClass().getResource(pName); + } + + private void testStreamEncodeDecode(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(); @@ -127,4 +145,33 @@ public class CCITTFaxEncoderStreamTest { assertArrayEquals(imageData, redecodedData); } + + private void testImage(URL imageUrl) throws IOException { + ImageInputStream iis = ImageIO.createImageInputStream(imageUrl.openStream()); + ImageReader reader = ImageIO.getImageReadersByFormatName("TIFF").next(); + reader.setInput(iis, true); + + ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream(); + ImageWriter writer = ImageIO.getImageWritersByFormatName("TIFF").next(); + ImageOutputStream output = ImageIO.createImageOutputStream(outputBuffer); + writer.setOutput(output); + BufferedImage originalImage = reader.read(0); + + IIOImage outputImage = new IIOImage(originalImage, null, reader.getImageMetadata(0)); + writer.write(outputImage); + + FileOutputStream stream = new FileOutputStream("H:\\tmp\\test.tif"); + try { + stream.write(outputBuffer.toByteArray()); + } + finally { + stream.close(); + } + + BufferedImage reencodedImage = ImageIO.read(new ByteArrayInputStream(outputBuffer.toByteArray())); + byte[] reencodedData = ((DataBufferByte) reencodedImage.getData().getDataBuffer()).getData(); + + Assert.assertArrayEquals(((DataBufferByte) originalImage.getData().getDataBuffer()).getData(), + reencodedData); + } }