Reader instance for group 4-decoded images, that buffer one line at a time

This commit is contained in:
Stephen Judkins authored and Aurelia Molzer committed 2025-08-31 01:24:21 +02:00
1 parent 9fcc2a3b65
commit c1087f6f08
1 file changed
+58 -23
+58 -23
View File
@@ -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<Box<dyn Read + 'r>> {
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<u8> = 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<R2> {
decoder: fax::decoder::Group4Decoder<std::io::Bytes<R2>>,
line_buf: VecDeque<u8>,
y: u16,
height: u16,
width: u16,
}
impl<R2: Read> Read for Group4Reader<R2> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
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 {