TMI-84: LZWDecoder fixes for > 12 bit exception when using full 12 bit (4096 entries) table

This commit is contained in:
Harald Kuhr 2014-12-04 16:51:37 +01:00
parent 9539e8e2e5
commit 27b2ff3745
4 changed files with 26 additions and 31 deletions

View File

@ -57,9 +57,10 @@ public interface Decoder {
* @return the total number of bytes read into the buffer, or {@code 0} * @return the total number of bytes read into the buffer, or {@code 0}
* if there is no more data because the end of the stream has been reached. * if there is no more data because the end of the stream has been reached.
* *
* @throws DecodeException if encoded data is corrupt * @throws DecodeException if encoded data is corrupt.
* @throws IOException if an I/O error occurs * @throws IOException if an I/O error occurs.
* @throws java.io.EOFException if a premature end-of-file is encountered * @throws java.io.EOFException if a premature end-of-file is encountered.
* @throws java.lang.NullPointerException if either argument is {@code null}.
*/ */
int decode(InputStream stream, ByteBuffer buffer) throws IOException; int decode(InputStream stream, ByteBuffer buffer) throws IOException;
} }

View File

@ -33,12 +33,11 @@ import com.twelvemonkeys.io.enc.Decoder;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.String;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
/** /**
* LempelZivWelch (LZW) decompression. LZW is a universal loss-less data compression algorithm * LempelZivWelch (LZW) decompression.
* created by Abraham Lempel, Jacob Ziv, and Terry Welch. * LZW is a universal loss-less data compression algorithm created by Abraham Lempel, Jacob Ziv, and Terry Welch.
* Inspired by libTiff's LZW decompression. * Inspired by libTiff's LZW decompression.
* *
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a> * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
@ -57,8 +56,6 @@ abstract class LZWDecoder implements Decoder {
private static final int TABLE_SIZE = 1 << MAX_BITS; private static final int TABLE_SIZE = 1 << MAX_BITS;
private final boolean compatibilityMode;
private final LZWString[] table; private final LZWString[] table;
private int tableLength; private int tableLength;
int bitsPerCode; int bitsPerCode;
@ -70,11 +67,8 @@ abstract class LZWDecoder implements Decoder {
int nextData; int nextData;
int nextBits; int nextBits;
protected LZWDecoder(int tableSize) {
protected LZWDecoder(final boolean compatibilityMode) { table = new LZWString[tableSize];
this.compatibilityMode = compatibilityMode;
table = new LZWString[compatibilityMode ? TABLE_SIZE + 1024 : TABLE_SIZE]; // libTiff adds 1024 "for compatibility"...
// First 258 entries of table is always fixed // First 258 entries of table is always fixed
for (int i = 0; i < 256; i++) { for (int i = 0; i < 256; i++) {
@ -97,6 +91,10 @@ abstract class LZWDecoder implements Decoder {
} }
public int decode(final InputStream stream, final ByteBuffer buffer) throws IOException { public int decode(final InputStream stream, final ByteBuffer buffer) throws IOException {
if (buffer == null) {
throw new NullPointerException("buffer == null"); // As per contract
}
// Adapted from the pseudo-code example found in the TIFF 6.0 Specification, 1992. // Adapted from the pseudo-code example found in the TIFF 6.0 Specification, 1992.
// See Section 13: "LZW Compression"/"LZW Decoding", page 61+ // See Section 13: "LZW Compression"/"LZW Decoding", page 61+
int code; int code;
@ -114,8 +112,7 @@ abstract class LZWDecoder implements Decoder {
} }
else { else {
if (table[oldCode] == null) { if (table[oldCode] == null) {
System.err.println("tableLength: " + tableLength); throw new DecodeException(String.format("Corrupted TIFF LZW: code %d (table size: %d)", oldCode, tableLength));
System.err.println("oldCode: " + oldCode);
} }
if (isInTable(code)) { if (isInTable(code)) {
@ -133,7 +130,7 @@ abstract class LZWDecoder implements Decoder {
oldCode = code; oldCode = code;
if (buffer.remaining() < maxString + 1) { if (buffer.remaining() < maxString + 1) {
// Buffer full, stop decoding for now // Buffer (almost) full, stop decoding for now
break; break;
} }
} }
@ -142,18 +139,18 @@ abstract class LZWDecoder implements Decoder {
} }
private void addStringToTable(final LZWString string) throws IOException { private void addStringToTable(final LZWString string) throws IOException {
if (tableLength > table.length) {
throw new DecodeException(String.format("TIFF LZW with more than %d bits per code encountered (table overflow)", MAX_BITS));
}
table[tableLength++] = string; table[tableLength++] = string;
if (tableLength > maxCode) { if (tableLength > maxCode) {
bitsPerCode++; bitsPerCode++;
if (bitsPerCode > MAX_BITS) { if (bitsPerCode > MAX_BITS) {
if (compatibilityMode) { // Continue reading MAX_BITS (12 bit) length codes
bitsPerCode--; bitsPerCode = MAX_BITS;
}
else {
throw new DecodeException(String.format("TIFF LZW with more than %d bits per code encountered (table overflow)", MAX_BITS));
}
} }
bitMask = bitmaskFor(bitsPerCode); bitMask = bitmaskFor(bitsPerCode);
@ -173,9 +170,9 @@ abstract class LZWDecoder implements Decoder {
protected abstract int getNextCode(final InputStream stream) throws IOException; protected abstract int getNextCode(final InputStream stream) throws IOException;
static boolean isOldBitReversedStream(final InputStream stream) throws IOException { static boolean isOldBitReversedStream(final InputStream stream) throws IOException {
stream.mark(2); stream.mark(2);
try { try {
int one = stream.read(); int one = stream.read();
int two = stream.read(); int two = stream.read();
@ -191,10 +188,10 @@ abstract class LZWDecoder implements Decoder {
return oldBitReversedStream ? new LZWCompatibilityDecoder() : new LZWSpecDecoder(); return oldBitReversedStream ? new LZWCompatibilityDecoder() : new LZWSpecDecoder();
} }
private static final class LZWSpecDecoder extends LZWDecoder { static final class LZWSpecDecoder extends LZWDecoder {
protected LZWSpecDecoder() { protected LZWSpecDecoder() {
super(false); super(TABLE_SIZE);
} }
@Override @Override
@ -243,7 +240,7 @@ abstract class LZWDecoder implements Decoder {
// compressed data will be identical whether it is an II or MM file." // compressed data will be identical whether it is an II or MM file."
protected LZWCompatibilityDecoder() { protected LZWCompatibilityDecoder() {
super(true); super(TABLE_SIZE + 1024); // libTiff adds 1024 "for compatibility", this value seems to work fine...
} }
@Override @Override

View File

@ -30,7 +30,6 @@ import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
import org.junit.Test; import org.junit.Test;
import javax.imageio.ImageReadParam; import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.event.IIOReadWarningListener; import javax.imageio.event.IIOReadWarningListener;
import javax.imageio.spi.ImageReaderSpi; import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageInputStream;
@ -42,12 +41,9 @@ import java.util.List;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.contains; import static org.mockito.Matchers.contains;
import static org.mockito.Matchers.eq; import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/** /**
* TIFFImageReaderTest * TIFFImageReaderTest
@ -72,6 +68,7 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTestCase<TIFFImageRe
new TestData(getClassLoaderResource("/tiff/bali.tif"), new Dimension(725, 489)), // Palette-based, LZW compressed new TestData(getClassLoaderResource("/tiff/bali.tif"), new Dimension(725, 489)), // Palette-based, LZW compressed
new TestData(getClassLoaderResource("/tiff/f14.tif"), new Dimension(640, 480)), // Gray, uncompressed new TestData(getClassLoaderResource("/tiff/f14.tif"), new Dimension(640, 480)), // Gray, uncompressed
new TestData(getClassLoaderResource("/tiff/marbles.tif"), new Dimension(1419, 1001)), // RGB, LZW compressed w/predictor new TestData(getClassLoaderResource("/tiff/marbles.tif"), new Dimension(1419, 1001)), // RGB, LZW compressed w/predictor
new TestData(getClassLoaderResource("/tiff/lzw-full-12-bit-table.tif"), new Dimension(874, 1240)), // Gray, LZW compressed, w/predictor
new TestData(getClassLoaderResource("/tiff/chifley_logo.tif"), new Dimension(591, 177)), // CMYK, uncompressed new TestData(getClassLoaderResource("/tiff/chifley_logo.tif"), new Dimension(591, 177)), // CMYK, uncompressed
new TestData(getClassLoaderResource("/tiff/ycbcr-cat.tif"), new Dimension(250, 325)), // YCbCr, LZW compressed new TestData(getClassLoaderResource("/tiff/ycbcr-cat.tif"), new Dimension(250, 325)), // YCbCr, LZW compressed
new TestData(getClassLoaderResource("/tiff/quad-jpeg.tif"), new Dimension(512, 384)), // YCbCr, JPEG compressed, striped new TestData(getClassLoaderResource("/tiff/quad-jpeg.tif"), new Dimension(512, 384)), // YCbCr, JPEG compressed, striped