From c1087f6f0895304ef1494039e52ffd2e6f0c4278 Mon Sep 17 00:00:00 2001 From: Stephen Judkins Date: Tue, 9 Apr 2024 15:25:42 -0700 Subject: [PATCH] `Reader` instance for group 4-decoded images, that buffer one line at a time --- src/decoder/image.rs | 81 +++++++++++++++++++++++++++++++------------- 1 file changed, 58 insertions(+), 23 deletions(-) diff --git a/src/decoder/image.rs b/src/decoder/image.rs index 3dcd03a..12752e4 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -9,6 +9,10 @@ use crate::tags::{ use crate::{ ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError, }; + +#[cfg(feature = "fax")] +use std::collections::VecDeque; + use std::io::{self, Cursor, Read, Seek}; use std::num::NonZeroUsize; use std::sync::Arc; @@ -381,12 +385,12 @@ impl Image { } fn create_reader<'r, R: 'r + Read>( - width: u32, - height: u32, 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), @@ -454,26 +458,58 @@ impl Image { Box::new(Cursor::new(data)) } + #[cfg(feature = "fax")] CompressionMethod::Fax4 => { - let width = u16::try_from(width)?; - let height = u16::try_from(height)?; - let mut out: Vec = Vec::with_capacity(usize::from(width) * usize::from(height)); + let width = u16::try_from(dimensions.0)?; + let height = u16::try_from(dimensions.1)?; - // all extant tiff/fax4 decoders I've found always assume that the photometric interpretation - // is `WhiteIsZero`, ignoring the tag. ImageMagick appears to generate fax4-encoded tiffs - // with the tag incorrectly set to `BlackIsZero`. - fax::decoder::decode_g4( - reader.bytes().map(|b| b.unwrap()), - width, - Some(height), - |transitions| { - out.extend(fax::decoder::pels(transitions, width).map(|c| match c { - fax::Color::Black => 255, - fax::Color::White => 0, - })) - }, - ); - Box::new(Cursor::new(out)) + struct Group4Reader { + decoder: fax::decoder::Group4Decoder>, + line_buf: VecDeque, + y: u16, + height: u16, + width: u16, + } + + impl Read for Group4Reader { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + if self.line_buf.is_empty() && self.y < self.height { + let next = self + .decoder + .advance() + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + + match next { + fax::decoder::DecodeStatus::End => (), + fax::decoder::DecodeStatus::Incomplete => { + self.y += 1; + self.line_buf.extend( + // all extant tiff/fax4 decoders I've found always assume that the photometric interpretation + // is `WhiteIsZero`, ignoring the tag. ImageMagick appears to generate fax4-encoded tiffs + // with the tag incorrectly set to `BlackIsZero`. + fax::decoder::pels(self.decoder.transition(), self.width) + .map(|c| match c { + fax::Color::Black => 255, + fax::Color::White => 0, + }), + ); + } + } + } + self.line_buf.read(buf) + } + } + + Box::new(Group4Reader { + decoder: fax::decoder::Group4Decoder::new( + reader.take(compressed_length).bytes(), + width, + )?, + line_buf: VecDeque::with_capacity(usize::from(width)), + y: 0, + width: width, + height: height, + }) } method => { return Err(TiffError::UnsupportedError( @@ -677,12 +713,11 @@ impl Image { assert!(buf.len() >= output_row_stride * (data_dims.1 as usize - 1) + data_row_bytes); let mut reader = Self::create_reader( - self.width, - self.height, reader, compression_method, *compressed_bytes, self.jpeg_tables.as_deref().map(|a| &**a), + chunk_dims, )?; if output_row_stride == chunk_row_bytes {