Merge pull request #229 from stephenjudkins/sdj/fax4-support

CCITT group 4 (Fax4) decoding support
This commit is contained in:
Aurelia Molzer authored and GitHub committed 2025-08-31 11:58:05 +00:00
commit 7c6c327817
6 files changed
+99 -3

No files matched your search

+3
View File
@@ -20,6 +20,8 @@ exclude = ["tests/images/*", "tests/fuzz_images/*"]
half = { version = "2.4.1" }
quick-error = "2.0.1"
# Rename to avoid confusion with the feature name.
fax34 = { package = "fax", version = "0.2.4", optional = true }
flate2 = { version = "1.0.20", optional = true }
weezl = { version = "0.1.10", optional = true }
zstd = { version = "0.13", optional = true }
@@ -35,6 +37,7 @@ default = ["deflate", "jpeg", "lzw"]
# Compression algorithms
deflate = ["dep:flate2"]
fax = ["dep:fax34"]
jpeg = ["dep:zune-jpeg"]
lzw = ["dep:weezl"]
zstd = ["dep:zstd"]
+11 -1
View File
@@ -9,6 +9,7 @@ use crate::tags::{
use crate::{
ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError,
};
use std::io::{self, Cursor, Read, Seek};
use std::num::NonZeroUsize;
use std::sync::Arc;
@@ -384,7 +385,9 @@ impl Image {
reader: R,
compression_method: CompressionMethod,
compressed_length: u64,
#[allow(unused_variables)] jpeg_tables: Option<&[u8]>,
// FIXME: these should be `expect` attributes or we choose another way of passing them.
#[cfg_attr(not(feature = "jpeg"), allow(unused_variables))] jpeg_tables: Option<&[u8]>,
#[cfg_attr(not(feature = "fax"), allow(unused_variables))] dimensions: (u32, u32),
) -> TiffResult<Box<dyn Read + 'r>> {
Ok(match compression_method {
CompressionMethod::None => Box::new(reader),
@@ -452,6 +455,12 @@ impl Image {
Box::new(Cursor::new(data))
}
#[cfg(feature = "fax")]
CompressionMethod::Fax4 => Box::new(super::stream::Group4Reader::new(
dimensions,
reader,
compressed_length,
)?),
method => {
return Err(TiffError::UnsupportedError(
TiffUnsupportedError::UnsupportedCompressionMethod(method),
@@ -658,6 +667,7 @@ impl Image {
compression_method,
*compressed_bytes,
self.jpeg_tables.as_deref().map(|a| &**a),
chunk_dims,
)?;
if output_row_stride == chunk_row_bytes {
-1
View File
@@ -547,7 +547,6 @@ fn fix_endianness(buf: &mut [u8], byte_order: ByteOrder, bit_depth: u8) {
}
impl<R: Read + Seek> Decoder<R> {
/// Create a new decoder that decodes from the stream ```r```
pub fn new(mut r: R) -> TiffResult<Decoder<R>> {
let mut endianess = Vec::with_capacity(2);
(&mut r).take(2).read_to_end(&mut endianess)?;
+79 -1
View File
@@ -1,5 +1,4 @@
//! All IO functionality needed for TIFF decoding
use std::io::{self, BufRead, BufReader, Read, Seek, Take};
/// Byte order of the TIFF file.
@@ -276,6 +275,85 @@ impl<R: Read> Read for PackBitsReader<R> {
}
}
#[cfg(feature = "fax")]
pub struct Group4Reader<R: Read> {
decoder: fax34::decoder::Group4Decoder<io::Bytes<io::BufReader<io::Take<R>>>>,
line_buf: io::Cursor<Vec<u8>>,
height: u16,
width: u16,
y: u16,
}
#[cfg(feature = "fax")]
impl<R: Read> Group4Reader<R> {
pub fn new(
dimensions: (u32, u32),
reader: R,
compressed_length: u64,
) -> crate::TiffResult<Self> {
let width = u16::try_from(dimensions.0)?;
let height = u16::try_from(dimensions.1)?;
Ok(Self {
decoder: fax34::decoder::Group4Decoder::new(
io::BufReader::new(reader.take(compressed_length)).bytes(),
width,
)?,
line_buf: io::Cursor::new(Vec::with_capacity(width.into())),
width,
height,
y: 0,
})
}
}
#[cfg(feature = "fax")]
impl<R: Read> Read for Group4Reader<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
// Either we have not read any line or we are at the end of a line.
if self.line_buf.position() as usize == self.line_buf.get_ref().len()
&& self.y < self.height
{
let next = self.decoder.advance().map_err(std::io::Error::other)?;
match next {
fax34::decoder::DecodeStatus::End => (),
fax34::decoder::DecodeStatus::Incomplete => {
self.y += 1;
// We known `transitions` yields exactly `self.width` items (per doc).
let transitions = fax34::decoder::pels(self.decoder.transition(), self.width);
let buffer = self.line_buf.get_mut();
buffer.resize(usize::from(self.width).div_ceil(8), 0u8);
let target = &mut buffer[..];
let mut bits = transitions.map(|c| match c {
fax34::Color::Black => true,
fax34::Color::White => false,
});
// Assemble bits in MSB as per our library representation for buffer.
for byte in target {
let mut val = 0;
for (idx, bit) in bits.by_ref().take(8).enumerate() {
val |= u8::from(bit) << (7 - idx % 8);
}
*byte = val;
}
self.line_buf.set_position(0);
}
}
}
self.line_buf.read(buf)
}
}
#[cfg(test)]
mod test {
use super::*;
+6
View File
@@ -672,3 +672,9 @@ fn bytes_gray_f32() {
byte_order_u32,
);
}
#[test]
#[cfg(feature = "fax")]
fn test_fax4() {
test_image_sum_u8("fax4.tiff", ColorType::Gray(1), 7802706);
}
Binary file not shown.