From f9eaf9632a4cb152c129d3735857ddee6c2b68da Mon Sep 17 00:00:00 2001 From: "A. Molzer" <5550310+197g@users.noreply.github.com> Date: Mon, 17 Nov 2025 21:11:28 +0100 Subject: [PATCH] Expand PreferredLayout for planar information --- src/decoder/image.rs | 17 ++++++++++++++- src/decoder/mod.rs | 49 +++++++++++++++++++++++++++++++++++++----- tests/decode_planes.rs | 3 ++- 3 files changed, 62 insertions(+), 7 deletions(-) diff --git a/src/decoder/image.rs b/src/decoder/image.rs index a0d41eb..a0586c8 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -45,6 +45,8 @@ pub(crate) struct PlaneLayout { pub plane_stride: usize, /// Buffer offset from one plane of output to the next. pub plane_offsets: Vec, + /// Total number of bytes for all planes in given order. + pub total_bytes: usize, } impl TileAttributes { @@ -655,11 +657,23 @@ impl Image { .ok_or(TiffError::LimitsExceeded)? .try_into()?; - let plane_offsets = (0..=usize::MAX) + // Using the standard range iterator as checked_add on steroids. + let mut offset = 0..=usize::MAX; + + let plane_offsets = offset + .by_ref() + // Note: for supporting subsampling, adjust as required. .step_by(plane_stride) .take(usize::from(color.num_samples() / samples)) .collect(); + // Get the past-the-end of the last plane. + // + // This also verifies the `take` above was not short. + let Some(total_bytes) = offset.nth(plane_stride) else { + return Err(TiffError::LimitsExceeded); + }; + let chunks_across: usize = ((width - 1) / chunk_dimensions.0 + 1).try_into()?; // We would not get an offset in byte units, sorry, no bit interleaving. @@ -679,6 +693,7 @@ impl Image { chunk_row_stride, plane_stride, plane_offsets, + total_bytes, }) } diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 2813da4..2eb3b6d 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -205,6 +205,7 @@ impl<'a> DecodingBuffer<'a> { /// caller provided buffer must fit the expectations of the decoder to be filled with data from the /// current image. #[non_exhaustive] +#[derive(Debug, Clone)] pub struct BufferLayoutPreference { /// Minimum byte size of the buffer to read image data. pub len: usize, @@ -214,6 +215,8 @@ pub struct BufferLayoutPreference { pub planes: usize, /// Number of bytes used to represent one plane. pub plane_stride: Option, + /// Number of bytes of data when reading all planes. + pub complete_len: usize, } /// The count and matching discriminant for a `DecodingBuffer`. @@ -992,16 +995,47 @@ impl Decoder { let output_row_stride = (output_width as u64) .saturating_mul(self.image.samples_per_out_texel(color) as u64) .saturating_mul(self.image.bits_per_sample as u64) - .div_ceil(8); + .div_ceil(8) + .try_into()?; - self.read_chunk_to_bytes(buffer.as_bytes_mut(), chunk_index, output_row_stride) + self.read_chunk_to_bytes(buffer.as_bytes_mut(), chunk_index, output_row_stride)?; + + Ok(()) + } + + /// Read all planes from a specific tile. + /// + /// This behaves in one of two ways depending on the planar configuration of the image: + /// 1. For chunky images, this reads the specified chunk index as if by + /// [`Self::read_chunk_bytes`]. + /// 2. For planar images, this reads the chunks of each complete plane to appropriate offsets + /// in the output buffer. The size of the buffer determines the number of planes being read. + /// The `chunk_index` determines the index of the first plane being read. + pub fn read_plane_bytes(&mut self, chunk_index: u32, buffer: &mut [u8]) -> TiffResult<()> { + let layout = self.image().preferred_output_layout()?; + + let plane_offsets = [0usize]; + let used_plane_offsets = 1; + let plane_chunks = 0u32; + + for (idx, &plane_offset) in plane_offsets[..used_plane_offsets].iter().enumerate() { + let chunk_index = chunk_index + idx as u32 * plane_chunks; + + self.read_chunk_to_bytes( + &mut buffer[plane_offset..], + chunk_index, + layout.output_row_stride, + )?; + } + + Ok(()) } fn read_chunk_to_bytes( &mut self, buffer: &mut [u8], chunk_index: u32, - output_row_stride: u64, + output_row_stride: usize, ) -> TiffResult<()> { let offset = self.image.chunk_file_range(chunk_index)?.0; self.goto_offset_u64(offset)?; @@ -1009,7 +1043,7 @@ impl Decoder { self.image.expand_chunk( &mut self.value_reader, buffer, - output_row_stride.try_into()?, + output_row_stride, chunk_index, )?; @@ -1121,6 +1155,7 @@ impl Decoder { row_stride, planes: 1, plane_stride, + complete_len: layout.size(), }) } @@ -1159,7 +1194,8 @@ impl Decoder { let output_row_stride = u64::from(data_dims.0) .saturating_mul(self.image.samples_per_pixel() as u64) .saturating_mul(self.image.bits_per_sample as u64) - .div_ceil(8); + .div_ceil(8) + .try_into()?; self.read_chunk_to_bytes(buffer, chunk_index, output_row_stride)?; @@ -1201,6 +1237,7 @@ impl Decoder { row_stride: core::num::NonZeroUsize::new(layout.output_row_stride), planes: layout.plane_offsets.len(), plane_stride: core::num::NonZeroUsize::new(layout.plane_stride), + complete_len: layout.total_bytes, }) } @@ -1239,6 +1276,8 @@ impl Decoder { chunks_across, plane_stride, plane_offsets, + // We assume that is correct, so really it can be ignored. + total_bytes: _, } = self.image().preferred_output_layout()?; // Find how many planes fit into the output buffer. diff --git a/tests/decode_planes.rs b/tests/decode_planes.rs index fcaff01..5b221da 100644 --- a/tests/decode_planes.rs +++ b/tests/decode_planes.rs @@ -16,7 +16,8 @@ fn read_all_planes() { assert_eq!(layout.planes, 3); let plane_stride = layout.plane_stride.map_or(0, |x| x.get()); - let mut buffer = vec![0u8; layout.planes * plane_stride]; + eprintln!("{:?}", layout); + let mut buffer = vec![0u8; layout.complete_len]; tif.read_image_bytes(&mut buffer).unwrap(); // Mainly you can see: these are different and non-zero. Otherwise these magic constants just