feat: add CCITT Group 3 (Fax3) compression and FillOrder support
Add Group3Reader for decoding CCITT Group 3 (T.4) 1D compressed TIFF images. The fax crate's Group3Decoder has a bug where new() verifies but doesn't consume the initial EOL marker, causing its lookup tables to destructively consume bits from the EOL code. We work around this by using the crate's lower-level white::decode / black::decode functions and handling EOL markers ourselves. Also adds FillOrder tag (266) support: FillOrder=2 (LSB-to-MSB, common in fax images) is handled by reversing bits in each byte before decoding. This applies to both Group 3 and Group 4 readers. Both libtiffpic fax test images (fax2d.tif, g3test.tif) now decode correctly with verified CRC32 hashes.
This commit is contained in:
1 parent
83dbb750df
commit
26301fc3f1
3 files changed
+175
-4
No files matched your search
@@ -85,6 +85,8 @@ pub(crate) struct Image {
|
||||
pub chunk_offsets: Vec<u64>,
|
||||
pub chunk_bytes: Vec<u64>,
|
||||
pub chroma_subsampling: (u16, u16),
|
||||
/// FillOrder: 1 = MSB-to-LSB (default), 2 = LSB-to-MSB (common in fax images)
|
||||
pub fill_order: u16,
|
||||
/// The color map (palette) for `PhotometricInterpretation::RGBPalette`.
|
||||
/// Contains `3 * 2^BitsPerSample` u16 entries: R values, then G values, then B values.
|
||||
pub color_map: Option<Vec<u16>>,
|
||||
@@ -173,6 +175,7 @@ impl Image {
|
||||
chunk_offsets: Vec::new(),
|
||||
chunk_bytes: Vec::new(),
|
||||
chroma_subsampling: (2, 2),
|
||||
fill_order: 1,
|
||||
color_map: None,
|
||||
decompression_to_host_endian: false,
|
||||
}
|
||||
@@ -333,6 +336,12 @@ impl Image {
|
||||
(2, 2)
|
||||
};
|
||||
|
||||
let fill_order = tag_reader
|
||||
.find_tag(Tag::FillOrder)?
|
||||
.map(Value::into_u16)
|
||||
.transpose()?
|
||||
.unwrap_or(1);
|
||||
|
||||
let planes = match planar_config {
|
||||
PlanarConfiguration::Chunky => 1,
|
||||
PlanarConfiguration::Planar => samples,
|
||||
@@ -510,6 +519,7 @@ impl Image {
|
||||
chunk_offsets,
|
||||
chunk_bytes,
|
||||
chroma_subsampling,
|
||||
fill_order,
|
||||
color_map,
|
||||
decompression_to_host_endian: matches!(
|
||||
compression_method,
|
||||
@@ -681,6 +691,7 @@ impl Image {
|
||||
#[cfg_attr(not(feature = "jpeg"), allow(unused_variables))] jpeg_tables: Option<&[u8]>,
|
||||
dimensions: (u32, u32),
|
||||
samples: u16,
|
||||
#[cfg_attr(not(feature = "fax"), allow(unused_variables))] fill_order: u16,
|
||||
) -> TiffResult<Box<dyn Read + 'r>> {
|
||||
Ok(match compression_method {
|
||||
CompressionMethod::None => Box::new(reader),
|
||||
@@ -774,10 +785,18 @@ impl Image {
|
||||
Box::new(Cursor::new(data))
|
||||
}
|
||||
#[cfg(feature = "fax")]
|
||||
CompressionMethod::Fax3 => Box::new(super::stream::Group3Reader::new(
|
||||
dimensions,
|
||||
reader,
|
||||
compressed_length,
|
||||
fill_order,
|
||||
)?),
|
||||
#[cfg(feature = "fax")]
|
||||
CompressionMethod::Fax4 => Box::new(super::stream::Group4Reader::new(
|
||||
dimensions,
|
||||
reader,
|
||||
compressed_length,
|
||||
fill_order,
|
||||
)?),
|
||||
#[cfg(feature = "webp")]
|
||||
CompressionMethod::WebP => Box::new(super::stream::WebPReader::new(
|
||||
@@ -1144,6 +1163,7 @@ impl Image {
|
||||
self.jpeg_tables.as_deref().map(|a| &**a),
|
||||
chunk_dims,
|
||||
self.samples,
|
||||
self.fill_order,
|
||||
)?;
|
||||
|
||||
// Extended bit depths (9-15, 17-31): packed N-bit samples must be unpacked to
|
||||
|
||||
Reference in new issue
Block a user