diff --git a/Cargo.toml b/Cargo.toml index d61ef4f..c61633b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] diff --git a/src/decoder/image.rs b/src/decoder/image.rs index bff237a..3ce9b3e 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -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> { 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 { diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 59cd355..3e03a9d 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -547,7 +547,6 @@ fn fix_endianness(buf: &mut [u8], byte_order: ByteOrder, bit_depth: u8) { } impl Decoder { - /// Create a new decoder that decodes from the stream ```r``` pub fn new(mut r: R) -> TiffResult> { let mut endianess = Vec::with_capacity(2); (&mut r).take(2).read_to_end(&mut endianess)?; diff --git a/src/decoder/stream.rs b/src/decoder/stream.rs index c2ff0f9..d0b05a5 100644 --- a/src/decoder/stream.rs +++ b/src/decoder/stream.rs @@ -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 Read for PackBitsReader { } } +#[cfg(feature = "fax")] +pub struct Group4Reader { + decoder: fax34::decoder::Group4Decoder>>>, + line_buf: io::Cursor>, + height: u16, + width: u16, + y: u16, +} + +#[cfg(feature = "fax")] +impl Group4Reader { + pub fn new( + dimensions: (u32, u32), + reader: R, + compressed_length: u64, + ) -> crate::TiffResult { + 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 Read for Group4Reader { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + // 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::*; diff --git a/tests/decode_images.rs b/tests/decode_images.rs index d7e4c31..2ebe4fc 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -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); +} diff --git a/tests/images/fax4.tiff b/tests/images/fax4.tiff new file mode 100644 index 0000000..e74dc8a Binary files /dev/null and b/tests/images/fax4.tiff differ