diff --git a/src/decoder/image.rs b/src/decoder/image.rs index 8f1983b..2ffa77c 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -1,5 +1,5 @@ use super::ifd::Value; -use super::stream::PackBitsReader; +use super::stream::{PackBitsReader, ReadSamples}; use super::tag_reader::TagReader; use super::ChunkType; use super::{predict_f16, predict_f32, predict_f64, ValueReader}; @@ -11,7 +11,7 @@ use crate::{ ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError, }; -use std::io::{self, Cursor, Read, Seek}; +use std::io::{Cursor, Read, Seek}; use std::sync::Arc; #[derive(Debug)] @@ -698,7 +698,7 @@ impl Image { dimensions: (u32, u32), samples: u16, #[cfg_attr(not(feature = "fax"), allow(unused_variables))] fill_order: u16, - ) -> TiffResult> { + ) -> TiffResult> { Ok(match compression_method { CompressionMethod::None => Box::new(reader), CompressionMethod::SgiLog => match samples { @@ -1204,7 +1204,7 @@ impl Image { let mut packed = vec![0u8; chunk_row_bytes]; for row in buf.chunks_mut(layout.row_stride).take(data_dims.1 as usize) { - reader.read_exact(&mut packed)?; + reader.read_samples(&mut packed)?; let out_row = &mut row[..data_row_bytes]; unpack_bits(&packed, out_row, tiff_bps, samples_per_data_row, out_bps); @@ -1238,7 +1238,7 @@ impl Image { } else if is_output_chunk_rows && is_all_bits { // Here we can read directly into the output buffer itself. let tile = &mut buf[..chunk_row_bytes * data_dims.1 as usize]; - reader.read_exact(tile)?; + reader.read_samples(tile)?; for row in tile.chunks_mut(chunk_row_bytes) { super::fix_endianness_and_predict( @@ -1272,7 +1272,7 @@ impl Image { // this case is handled specially when needed. let mut encoded = vec![0u8; chunk_row_bytes]; for row in buf.chunks_mut(layout.row_stride).take(data_dims.1 as usize) { - reader.read_exact(&mut encoded)?; + reader.read_samples(&mut encoded)?; let row = &mut row[..data_row_bytes]; match color_type.bit_depth() { @@ -1308,11 +1308,12 @@ impl Image { // Two ways how we get here: we have more bytes in our chunk data than in the image // we are to read. Then we need to skip the rest of the data. Or we have a bigger // row stride than the chunk contains data, then we need to fill only the front. - reader.read_exact(&mut row[..used])?; - // Skip horizontal padding if chunk_row_bytes > data_row_bytes { - let len = u64::try_from(chunk_row_bytes - data_row_bytes)?; - io::copy(&mut reader.by_ref().take(len), &mut io::sink())?; + // Skip horizontal padding + let skip = u64::try_from(chunk_row_bytes - data_row_bytes)?; + reader.read_padded_samples(&mut row[..used], skip)?; + } else { + reader.read_samples(&mut row[..used])?; } super::fix_endianness_and_predict( @@ -1370,7 +1371,7 @@ impl Image { // width, and writing their horizontal padding would spill into the following // output row (and predict/invert would run past the chunk's own columns). let row = &mut row[..data_row_bytes]; - reader.read_exact(&mut encoded)?; + reader.read_samples(&mut encoded)?; Self::compact_photometric_bytes(&mut encoded, row, &photo_range); diff --git a/src/decoder/stream.rs b/src/decoder/stream.rs index 29535f2..71e8937 100644 --- a/src/decoder/stream.rs +++ b/src/decoder/stream.rs @@ -122,6 +122,39 @@ impl EndianReader { // # READERS // +/// A variant of `io::Read` that takes one block to the end. +/// +/// We want our decompression engines to be much more specialized than the standard library trait. +/// Firstly there is more information available to communicate (the tiff Type, known decompressed +/// size, …) and secondly they can potentially *reset*. This could be solved with an extension trait +/// but since we want to avoid `read` in favor of `read_exact` (or an equivalent if we want to pass +/// in a strided block buffer) a separate trait that's auto derived is favored. +/// +/// FIXME: Also consider that we will want to read subsets of samples. Via IO this is done by +/// discarding data *after* it is put into the output buffer. Yet, for some decompression that write +/// alone is considerable work where it could simply skip bits more efficiently. So the reader would +/// likely want to know about a sample mask. +pub trait ReadSamples { + fn read_samples(&mut self, buf: &mut [u8]) -> io::Result<()>; + + /// Read a row of samples and throw away some additional padding samples at the end. + /// + /// This is automatically implemented for `Self: Read` as a copy into `io::Sink`. + fn read_padded_samples(&mut self, buf: &mut [u8], discard: u64) -> io::Result<()>; +} + +impl ReadSamples for R { + fn read_samples(&mut self, buf: &mut [u8]) -> io::Result<()> { + self.read_exact(buf) + } + + fn read_padded_samples(&mut self, buf: &mut [u8], discard: u64) -> io::Result<()> { + self.read_exact(buf)?; + std::io::copy(&mut self.by_ref().take(discard), &mut std::io::sink())?; + Ok(()) + } +} + /// Type alias for the deflate Reader #[cfg(feature = "deflate")] pub type DeflateReader = flate2::read::ZlibDecoder;