diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java index a8cf4617..9a86946b 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java @@ -43,6 +43,8 @@ import java.util.Arrays; * @version $Id: LZWDecoder.java,v 1.0 08.05.12 21:11 haraldk Exp$ */ final class LZWDecoder implements Decoder { + // TODO: Break out compatibility handling to subclass, to avoid code branching? + /** Clear: Re-initialize tables. */ static final int CLEAR_CODE = 256; /** End of Information. */ @@ -53,17 +55,16 @@ final class LZWDecoder implements Decoder { private final boolean reverseBitOrder; - private int currentByte = -1; - private int bitPos; - // TODO: Consider speeding things up with a "string" type (instead of the inner byte[]), // that uses variable size/dynamic allocation, to avoid the excessive array copying? // private final byte[][] table = new byte[4096][0]; // libTiff adds another 1024 "for compatibility"... private final byte[][] table = new byte[4096 + 1024][0]; // libTiff adds another 1024 "for compatibility"... +// private final Entry[] tableToo = new Entry[4096 + 1024]; private int tableLength; private int bitsPerCode; private int oldCode = CLEAR_CODE; private int maxCode; + private int bitMask; private int maxString; private boolean eofReached; @@ -74,6 +75,10 @@ final class LZWDecoder implements Decoder { table[i] = new byte[] {(byte) i}; } +// for (int i = 0; i < 256; i++) { +// tableToo[i] = new Entry((byte) i); +// } +// init(); } @@ -81,14 +86,15 @@ final class LZWDecoder implements Decoder { this(false); } - private int maxCodeFor(final int bits) { - return reverseBitOrder ? (1 << bits) - 2 : (1 << bits) - 1; + private static int maxCodeFor(final int bits) { + return (1 << bits) - 1; } private void init() { tableLength = 258; bitsPerCode = MIN_BITS; - maxCode = maxCodeFor(bitsPerCode); + bitMask = maxCodeFor(bitsPerCode); + maxCode = reverseBitOrder ? bitMask : bitMask - 1; maxString = 1; } @@ -151,7 +157,7 @@ final class LZWDecoder implements Decoder { private void addStringToTable(final byte[] string) throws IOException { table[tableLength++] = string; - if (tableLength >= maxCode) { + if (tableLength > maxCode) { bitsPerCode++; if (bitsPerCode > MAX_BITS) { @@ -163,7 +169,8 @@ final class LZWDecoder implements Decoder { } } - maxCode = maxCodeFor(bitsPerCode); + bitMask = maxCodeFor(bitsPerCode); + maxCode = reverseBitOrder ? bitMask : bitMask - 1; } if (string.length > maxString) { @@ -191,68 +198,65 @@ final class LZWDecoder implements Decoder { return code < tableLength; } + + int nextData, nextBits; + private int getNextCode(final InputStream stream) throws IOException { if (eofReached) { return EOI_CODE; } - int bitsToFill = bitsPerCode; - int value = 0; + int code; + int read = stream.read(); + if (read < 0) { + eofReached = true; + return EOI_CODE; + } - while (bitsToFill > 0) { - int nextBits; - if (bitPos == 0) { - nextBits = stream.read(); + if (reverseBitOrder) { + // NOTE: This is a spec violation. However, libTiff reads such files. + // TIFF 6.0 Specification, Section 13: "LZW Compression"/"The Algorithm", page 61, says: + // "LZW compression codes are stored into bytes in high-to-low-order fashion, i.e., FillOrder + // is assumed to be 1. The compressed codes are written as bytes (not words) so that the + // compressed data will be identical whether it is an ‘II’ or ‘MM’ file." + nextData |= read << nextBits; + nextBits += 8; - if (nextBits == -1) { - // This is really a bad stream, but should be safe to handle this way, rather than throwing an EOFException. - // An EOFException will be thrown by the decoder stream later, if further reading is attempted. + if (nextBits < bitsPerCode) { + read = stream.read(); + if (read < 0) { eofReached = true; return EOI_CODE; } - } - else { - nextBits = currentByte; + + nextData |= read << nextBits; + nextBits += 8; } - int bitsFromHere = 8 - bitPos; - if (bitsFromHere > bitsToFill) { - bitsFromHere = bitsToFill; - } + code = (nextData & bitMask); + nextData >>= bitsPerCode; + nextBits -= bitsPerCode; + } + else { + nextData = (nextData << 8) | read; + nextBits += 8; - if (reverseBitOrder) { - // NOTE: This is a spec violation. However, libTiff reads such files. - // TIFF 6.0 Specification, Section 13: "LZW Compression"/"The Algorithm", page 61, says: - // "LZW compression codes are stored into bytes in high-to-low-order fashion, i.e., FillOrder - // is assumed to be 1. The compressed codes are written as bytes (not words) so that the - // compressed data will be identical whether it is an ‘II’ or ‘MM’ file." - - // Fill bytes from right-to-left - for (int i = 0; i < bitsFromHere; i++) { - int destBitPos = bitsPerCode - bitsToFill + i; - int srcBitPos = bitPos + i; - value |= ((nextBits & (1 << srcBitPos)) >> srcBitPos) << destBitPos; + if (nextBits < bitsPerCode) { + read = stream.read(); + if (read < 0) { + eofReached = true; + return EOI_CODE; } - } - else { - value |= (nextBits >> 8 - bitPos - bitsFromHere & 0xff >> 8 - bitsFromHere) << bitsToFill - bitsFromHere; + + nextData = (nextData << 8) | read; + nextBits += 8; } - bitsToFill -= bitsFromHere; - bitPos += bitsFromHere; - - if (bitPos >= 8) { - bitPos = 0; - } - - currentByte = nextBits; + code = ((nextData >> (nextBits - bitsPerCode)) & bitMask); + nextBits -= bitsPerCode; } - if (value == EOI_CODE) { - eofReached = true; - } - - return value; + return code; } static boolean isOldBitReversedStream(final InputStream stream) throws IOException { @@ -267,5 +271,24 @@ final class LZWDecoder implements Decoder { stream.reset(); } } + + private class Entry { + final Entry next; + + final int length; + final byte value; + final byte firstChar; + + public Entry(byte code) { + this(code, code, 1, null); + } + + public Entry(byte value, byte firstChar, int length, Entry next) { + this.length = length; + this.value = value; + this.firstChar = firstChar; + this.next = next; + } + } } diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java index d45d73e5..b35d847f 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java @@ -109,6 +109,7 @@ public class TIFFImageReader extends ImageReaderBase { // TODO: Implement readAsRenderedImage to allow tiled renderImage? // For some layouts, we could do reads super-fast with a memory mapped buffer. // TODO: Implement readAsRaster directly + // TODO: IIOMetadata // TODOs Full BaseLine support: // TODO: Support ExtraSamples (an array, if multiple extra samples!) @@ -282,7 +283,7 @@ public class TIFFImageReader extends ImageReaderBase { return ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR); } - return ImageTypeSpecifier.createInterleaved(cs, new int[] {0, 1, 2, 3}, dataType, false, false); + return ImageTypeSpecifier.createInterleaved(cs, new int[] {0, 1, 2, 3}, dataType, true, false); case TIFFExtension.PLANARCONFIG_PLANAR: return ImageTypeSpecifier.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, dataType, false, false); @@ -704,7 +705,7 @@ public class TIFFImageReader extends ImageReaderBase { // } // catch (IOException e) { // Arrays.fill(rowData, k, rowData.length, (byte) -1); -// System.err.printf("Unexpected EOF or bad data at [%d %d]\n", col + k, row); +// System.err.printf("Unexpected EOF or bad data at [%d, %d]\n", col + k, row); // break; // } // } diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoderTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoderTest.java index e2d8dc62..b4a82787 100644 --- a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoderTest.java +++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoderTest.java @@ -30,7 +30,6 @@ import com.twelvemonkeys.io.enc.Decoder; import com.twelvemonkeys.io.enc.DecoderAbstractTestCase; import com.twelvemonkeys.io.enc.DecoderStream; import com.twelvemonkeys.io.enc.Encoder; -import org.junit.Ignore; import org.junit.Test; import java.io.ByteArrayInputStream; @@ -66,15 +65,6 @@ public class LZWDecoderTest extends DecoderAbstractTestCase { assertSameStreamContents(unpacked, stream); } - @Ignore("Known issue") - @Test - public void testShortBitReversedStreamLine45To49() throws IOException { - InputStream stream = new DecoderStream(getClass().getResourceAsStream("/lzw/lzw-short-45-49.bin"), new LZWDecoder(true), 128); - InputStream unpacked = getClass().getResourceAsStream("/lzw/unpacked-short-45-49.bin"); - - assertSameStreamContents(unpacked, stream); - } - @Test public void testLongStream() throws IOException { InputStream stream = new DecoderStream(getClass().getResourceAsStream("/lzw/lzw-long.bin"), new LZWDecoder(), 1024); diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java index ced889d5..8200accd 100644 --- a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java +++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java @@ -52,6 +52,7 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTestCase