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 2094c14a..53c1c8aa 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 @@ -225,7 +225,7 @@ final class CCITTFaxDecoderStream extends FilterInputStream { if (a0 < changesReferenceRow[i] || (a0 == 0 && changesReferenceRow[i] == 0)) { return i; } - } + } return -1; } @@ -281,7 +281,6 @@ final class CCITTFaxDecoderStream extends FilterInputStream { int index = 0; boolean white = true; - for (int i = 0; i <= changesCurrentRowCount; i++) { int nextChange = columns; @@ -404,14 +403,14 @@ final class CCITTFaxDecoderStream extends FilterInputStream { @Override public int read() throws IOException { if (decodedLength < 0) { - return 0xFF; + return 0x0; } if (decodedPos >= decodedLength) { fetch(); if (decodedLength < 0) { - return 0xFF; + return 0x0; } } @@ -421,7 +420,7 @@ final class CCITTFaxDecoderStream extends FilterInputStream { @Override public int read(byte[] b, int off, int len) throws IOException { if (decodedLength < 0) { - Arrays.fill(b, off, len, (byte)0xFF); + Arrays.fill(b, off, off + len, (byte) 0x0); return len; } @@ -429,7 +428,7 @@ final class CCITTFaxDecoderStream extends FilterInputStream { fetch(); if (decodedLength < 0) { - Arrays.fill(b, off, len, (byte)0xFF); + Arrays.fill(b, off, off + len, (byte) 0x0); return len; } } @@ -570,146 +569,146 @@ final class CCITTFaxDecoderStream extends FilterInputStream { static final short[][] BLACK_CODES = { { // 2 bits 0x2, 0x3, - }, + }, { // 3 bits 0x2, 0x3, - }, + }, { // 4 bits 0x2, 0x3, - }, + }, { // 5 bits 0x3, - }, + }, { // 6 bits 0x4, 0x5, - }, + }, { // 7 bits 0x4, 0x5, 0x7, - }, + }, { // 8 bits 0x4, 0x7, - }, + }, { // 9 bits 0x18, - }, + }, { // 10 bits 0x17, 0x18, 0x37, 0x8, 0xf, - }, + }, { // 11 bits 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, - }, + }, { // 13 bits 0x4a, 0x4b, 0x4c, 0x4d, 0x52, 0x53, 0x54, 0x55, 0x5a, 0x5b, 0x64, 0x65, 0x6c, 0x6d, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, - } + } }; static final short[][] BLACK_RUN_LENGTHS = { { // 2 bits 3, 2, - }, + }, { // 3 bits 1, 4, - }, + }, { // 4 bits 6, 5, - }, + }, { // 5 bits 7, - }, + }, { // 6 bits 9, 8, - }, + }, { // 7 bits 10, 11, 12, - }, + }, { // 8 bits 13, 14, - }, + }, { // 9 bits 15, - }, + }, { // 10 bits 16, 17, 0, 18, 64, - }, + }, { // 11 bits 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, - }, + }, { // 13 bits 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, - }, + }, { // 5 bits 0x12, 0x13, 0x14, 0x1b, 0x7, 0x8, - }, + }, { // 6 bits 0x17, 0x18, 0x2a, 0x2b, 0x3, 0x34, 0x35, 0x7, 0x8, - }, + }, { // 7 bits 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, - }, + }, { // 9 bits 0x98, 0x99, 0x9a, 0x9b, 0xcc, 0xcd, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, - }, + }, { // 10 bits }, { // 11 bits 0x8, 0xc, 0xd, - }, + }, { // 12 bits 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, - }, + }, { // 5 bits 128, 8, 9, 64, 10, 11, - }, + }, { // 6 bits 192, 1664, 16, 17, 13, 14, 15, 1, 12, - }, + }, { // 7 bits 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, - }, + }, { // 10 bits }, { // 11 bits 1792, 1856, 1920, - }, + }, { // 12 bits 1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560, - } + } }; final static Node EOL; 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 b09ced18..8a250ece 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 @@ -40,7 +40,7 @@ import java.io.OutputStream; * @author last modified by $Author$ * @version $Id$ */ -public class CCITTFaxEncoderStream extends OutputStream { +final class CCITTFaxEncoderStream extends OutputStream { private int currentBufferLength = 0; private final byte[] inputBuffer; 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 31189ff0..68c59d7b 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 @@ -37,8 +37,9 @@ import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Arrays; -import static org.junit.Assert.*; +import static org.junit.Assert.assertArrayEquals; /** * CCITTFaxDecoderStreamTest @@ -109,11 +110,11 @@ public class CCITTFaxDecoderStreamTest { // 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; + final BufferedImage image = new BufferedImage(6, 4, BufferedImage.TYPE_BYTE_BINARY);; @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); @@ -200,4 +201,32 @@ public class CCITTFaxDecoderStreamTest { new DataInputStream(stream).readFully(bytes); assertArrayEquals(imageData, bytes); } + + @Test + public void testDecodeMissingRows() throws IOException { + // See https://github.com/haraldk/TwelveMonkeys/pull/225 and https://github.com/haraldk/TwelveMonkeys/issues/232 + InputStream inputStream = getClass().getResourceAsStream("/tiff/ccitt_tolessrows.tif"); + + // Skip until StripOffsets: 8 + for (int i = 0; i < 8; i++) { + inputStream.read(); + } + + // Read until StripByteCounts: 7 + byte[] data = new byte[7]; + new DataInputStream(inputStream).readFully(data); + + InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(data), + 6, TIFFExtension.COMPRESSION_CCITT_T6, 1, 0L); + + byte[] bytes = new byte[6]; // 6 x 6 pixel, 1 bpp => 6 bytes + new DataInputStream(stream).readFully(bytes); + + // Pad image data with 0s + byte[] imageData = Arrays.copyOf(((DataBufferByte) image.getData().getDataBuffer()).getData(), 6); + assertArrayEquals(imageData, bytes); + + // Ideally, we should have no more data now, but the stream don't know that... + // assertEquals("Should contain no more data", -1, stream.read()); + } } 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 a3076ca1..a9eb0599 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,7 +29,6 @@ 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; @@ -122,16 +121,34 @@ public class CCITTFaxEncoderStreamTest { @Test public void testReencodeImages() throws IOException { - testImage(getClassLoaderResource("/tiff/fivepages-scan-causingerrors.tif")); + try (ImageInputStream iis = ImageIO.createImageInputStream(getClassLoaderResource("/tiff/fivepages-scan-causingerrors.tif").openStream())) { + ImageReader reader = ImageIO.getImageReaders(iis).next(); + reader.setInput(iis, true); + + ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream(); + ImageWriter writer = ImageIO.getImageWritersByFormatName("TIFF").next(); + BufferedImage originalImage; + + try (ImageOutputStream output = ImageIO.createImageOutputStream(outputBuffer)) { + writer.setOutput(output); + originalImage = reader.read(0); + + IIOImage outputImage = new IIOImage(originalImage, null, reader.getImageMetadata(0)); + writer.write(outputImage); + } + + byte[] originalData = ((DataBufferByte) originalImage.getData().getDataBuffer()).getData(); + + BufferedImage reencodedImage = ImageIO.read(new ByteArrayInputStream(outputBuffer.toByteArray())); + byte[] reencodedData = ((DataBufferByte) reencodedImage.getData().getDataBuffer()).getData(); + + assertArrayEquals(originalData, reencodedData); + } } - /** - * Test for "Fixed an issue with long runlengths in CCITTFax writing #188" - * - * @throws IOException - */ @Test public void testRunlengthIssue() throws IOException { + // Test for "Fixed an issue with long runlengths in CCITTFax writing #188" byte[] data = new byte[400]; Arrays.fill(data, (byte) 0xFF); data[0] = 0; @@ -158,37 +175,19 @@ public class CCITTFaxEncoderStreamTest { 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(); OutputStream outputSteam = new CCITTFaxEncoderStream(imageOutput, 6, 4, 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(); + try (CCITTFaxDecoderStream inputStream = + new CCITTFaxDecoderStream(new ByteArrayInputStream(encodedData), 6, type, fillOrder, options)) { + new DataInputStream(inputStream).readFully(redecodedData); + } 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); - - BufferedImage reencodedImage = ImageIO.read(new ByteArrayInputStream(outputBuffer.toByteArray())); - byte[] reencodedData = ((DataBufferByte) reencodedImage.getData().getDataBuffer()).getData(); - - Assert.assertArrayEquals(((DataBufferByte) originalImage.getData().getDataBuffer()).getData(), reencodedData); - } }