feat: decode old-style (pre-TIFF-6.0) LZW streams

This commit is contained in:
aarshivv committed 2026-07-25 12:45:40 +05:30
1 parent f28f5fc830
commit c27fd94e1d
3 files changed
+35 -7

No files matched your search

+24 -6
View File
@@ -141,14 +141,32 @@ pub struct LZWReader<R: Read> {
impl<R: Read> LZWReader<R> { impl<R: Read> LZWReader<R> {
/// Wraps a reader /// Wraps a reader
pub fn new(reader: R, compressed_length: usize) -> LZWReader<R> { pub fn new(reader: R, compressed_length: usize) -> LZWReader<R> {
let configuration = let mut buffered = BufReader::with_capacity(
weezl::decode::Configuration::with_tiff_size_switch(weezl::BitOrder::Msb, 8)
.with_yield_on_full_buffer(true);
Self {
reader: BufReader::with_capacity(
(32 * 1024).min(compressed_length), (32 * 1024).min(compressed_length),
reader.take(u64::try_from(compressed_length).unwrap()), reader.take(u64::try_from(compressed_length).unwrap()),
), );
// `Compression = 5` covers both the current LZW variant and the pre-TIFF-6.0
// "old-style" one, with no tag to distinguish them. New-style is MSB-first with
// the "early change" code-size switch, and its stream begins with the Clear code
// (256) -> first byte 0x80. Old-style is LSB-first *without* the early change
// (GIF-style increment timing), and begins with 0x00 followed by a byte with bit
// 0 set. This is the heuristic libtiff uses to select its compatibility decoder;
// on a short/failed peek we default to the modern variant.
let old_style = matches!(
buffered.fill_buf(),
Ok(head) if head.len() >= 2 && head[0] == 0x00 && head[1] & 0x01 != 0
);
let configuration = if old_style {
weezl::decode::Configuration::new(weezl::BitOrder::Lsb, 8)
} else {
weezl::decode::Configuration::with_tiff_size_switch(weezl::BitOrder::Msb, 8)
}
.with_yield_on_full_buffer(true);
Self {
reader: buffered,
decoder: configuration.build(), decoder: configuration.build(),
} }
} }
+10
View File
@@ -1060,6 +1060,16 @@ fn test_gray_u12_hpredict() {
test_image_sum_u16("hpredict-1c-12b.tiff", ColorType::Gray(12), 11000); test_image_sum_u16("hpredict-1c-12b.tiff", ColorType::Gray(12), 11000);
} }
// ---- Old-style LZW ----
#[test]
fn test_old_style_lzw() {
// Pre-TIFF-6.0 "old-style" LZW: LSB-first bit order with no early-change size
// switch. Auto-detected from the stream (first byte 0x00, second byte odd).
// Same image as libtiffpic's quad-lzw.tif; the decoded pixels must match.
test_image_sum_u8("quad-lzw-compat.tiff", ColorType::RGB(8), 21048924);
}
// ---- TransparencyMask ---- // ---- TransparencyMask ----
/// Build a minimal uncompressed 2x2 TIFF in memory, in the requested byte order, /// Build a minimal uncompressed 2x2 TIFF in memory, in the requested byte order,
+1 -1
View File
@@ -190,7 +190,7 @@ const FILES: &[(&str, Option<u32>)] = &[
("tests/libtiffpic/oxford.tif", Some(0x355d2e3e)), ("tests/libtiffpic/oxford.tif", Some(0x355d2e3e)),
("tests/libtiffpic/pc260001.tif", Some(0xdb56b753)), ("tests/libtiffpic/pc260001.tif", Some(0xdb56b753)),
("tests/libtiffpic/quad-jpeg.tif", Some(0xcba22475)), ("tests/libtiffpic/quad-jpeg.tif", Some(0xcba22475)),
("tests/libtiffpic/quad-lzw.tif", Some(0)), // invalid LZW stream ("tests/libtiffpic/quad-lzw.tif", Some(0x6055f9ee)), // old-style (LSB) LZW
("tests/libtiffpic/quad-tile.tif", Some(0x6055f9ee)), ("tests/libtiffpic/quad-tile.tif", Some(0x6055f9ee)),
("tests/libtiffpic/README", None), ("tests/libtiffpic/README", None),
("tests/libtiffpic/smallliz.tif", Some(0)), // unsupported YCbCr chroma subsampling ("tests/libtiffpic/smallliz.tif", Some(0)), // unsupported YCbCr chroma subsampling