diff --git a/src/decoder/image.rs b/src/decoder/image.rs index 49510a3..1145e5c 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -10,6 +10,7 @@ use crate::{ ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError, }; use std::io::{self, Cursor, Read, Seek}; +use std::num::NonZeroUsize; use std::sync::Arc; #[derive(Debug)] @@ -363,6 +364,22 @@ impl Image { } } + pub(crate) fn minimum_row_stride(&self, dims: (u32, u32)) -> Option { + let (width, height) = dims; + + let row_stride = u64::from(width) + .saturating_mul(self.samples_per_pixel() as u64) + .saturating_mul(self.bits_per_sample as u64) + .div_ceil(8); + + // Note: row stride should be smaller than the len if we have an actual buffer. If there + // are no pixels in the buffer (height _or_ width is 0) then the stride is not well defined + // and we return `None`. + (height > 0) + .then_some(row_stride as usize) + .and_then(NonZeroUsize::new) + } + fn create_reader<'r, R: 'r + Read>( reader: R, compression_method: CompressionMethod, diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 1228335..cac2591 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -1,6 +1,7 @@ use std::alloc::{Layout, LayoutError}; use std::collections::BTreeMap; use std::io::{self, Read, Seek}; +use std::num::NonZeroUsize; use crate::tags::{ CompressionMethod, IfdPointer, PhotometricInterpretation, PlanarConfiguration, Predictor, @@ -198,6 +199,19 @@ impl<'a> DecodingBuffer<'a> { } } +/// Information on the byte buffer that should be supplied to the decoder. +/// +/// This is relevant for [`Decoder::read_image_bytes`] and [`Decoder::read_chunk_bytes`] where the +/// caller provided buffer must fit the expectations of the decoder to be filled with data from the +/// current image. +#[non_exhaustive] +pub struct BufferLayoutPreference { + /// Minimum byte size of the buffer to read image data. + pub len: usize, + /// Minimum number of bytes to represent a row of image data of the requested content. + pub row_stride: Option, +} + /// The count and matching discriminant for a `DecodingBuffer`. #[derive(Clone)] enum DecodingExtent { @@ -939,26 +953,32 @@ impl Decoder { .to_result_buffer(&self.value_reader.limits) } + // FIXME: it's unusual for this method to take a `usize` dimension parameter since those would + // typically come from the image data. The preferred consistent representation should be `u32` + // and that would avoid some unusual casts `usize -> u64` with a `u64::from` instead. fn result_extent(&self, width: usize, height: usize) -> TiffResult { - let bits_per_sample = self.image().bits_per_sample; + let bits_per_sample = self.image.bits_per_sample; + // If samples take up more than one byte, we expand it to a full number (integer or float) + // and the number of samples in that integer type just counts the number in the underlying + // image itself. Otherwise, a row is represented as the byte slice of bitfields without + // expansion. let row_samples = if bits_per_sample >= 8 { width } else { - ((((width as u64) * bits_per_sample as u64) + 7) / 8) + ((width as u64) * bits_per_sample as u64) + .div_ceil(8) .try_into() .map_err(|_| TiffError::LimitsExceeded)? }; let buffer_size = row_samples .checked_mul(height) - .and_then(|x| x.checked_mul(self.image().samples_per_pixel())) + .and_then(|x| x.checked_mul(self.image.samples_per_pixel())) .ok_or(TiffError::LimitsExceeded)?; - let max_sample_bits = self.image().bits_per_sample; - Ok(match self.image().sample_format { - SampleFormat::Uint => match max_sample_bits { + SampleFormat::Uint => match bits_per_sample { n if n <= 8 => DecodingExtent::U8(buffer_size), n if n <= 16 => DecodingExtent::U16(buffer_size), n if n <= 32 => DecodingExtent::U32(buffer_size), @@ -969,7 +989,7 @@ impl Decoder { )) } }, - SampleFormat::IEEEFP => match max_sample_bits { + SampleFormat::IEEEFP => match bits_per_sample { 16 => DecodingExtent::F16(buffer_size), 32 => DecodingExtent::F32(buffer_size), 64 => DecodingExtent::F64(buffer_size), @@ -979,7 +999,7 @@ impl Decoder { )) } }, - SampleFormat::Int => match max_sample_bits { + SampleFormat::Int => match bits_per_sample { n if n <= 8 => DecodingExtent::I8(buffer_size), n if n <= 16 => DecodingExtent::I16(buffer_size), n if n <= 32 => DecodingExtent::I32(buffer_size), @@ -996,17 +1016,28 @@ impl Decoder { }) } - /// Returns the layout required to read the specified chunk with [`Self::read_chunk_bytes`]. + /// Returns the layout preferred to read the specified chunk with [`Self::read_chunk_bytes`]. /// /// Returns the layout without being specific as to the underlying type for forward /// compatibility. Note that, in general, a TIFF may contain an almost arbitrary number of /// channels of individual *bit* length and format each. /// /// See [`Self::colortype`] to describe the sample types. - pub fn image_chunk_buffer_layout(&mut self, chunk_index: u32) -> TiffResult { + pub fn image_chunk_buffer_layout( + &mut self, + chunk_index: u32, + ) -> TiffResult { let data_dims = self.image().chunk_data_dimensions(chunk_index)?; - self.result_extent(data_dims.0 as usize, data_dims.1 as usize)? - .preferred_layout() + let layout = self + .result_extent(data_dims.0 as usize, data_dims.1 as usize)? + .preferred_layout()?; + + let row_stride = self.image.minimum_row_stride(data_dims); + + Ok(BufferLayoutPreference { + len: layout.size(), + row_stride, + }) } /// Read the specified chunk (at index `chunk_index`) and return the binary data as a Vector. @@ -1062,12 +1093,19 @@ impl Decoder { /// channels of individual *bit* length and format each. /// /// See [`Self::colortype`] to describe the sample types. - pub fn image_buffer_layout(&mut self) -> TiffResult { - let width = self.image().width; - let height = self.image().height; + pub fn image_buffer_layout(&mut self) -> TiffResult { + let data_dims @ (width, height) = (self.image.width, self.image.height); - self.result_extent(width as usize, height as usize)? - .preferred_layout() + let layout = self + .result_extent(width as usize, height as usize)? + .preferred_layout()?; + + let row_stride = self.image.minimum_row_stride(data_dims); + + Ok(BufferLayoutPreference { + len: layout.size(), + row_stride, + }) } /// Decodes the entire image and return it as a Vector diff --git a/tests/decode_images.rs b/tests/decode_images.rs index bc93640..fc4b8eb 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -541,7 +541,7 @@ fn test_image_bytes(file: &str, expected: &[(ColorType, u32)]) { .image_buffer_layout() .expect("Cannot describe buffer layout"); - buffer.resize(layout.size(), 0u8); + buffer.resize(layout.len, 0u8); decoder.read_image_bytes(&mut buffer).unwrap(); // Well almost a crc..