Compare commits

...
Author SHA1 Message Date
A. Molzer 3d6b94b65a Make read_image_to_buffer read out all planes 2026-01-24 21:24:55 +01:00
2 changed files with 90 additions and 9 deletions

No files matched your search

+42 -8
View File
@@ -1396,14 +1396,16 @@ impl<R: Read + Seek> Decoder<R> {
/// # Bugs /// # Bugs
/// ///
/// When the image is stored as a planar configuration, this method will currently only read /// When the image is stored as a planar configuration, this method will currently only read
/// the first sample's plane. This will be fixed in a future major version of `tiff`. /// the first sample's plane. This will be fixed in a future major version of `tiff`. To read
/// multiple planes, [`Self::read_image_to_buffer`] can be used instead.
/// ///
/// # Intent to deprecate /// # Intent to deprecate
/// ///
/// Use `DecodingResult::resize_to` and `read_image_bytes` instead where possible, preserving /// Use [`Self::read_image_to_buffer`] or a combination of [`DecodingResult::resize_to`] and
/// the buffer across multiple calls. This old method will likely keep its bugged planar /// [`Self::read_image_bytes`] instead where possible, preserving the buffer across multiple
/// behavior until it is fully replaced, to ensure that existing code will not run into /// calls. This old method will likely keep its bugged planar behavior until it is fully
/// unexpectedly large allocations that will error on limits instead. /// replaced, to ensure that existing code will not run into unexpectedly large allocations
/// that will error on limits instead.
pub fn read_image(&mut self) -> TiffResult<DecodingResult> { pub fn read_image(&mut self) -> TiffResult<DecodingResult> {
let readout = self.image().readout_for_image()?; let readout = self.image().readout_for_image()?;
@@ -1422,6 +1424,30 @@ impl<R: Read + Seek> Decoder<R> {
/// configured limits, and then pass it to the underlying method. This is essentially a /// configured limits, and then pass it to the underlying method. This is essentially a
/// type-safe wrapper around the raw [`Self::read_image_bytes`] method. /// type-safe wrapper around the raw [`Self::read_image_bytes`] method.
/// ///
/// ## Planar behavior
///
/// If the image is stored as a planar configuration, an attempt is made to resize the buffer
/// to hold all planes. If that does not fit then only the first plane is read. Check the
/// buffer size against [`BufferLayoutPreference::complete_len`] to ensure that all planes were
/// read:
///
/// ```
/// use tiff::decoder::{Decoder, DecodingResult};
/// let mut result = DecodingResult::U8(vec![]);
///
/// let mut reader = /* */
/// # Decoder::new(std::io::Cursor::new(include_bytes!(concat!(
/// # env!("CARGO_MANIFEST_DIR"), "/tests/images/tiled-gray-i1.tif"
/// # )))).unwrap();
/// let layout = reader.read_image_to_buffer(&mut result)?;
///
/// if result.as_buffer(0).as_bytes().len() < layout.complete_len {
/// println!("Only the first plane was read");
/// }
///
/// # Ok::<_, tiff::TiffError>(())
/// ```
///
/// # Examples /// # Examples
/// ///
/// ``` /// ```
@@ -1443,12 +1469,20 @@ impl<R: Read + Seek> Decoder<R> {
result: &mut DecodingResult, result: &mut DecodingResult,
) -> TiffResult<BufferLayoutPreference> { ) -> TiffResult<BufferLayoutPreference> {
let readout = self.image().readout_for_image()?; let readout = self.image().readout_for_image()?;
let planes = readout.to_plane_layout()?; let planes = readout.to_plane_layout()?;
let layout = BufferLayoutPreference::from_planes(&planes);
let extent = readout.result_extent_for_planes(0..1)?; let num_planes = if planes.total_bytes <= self.value_reader.limits.decoding_buffer_size {
planes.plane_offsets.len() as u16
} else {
1
};
let layout = BufferLayoutPreference::from_planes(&planes);
let extent = readout.result_extent_for_planes(0..num_planes)?;
// Compatibility: if this extent is too large our configured limits we will fall back to
// reading only the first plane.
result.resize_to_extent(extent, &self.value_reader.limits)?; result.resize_to_extent(extent, &self.value_reader.limits)?;
self.read_image_bytes(result.as_buffer(0).as_bytes_mut())?; self.read_image_bytes(result.as_buffer(0).as_bytes_mut())?;
Ok(layout) Ok(layout)
+48 -1
View File
@@ -1,4 +1,4 @@
use tiff::decoder::{Decoder, TiffCodingUnit}; use tiff::decoder::{Decoder, DecodingResult, Limits, TiffCodingUnit};
use std::fs::File; use std::fs::File;
use std::path::PathBuf; use std::path::PathBuf;
@@ -74,3 +74,50 @@ fn read_first_coding_unit() {
} }
} }
} }
#[test]
fn read_planes_by_limits() {
let path = PathBuf::from(TEST_IMAGE_DIR).join("planar-rgb-u8.tif");
let file = File::open(path).unwrap();
let io = std::io::BufReader::new(file);
let mut limits = Limits::default();
let mut buffer = DecodingResult::U8(vec![]);
// Make room for 1 plane.
let mut tif = Decoder::new(io).unwrap();
tif = tif.with_limits({
limits.decoding_buffer_size = 374 * 499;
limits.clone()
});
tif.read_image_to_buffer(&mut buffer).unwrap();
assert_eq!(buffer.as_buffer(0).byte_len(), 374 * 499);
// Make room for 2 planes.
tif = tif.with_limits({
limits.decoding_buffer_size = 374 * 499 * 2;
limits.clone()
});
tif.read_image_to_buffer(&mut buffer).unwrap();
assert_eq!(buffer.as_buffer(0).byte_len(), 374 * 499);
// Make room for 3 planes.
tif = tif.with_limits({
limits.decoding_buffer_size = 374 * 499 * 3;
limits.clone()
});
tif.read_image_to_buffer(&mut buffer).unwrap();
assert_eq!(buffer.as_buffer(0).byte_len(), 374 * 499 * 3);
// Make room for 4 planes?
tif = tif.with_limits({
limits.decoding_buffer_size = 374 * 499 * 4;
limits.clone()
});
tif.read_image_to_buffer(&mut buffer).unwrap();
assert_eq!(buffer.as_buffer(0).byte_len(), 374 * 499 * 3);
}