mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-03 11:35:29 -04:00
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:
parent
7846f497af
commit
59b91918e0
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
Binary file not shown.
Binary file not shown.
BIN
imageio/imageio-tiff/src/test/resources/tiff/quad-lzw.tif
Normal file
BIN
imageio/imageio-tiff/src/test/resources/tiff/quad-lzw.tif
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user