TMI-TIFF: Fixed minor bug in type spec for ARGB images + implemented support for "old-style" (reversed) LZW compression from libtiff.

This commit is contained in:
Harald Kuhr 2013-01-29 21:01:46 +01:00
parent 7846f497af
commit 59b91918e0
7 changed files with 79 additions and 64 deletions

View File

@ -43,6 +43,8 @@ import java.util.Arrays;
* @version $Id: LZWDecoder.java,v 1.0 08.05.12 21:11 haraldk Exp$ * @version $Id: LZWDecoder.java,v 1.0 08.05.12 21:11 haraldk Exp$
*/ */
final class LZWDecoder implements Decoder { final class LZWDecoder implements Decoder {
// TODO: Break out compatibility handling to subclass, to avoid code branching?
/** Clear: Re-initialize tables. */ /** Clear: Re-initialize tables. */
static final int CLEAR_CODE = 256; static final int CLEAR_CODE = 256;
/** End of Information. */ /** End of Information. */
@ -53,17 +55,16 @@ final class LZWDecoder implements Decoder {
private final boolean reverseBitOrder; 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[]), // 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? // 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][0]; // libTiff adds another 1024 "for compatibility"...
private final byte[][] table = new byte[4096 + 1024][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 tableLength;
private int bitsPerCode; private int bitsPerCode;
private int oldCode = CLEAR_CODE; private int oldCode = CLEAR_CODE;
private int maxCode; private int maxCode;
private int bitMask;
private int maxString; private int maxString;
private boolean eofReached; private boolean eofReached;
@ -74,6 +75,10 @@ final class LZWDecoder implements Decoder {
table[i] = new byte[] {(byte) i}; table[i] = new byte[] {(byte) i};
} }
// for (int i = 0; i < 256; i++) {
// tableToo[i] = new Entry((byte) i);
// }
//
init(); init();
} }
@ -81,14 +86,15 @@ final class LZWDecoder implements Decoder {
this(false); this(false);
} }
private int maxCodeFor(final int bits) { private static int maxCodeFor(final int bits) {
return reverseBitOrder ? (1 << bits) - 2 : (1 << bits) - 1; return (1 << bits) - 1;
} }
private void init() { private void init() {
tableLength = 258; tableLength = 258;
bitsPerCode = MIN_BITS; bitsPerCode = MIN_BITS;
maxCode = maxCodeFor(bitsPerCode); bitMask = maxCodeFor(bitsPerCode);
maxCode = reverseBitOrder ? bitMask : bitMask - 1;
maxString = 1; maxString = 1;
} }
@ -151,7 +157,7 @@ final class LZWDecoder implements Decoder {
private void addStringToTable(final byte[] string) throws IOException { private void addStringToTable(final byte[] string) throws IOException {
table[tableLength++] = string; table[tableLength++] = string;
if (tableLength >= maxCode) { if (tableLength > maxCode) {
bitsPerCode++; bitsPerCode++;
if (bitsPerCode > MAX_BITS) { 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) { if (string.length > maxString) {
@ -191,34 +198,20 @@ final class LZWDecoder implements Decoder {
return code < tableLength; return code < tableLength;
} }
int nextData, nextBits;
private int getNextCode(final InputStream stream) throws IOException { private int getNextCode(final InputStream stream) throws IOException {
if (eofReached) { if (eofReached) {
return EOI_CODE; return EOI_CODE;
} }
int bitsToFill = bitsPerCode; int code;
int value = 0; int read = stream.read();
if (read < 0) {
while (bitsToFill > 0) {
int nextBits;
if (bitPos == 0) {
nextBits = stream.read();
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.
eofReached = true; eofReached = true;
return EOI_CODE; return EOI_CODE;
} }
}
else {
nextBits = currentByte;
}
int bitsFromHere = 8 - bitPos;
if (bitsFromHere > bitsToFill) {
bitsFromHere = bitsToFill;
}
if (reverseBitOrder) { if (reverseBitOrder) {
// NOTE: This is a spec violation. However, libTiff reads such files. // NOTE: This is a spec violation. However, libTiff reads such files.
@ -226,33 +219,44 @@ final class LZWDecoder implements Decoder {
// "LZW compression codes are stored into bytes in high-to-low-order fashion, i.e., FillOrder // "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 // 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." // compressed data will be identical whether it is an II or MM file."
nextData |= read << nextBits;
nextBits += 8;
// Fill bytes from right-to-left if (nextBits < bitsPerCode) {
for (int i = 0; i < bitsFromHere; i++) { read = stream.read();
int destBitPos = bitsPerCode - bitsToFill + i; if (read < 0) {
int srcBitPos = bitPos + i; eofReached = true;
value |= ((nextBits & (1 << srcBitPos)) >> srcBitPos) << destBitPos; return EOI_CODE;
} }
nextData |= read << nextBits;
nextBits += 8;
}
code = (nextData & bitMask);
nextData >>= bitsPerCode;
nextBits -= bitsPerCode;
} }
else { else {
value |= (nextBits >> 8 - bitPos - bitsFromHere & 0xff >> 8 - bitsFromHere) << bitsToFill - bitsFromHere; nextData = (nextData << 8) | read;
} nextBits += 8;
bitsToFill -= bitsFromHere; if (nextBits < bitsPerCode) {
bitPos += bitsFromHere; read = stream.read();
if (read < 0) {
if (bitPos >= 8) {
bitPos = 0;
}
currentByte = nextBits;
}
if (value == EOI_CODE) {
eofReached = true; eofReached = true;
return EOI_CODE;
} }
return value; nextData = (nextData << 8) | read;
nextBits += 8;
}
code = ((nextData >> (nextBits - bitsPerCode)) & bitMask);
nextBits -= bitsPerCode;
}
return code;
} }
static boolean isOldBitReversedStream(final InputStream stream) throws IOException { static boolean isOldBitReversedStream(final InputStream stream) throws IOException {
@ -267,5 +271,24 @@ final class LZWDecoder implements Decoder {
stream.reset(); 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;
}
}
} }

View File

@ -109,6 +109,7 @@ public class TIFFImageReader extends ImageReaderBase {
// TODO: Implement readAsRenderedImage to allow tiled renderImage? // TODO: Implement readAsRenderedImage to allow tiled renderImage?
// For some layouts, we could do reads super-fast with a memory mapped buffer. // For some layouts, we could do reads super-fast with a memory mapped buffer.
// TODO: Implement readAsRaster directly // TODO: Implement readAsRaster directly
// TODO: IIOMetadata
// TODOs Full BaseLine support: // TODOs Full BaseLine support:
// TODO: Support ExtraSamples (an array, if multiple extra samples!) // 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.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: case TIFFExtension.PLANARCONFIG_PLANAR:
return ImageTypeSpecifier.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, dataType, false, false); 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) { // catch (IOException e) {
// Arrays.fill(rowData, k, rowData.length, (byte) -1); // 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; // break;
// } // }
// } // }

View File

@ -30,7 +30,6 @@ import com.twelvemonkeys.io.enc.Decoder;
import com.twelvemonkeys.io.enc.DecoderAbstractTestCase; import com.twelvemonkeys.io.enc.DecoderAbstractTestCase;
import com.twelvemonkeys.io.enc.DecoderStream; import com.twelvemonkeys.io.enc.DecoderStream;
import com.twelvemonkeys.io.enc.Encoder; import com.twelvemonkeys.io.enc.Encoder;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@ -66,15 +65,6 @@ public class LZWDecoderTest extends DecoderAbstractTestCase {
assertSameStreamContents(unpacked, stream); 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 @Test
public void testLongStream() throws IOException { public void testLongStream() throws IOException {
InputStream stream = new DecoderStream(getClass().getResourceAsStream("/lzw/lzw-long.bin"), new LZWDecoder(), 1024); InputStream stream = new DecoderStream(getClass().getResourceAsStream("/lzw/lzw-long.bin"), new LZWDecoder(), 1024);

View File

@ -52,6 +52,7 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTestCase<TIFFImageRe
new TestData(getClassLoaderResource("/tiff/sm_colors_tile.tif"), new Dimension(64, 64)), // RGB, uncompressed, tiled new TestData(getClassLoaderResource("/tiff/sm_colors_tile.tif"), new Dimension(64, 64)), // RGB, uncompressed, tiled
new TestData(getClassLoaderResource("/tiff/sm_colors_pb_tile.tif"), new Dimension(64, 64)), // RGB, PackBits compressed, tiled new TestData(getClassLoaderResource("/tiff/sm_colors_pb_tile.tif"), new Dimension(64, 64)), // RGB, PackBits compressed, tiled
new TestData(getClassLoaderResource("/tiff/galaxy.tif"), new Dimension(965, 965)), // RGB, LZW compressed new TestData(getClassLoaderResource("/tiff/galaxy.tif"), new Dimension(965, 965)), // RGB, LZW compressed
new TestData(getClassLoaderResource("/tiff/quad-lzw.tif"), new Dimension(512, 384)), // RGB, OLD LZW compressed, tiled
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