Merge pull request #333 from image-rs/read-image-all-planes

Make `read_image_to_buffer` read out all planes
This commit is contained in:
Aurelia Molzer authored and GitHub committed 2026-01-24 23:28:07 +00:00
commit 529fa64e13
2 files changed
+90 -9

No files matched your search

+42 -8
View File
@@ -1396,14 +1396,16 @@ impl<R: Read + Seek> Decoder<R> {
/// # Bugs
///
/// 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
///
/// Use `DecodingResult::resize_to` and `read_image_bytes` instead where possible, preserving
/// the buffer across multiple calls. This old method will likely keep its bugged planar
/// behavior until it is fully replaced, to ensure that existing code will not run into
/// unexpectedly large allocations that will error on limits instead.
/// Use [`Self::read_image_to_buffer`] or a combination of [`DecodingResult::resize_to`] and
/// [`Self::read_image_bytes`] instead where possible, preserving the buffer across multiple
/// calls. This old method will likely keep its bugged planar behavior until it is fully
/// 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> {
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
/// 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
///
/// ```
@@ -1443,12 +1469,20 @@ impl<R: Read + Seek> Decoder<R> {
result: &mut DecodingResult,
) -> TiffResult<BufferLayoutPreference> {
let readout = self.image().readout_for_image()?;
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)?;
self.read_image_bytes(result.as_buffer(0).as_bytes_mut())?;
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::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);
}