Merge pull request #313 from image-rs/decode-planar-data

Implement support for planar data
This commit is contained in:
Aurelia Molzer authored and GitHub committed 2025-12-29 19:05:39 +00:00
commit 355ecc8ad9
7 files changed
+943 -340

No files matched your search

+103
View File
@@ -0,0 +1,103 @@
use tiff::decoder::{BufferLayoutPreference, Decoder, DecodingBuffer, DecodingResult};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let Some(image) = std::env::args_os().nth(1) else {
eprintln!("Usage: decode FILE");
return Ok(());
};
let file = std::fs::File::open(image)?;
let io = std::io::BufReader::new(file);
let mut reader = Decoder::new(io)?;
let mut data = DecodingResult::I8(vec![]);
for i in 0u32.. {
let colortype = reader.colortype()?;
let dimensions = reader.dimensions()?;
let layout = reader.read_image_to_buffer(&mut data)?;
debug_planes(i, &mut data, &layout, &colortype, dimensions);
if !reader.more_images() {
break;
}
reader.next_image()?
}
Ok(())
}
fn debug_planes(
index: u32,
data: &mut DecodingResult,
layout: &BufferLayoutPreference,
colortype: &tiff::ColorType,
(width, height): (u32, u32),
) {
let (depth, mut tupltype) = match colortype {
tiff::ColorType::Gray(_) => (1, "GRAYSCALE"),
tiff::ColorType::RGB(_) => (3, "RGB"),
tiff::ColorType::RGBA(_) => (4, "RGB_ALPHA"),
tiff::ColorType::Palette(_) => (1, "PALETTE"),
tiff::ColorType::CMYK(_) => (4, "CMYK"),
tiff::ColorType::Multiband { num_samples: 2, .. } => (2, "GRAYSCALE_ALPHA"),
_ => {
eprintln!("Unsupported color type for PAM output: {:?}", colortype);
return;
}
};
let maxval = match data.as_buffer(0) {
DecodingBuffer::U8(_) => u64::from(u8::MAX),
DecodingBuffer::U16(_) => u64::from(u16::MAX),
DecodingBuffer::U32(_) => u64::from(u32::MAX),
DecodingBuffer::U64(_) => u64::MAX,
// Eh, this is a good effort I guess.
DecodingBuffer::I8(items) => {
items.iter_mut().for_each(|v| *v = 0.max(*v));
u64::from(i8::MAX as u8)
}
DecodingBuffer::I16(items) => {
items.iter_mut().for_each(|v| *v = 0.max(*v));
u64::from(i16::MAX as u16)
}
DecodingBuffer::I32(items) => {
items.iter_mut().for_each(|v| *v = 0.max(*v));
u64::from(i32::MAX as u32)
}
DecodingBuffer::I64(items) => {
items.iter_mut().for_each(|v| *v = 0.max(*v));
i64::MAX as u64
}
// Not supported by PAM.
DecodingBuffer::F16(_) | DecodingBuffer::F32(_) | DecodingBuffer::F64(_) => return,
};
let byte_len = if layout.planes > 1 {
tupltype = "GRAYSCALE";
layout.plane_stride.unwrap().get()
} else {
data.as_buffer(0).byte_len()
};
// Write out this image's planes to PAM files.
let header = format!("P7\nWIDTH {width}\nHEIGHT {height}\nDEPTH {depth}\nMAXVAL {maxval}\nTUPLTYPE {tupltype}\nENDHDR\n");
for (plane, plane_data) in data
.as_buffer(0)
.as_bytes_mut()
.chunks_exact(byte_len)
.enumerate()
{
let filename = format!("image_{index:03}_plane_{plane:03}.pam");
let mut file = std::fs::File::create(filename).expect("Failed to create output file");
use std::io::Write;
file.write_all(header.as_bytes())
.expect("Failed to write PAM header");
file.write_all(plane_data)
.expect("Failed to write PAM data");
}
}
+259 -66
View File
@@ -12,7 +12,6 @@ use crate::{
};
use std::io::{self, Cursor, Read, Seek};
use std::num::NonZeroUsize;
use std::sync::Arc;
#[derive(Debug)]
@@ -88,6 +87,44 @@ pub(crate) struct Image {
pub chunk_bytes: Vec<u64>,
}
/// Describes how to read a tile-aligned portion of the image.
#[derive(Clone)]
pub(crate) struct ReadoutLayout {
/// The planar configuration, which applies to both the underlying image and the output buffer.
/// This may be relaxed if we find a clean enough way to provide it.
pub planar_config: PlanarConfiguration,
/// The sample interpretation (interpret with planar_config).
pub color: ColorType,
/// The number of bytes from one row to another.
pub minimum_row_stride: usize,
/// The format of samples (assumed uniform for now, same with depth of `ColorType`).
pub sample_format: SampleFormat,
/// Number of bytes to advance in output per row.
pub row_stride: usize,
/// Number of bytes to advance in output per chunk in width.
pub chunk_row_stride: usize,
/// Number of bytes to advance in output per chunk in height.
pub chunk_col_stride: usize,
/// Number of bytes in output from one plane to another.
pub plane_stride: usize,
/// Bits per sample in the encoded data.
pub tiff_bits_per_sample: u8,
/// Number of samples in the encoded data.
pub tiff_samples: u16,
/// Dimensions of the underlying rectangular chunks (tile or strips).
pub tiff_chunk_dimensions: (u32, u32),
/// Number of bytes in the underlying data with all samples per row of chunks.
pub tiff_row_bytes: usize,
/// Chunks until wrapping to the next row of chunks.
pub chunks_across: u32,
/// Chunks to advance to get to the next plane of chunks.
pub chunks_per_plane: u32,
}
impl Image {
pub fn from_reader<R: Read + Seek>(
decoder: &mut ValueReader<R>,
@@ -190,7 +227,7 @@ impl Image {
// Technically bits_per_sample.len() should be *equal* to samples, but libtiff also allows
// it to be a single value that applies to all samples.
if bits_per_sample.len() != samples.into() && bits_per_sample.len() != 1 {
if bits_per_sample.len() != usize::from(samples) && bits_per_sample.len() != 1 {
return Err(TiffError::FormatError(
TiffFormatError::InconsistentSizesEncountered,
));
@@ -342,7 +379,7 @@ impl Image {
pub(crate) fn colortype(&self) -> TiffResult<ColorType> {
let is_alpha_extra_samples = matches!(
self.extra_samples.as_slice(),
[ExtraSamples::AssociatedAlpha] | [ExtraSamples::UnassociatedAlpha]
[ExtraSamples::AssociatedAlpha, ..] | [ExtraSamples::UnassociatedAlpha, ..]
);
match self.photometric_interpretation {
@@ -410,38 +447,6 @@ impl Image {
}
}
/// Get the multiband color describing this with its extra samples.
pub(crate) fn color_multiband_with_extras(&self) -> ColorType {
ColorType::Multiband {
bit_depth: self.bits_per_sample,
num_samples: self.samples,
}
}
/// Describe this with an accurate color or a multiband.
pub(crate) fn color_or_fallback(&self) -> ColorType {
self.colortype()
.unwrap_or_else(|_| self.color_multiband_with_extras())
}
pub(crate) fn minimum_row_stride(&self, dims: (u32, u32)) -> Option<NonZeroUsize> {
let (width, height) = dims;
let color = self.color_or_fallback();
let row_stride = u64::from(width)
.saturating_mul(u64::from(self.samples_per_out_texel(color)))
.saturating_mul(u64::from(self.bits_per_sample))
.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,
@@ -538,9 +543,9 @@ impl Image {
/// Example with `bits_per_sample = [8, 8, 8]` and `PhotometricInterpretation::RGB`:
/// * `PlanarConfiguration::Chunky` -> 3 (RGBRGBRGB...)
/// * `PlanarConfiguration::Planar` -> 1 (RRR...) (GGG...) (BBB...)
pub(crate) fn samples_per_pixel(&self) -> usize {
pub(crate) fn samples_per_pixel(&self) -> u16 {
match self.planar_config {
PlanarConfiguration::Chunky => self.samples.into(),
PlanarConfiguration::Chunky => self.samples,
PlanarConfiguration::Planar => 1,
}
}
@@ -553,10 +558,10 @@ impl Image {
}
/// Number of strips per pixel.
pub(crate) fn strips_per_pixel(&self) -> usize {
pub(crate) fn strips_per_pixel(&self) -> u16 {
match self.planar_config {
PlanarConfiguration::Chunky => 1,
PlanarConfiguration::Planar => self.samples.into(),
PlanarConfiguration::Planar => self.samples,
}
}
@@ -594,14 +599,90 @@ impl Image {
}
}
pub(crate) fn readout_for_image(&self) -> TiffResult<ReadoutLayout> {
let Image { width, height, .. } = *self;
self.readout_for_size(width, height)
}
/// Get the layout for reading out a tile-aligned portion of the image.
///
/// The provided width and height should be less than or equal to the image dimensions.
pub(crate) fn readout_for_size(&self, width: u32, height: u32) -> TiffResult<ReadoutLayout> {
let color = self.colortype()?;
let tiff_samples = self.samples_per_pixel();
let tiff_bits_per_sample = self.bits_per_sample;
let data_samples = self.samples_per_out_texel(color);
let tiff_chunk_dimensions = self.chunk_dimensions()?;
let strips_per_pixel = self.strips_per_pixel();
let data_dimensions = (width, height);
let tiff_row_bits = (u64::from(tiff_chunk_dimensions.0) * u64::from(tiff_bits_per_sample))
.checked_mul(u64::from(tiff_samples))
.ok_or(TiffError::LimitsExceeded)?;
let tiff_row_bytes: usize = tiff_row_bits.div_ceil(8).try_into()?;
let chunk_row_bits = (u64::from(tiff_chunk_dimensions.0) * u64::from(tiff_bits_per_sample))
.checked_mul(u64::from(data_samples))
.ok_or(TiffError::LimitsExceeded)?;
let chunk_row_bytes: usize = chunk_row_bits.div_ceil(8).try_into()?;
let data_row_bits = (u64::from(data_dimensions.0) * u64::from(tiff_bits_per_sample))
.checked_mul(u64::from(data_samples))
.ok_or(TiffError::LimitsExceeded)?;
let data_row_bytes: usize = data_row_bits.div_ceil(8).try_into()?;
let chunk_col_stride: usize = data_row_bits
.div_ceil(8)
.checked_mul(u64::from(tiff_chunk_dimensions.1))
.ok_or(TiffError::LimitsExceeded)?
.try_into()?;
let plane_stride: usize = data_row_bits
.div_ceil(8)
.checked_mul(u64::from(data_dimensions.1))
.ok_or(TiffError::LimitsExceeded)?
.try_into()?;
let minimum_row_stride = data_row_bytes;
let chunks_across: u32 = data_dimensions.0.div_ceil(tiff_chunk_dimensions.0);
let chunks_per_plane = (self.chunk_offsets.len() as u32) / u32::from(strips_per_pixel);
// We would not get an offset in byte units, sorry, no bit interleaving in the output.
if chunks_across > 1 && chunk_row_bits % 8 != 0 {
return Err(TiffError::UnsupportedError(
TiffUnsupportedError::MisalignedTileBoundaries,
));
}
Ok(ReadoutLayout {
planar_config: self.planar_config,
color,
minimum_row_stride,
sample_format: self.sample_format,
row_stride: data_row_bytes,
chunk_row_stride: chunk_row_bytes,
chunk_col_stride,
plane_stride,
tiff_bits_per_sample,
tiff_samples,
tiff_chunk_dimensions,
tiff_row_bytes,
chunks_across,
chunks_per_plane,
})
}
pub(crate) fn chunk_data_dimensions(&self, chunk_index: u32) -> TiffResult<(u32, u32)> {
let dims = self.chunk_dimensions()?;
match self.chunk_type {
ChunkType::Strip => {
let strip_attrs = self.strip_decoder.as_ref().unwrap();
let strips_per_band =
self.height.saturating_sub(1) / strip_attrs.rows_per_strip + 1;
let rows_per_strip = dims.1;
let strips_per_band = self.height.div_ceil(rows_per_strip);
let strip_height_without_padding = (chunk_index % strips_per_band)
.checked_mul(dims.1)
.and_then(|x| self.height.checked_sub(x))
@@ -630,7 +711,7 @@ impl Image {
&self,
reader: &mut ValueReader<impl Read>,
buf: &mut [u8],
output_row_stride: usize,
layout: &ReadoutLayout,
chunk_index: u32,
) -> TiffResult<()> {
let ValueReader {
@@ -640,10 +721,10 @@ impl Image {
} = reader;
let byte_order = reader.byte_order;
let reader = reader.inner();
// Validate that the color type is supported.
let color_type = self.colortype()?;
let color_type = layout.color;
match color_type {
ColorType::RGB(n)
| ColorType::RGBA(n)
@@ -714,8 +795,8 @@ impl Image {
let photometric_interpretation = self.photometric_interpretation;
let predictor = self.predictor;
let samples = self.samples_per_pixel();
let data_samples = self.samples_per_out_texel(color_type);
let samples = layout.tiff_samples;
let data_samples = layout.samples_per_out_texel();
// We have two dimensions: the 2d rectangle of encoded data and the 2d rectangle this
// takes up in the output. Each has an associated count of bits per pixel. The first
@@ -726,31 +807,24 @@ impl Image {
let chunk_dims = self.chunk_dimensions()?;
let data_dims = self.chunk_data_dimensions(chunk_index)?;
let chunk_row_bits = (u64::from(chunk_dims.0) * u64::from(self.bits_per_sample))
.checked_mul(samples as u64)
.ok_or(TiffError::LimitsExceeded)?;
let chunk_row_bytes: usize = chunk_row_bits.div_ceil(8).try_into()?;
let data_row_bits = (u64::from(data_dims.0) * u64::from(self.bits_per_sample))
.checked_mul(data_samples as u64)
.ok_or(TiffError::LimitsExceeded)?;
let data_row_bytes: usize = data_row_bits.div_ceil(8).try_into()?;
let chunk_row_bytes: usize = layout.tiff_row_bytes;
let data_row_bytes: usize = layout.chunk_row_bytes(data_dims.0)?;
// TODO: Should these return errors instead?
assert!(output_row_stride >= data_row_bytes);
assert!(buf.len() >= output_row_stride * (data_dims.1 as usize - 1) + data_row_bytes);
assert!(layout.minimum_row_stride >= data_row_bytes);
assert!(buf.len() >= layout.row_stride * (data_dims.1 as usize - 1) + data_row_bytes);
let is_all_bits = samples == data_samples;
let is_output_chunk_rows = layout.row_stride == chunk_row_bytes;
let mut reader = Self::create_reader(
reader,
reader.inner(),
compression_method,
*compressed_bytes,
self.jpeg_tables.as_deref().map(|a| &**a),
chunk_dims,
)?;
let is_all_bits = samples == usize::from(data_samples);
let is_output_chunk_rows = output_row_stride == chunk_row_bytes;
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];
@@ -773,7 +847,7 @@ impl Image {
// The floating point predictor shuffles the padding bytes into the encoded output, so
// this case is handled specially when needed.
let mut encoded = vec![0u8; chunk_row_bytes];
for row in buf.chunks_mut(output_row_stride).take(data_dims.1 as usize) {
for row in buf.chunks_mut(layout.row_stride).take(data_dims.1 as usize) {
reader.read_exact(&mut encoded)?;
let row = &mut row[..data_row_bytes];
@@ -789,10 +863,14 @@ impl Image {
}
} else if is_all_bits {
// We read row-by-row but each row fits in its output buffer.
for row in buf.chunks_mut(output_row_stride).take(data_dims.1 as usize) {
for row in buf.chunks_mut(layout.row_stride).take(data_dims.1 as usize) {
let row = &mut row[..data_row_bytes];
reader.read_exact(row)?;
let used = data_row_bytes.min(chunk_row_bytes);
// 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)?;
@@ -835,7 +913,7 @@ impl Image {
let photo_range = photometric_bit_end / 8..bits_per_pixel / 8;
let mut encoded = vec![0u8; chunk_row_bytes];
for row in buf.chunks_mut(output_row_stride).take(data_dims.1 as usize) {
for row in buf.chunks_mut(layout.row_stride).take(data_dims.1 as usize) {
reader.read_exact(&mut encoded)?;
Self::compact_photometric_bytes(&mut encoded, row, &photo_range);
@@ -871,3 +949,118 @@ impl Image {
});
}
}
impl ReadoutLayout {
pub(crate) fn samples_per_out_texel(&self) -> u16 {
match self.planar_config {
PlanarConfiguration::Chunky => self.color.num_samples(),
PlanarConfiguration::Planar => 1,
}
}
// For a concrete chunk, which may be a partial border chunk, the byte length of one row of its
// pixel data.
pub(crate) fn chunk_row_bytes(&self, width: u32) -> TiffResult<usize> {
let data_samples = self.samples_per_out_texel();
let data_row_bits = (u64::from(width) * u64::from(self.tiff_bits_per_sample))
.checked_mul(u64::from(data_samples))
.ok_or(TiffError::LimitsExceeded)?;
Ok(data_row_bits.div_ceil(8).try_into()?)
}
pub(crate) fn set_row_stride(&mut self, row_stride: usize) -> Result<(), TiffError> {
if row_stride < self.minimum_row_stride {
return Err(TiffError::UsageError(
UsageError::InsufficientOutputRowStride {
needed: self.minimum_row_stride,
requested: row_stride,
},
));
}
let data_row_bytes = u64::try_from(row_stride)?;
let chunk_col_stride = data_row_bytes
.checked_mul(u64::from(self.tiff_chunk_dimensions.1))
.ok_or(TiffError::LimitsExceeded)?
.try_into()?;
let height = self.plane_stride.checked_div(self.row_stride);
let plane_stride = height
.and_then(|h| data_row_bytes.checked_mul(h as u64))
// If height was zero, or the previous stride was zero, there are no bytes in a plane
.unwrap_or(0)
.try_into()?;
self.row_stride = row_stride;
self.chunk_col_stride = chunk_col_stride;
self.plane_stride = plane_stride;
Ok(())
}
/// Reduce this down to the layout of the output planes.
pub(crate) fn to_plane_layout(&self) -> Result<PlaneLayout, TiffError> {
let num_planes = self.color.num_samples() / self.samples_per_out_texel();
// Using the standard range iterator as checked_add on steroids.
//
// Note: for supporting subsampling, adjust as required.
let mut offset = (0..=usize::MAX).step_by(self.plane_stride);
let plane_offsets = offset.by_ref().take(usize::from(num_planes)).collect();
// Get the past-the-end of the last plane.
//
// This also verifies the `take` above was not short.
let Some(total_bytes) = offset.next() else {
return Err(TiffError::LimitsExceeded);
};
Ok(PlaneLayout {
plane_offsets,
total_bytes,
readout: self.clone(),
})
}
}
/// A `ReadoutLayout` with pre-calculated plane information.
pub(crate) struct PlaneLayout {
/// The underlying readout layout.
pub readout: ReadoutLayout,
/// Buffer offset from one plane of output to the next.
pub plane_offsets: Vec<usize>,
/// Total number of bytes for all planes in given order.
pub total_bytes: usize,
}
impl PlaneLayout {
/// Return the number of planes to extract into the provided buffer.
pub(crate) fn used_planes(&self, buffer: &[impl Sized]) -> TiffResult<u16> {
self.readout.assert_min_layout(buffer)?;
let buffer_len = core::mem::size_of_val(buffer);
// Note: with differently sized planes this is dependent on the plane.
let last_plane_start = buffer_len.checked_sub(self.readout.plane_stride);
// Find how many planes fit into the output buffer.
let used_plane_offsets = self
.plane_offsets
.iter()
.enumerate()
// Find the first plane that would not fit completely at its offset.
.skip_while(|(_, &offset)| last_plane_start >= Some(offset))
.nth(0)
// If all planes fit, use all of them.
.map_or(self.plane_offsets.len(), |(idx, _)| idx);
debug_assert!(
used_plane_offsets <= usize::from(u16::MAX),
"Planes limited by number of samples, which is encoded as u16"
);
Ok(used_plane_offsets as u16)
}
}
+473 -267
View File
@@ -22,6 +22,10 @@ mod image;
mod stream;
mod tag_reader;
/// An index referring to a (rectangular) region of an image.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct TiffCodingUnit(pub u32);
/// Result of a decoding process
#[derive(Debug)]
pub enum DecodingResult {
@@ -50,94 +54,87 @@ pub enum DecodingResult {
}
impl DecodingResult {
fn new_u8(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
if size > limits.decoding_buffer_size {
/// Reallocate the buffer to decode all planes of the indicated layout.
pub fn resize_to(
&mut self,
buffer: &BufferLayoutPreference,
limits: &Limits,
) -> Result<(), TiffError> {
let sample_type = buffer.sample_type.ok_or(TiffError::UnsupportedError(
TiffUnsupportedError::UnknownInterpretation,
))?;
let extent = sample_type.extent_for_bytes(buffer.complete_len);
self.resize_to_extent(extent, limits)
}
fn resize_to_extent(
&mut self,
extent: DecodingExtent,
limits: &Limits,
) -> Result<(), TiffError> {
// FIXME: we *can* reuse the allocation sometimes.
*self = extent.to_result_buffer(limits)?;
Ok(())
}
fn new<T: Default + Copy>(
size: usize,
limits: &Limits,
from_fn: fn(Vec<T>) -> Self,
) -> TiffResult<DecodingResult> {
if size > limits.decoding_buffer_size / core::mem::size_of::<T>() {
Err(TiffError::LimitsExceeded)
} else {
Ok(DecodingResult::U8(vec![0; size]))
Ok(from_fn(vec![T::default(); size]))
}
}
fn new_u8(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
Self::new(size, limits, DecodingResult::U8)
}
fn new_u16(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
if size > limits.decoding_buffer_size / 2 {
Err(TiffError::LimitsExceeded)
} else {
Ok(DecodingResult::U16(vec![0; size]))
}
Self::new(size, limits, DecodingResult::U16)
}
fn new_u32(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
if size > limits.decoding_buffer_size / 4 {
Err(TiffError::LimitsExceeded)
} else {
Ok(DecodingResult::U32(vec![0; size]))
}
Self::new(size, limits, DecodingResult::U32)
}
fn new_u64(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
if size > limits.decoding_buffer_size / 8 {
Err(TiffError::LimitsExceeded)
} else {
Ok(DecodingResult::U64(vec![0; size]))
}
Self::new(size, limits, DecodingResult::U64)
}
fn new_f32(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
if size > limits.decoding_buffer_size / std::mem::size_of::<f32>() {
Err(TiffError::LimitsExceeded)
} else {
Ok(DecodingResult::F32(vec![0.0; size]))
}
Self::new(size, limits, DecodingResult::F32)
}
fn new_f64(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
if size > limits.decoding_buffer_size / std::mem::size_of::<f64>() {
Err(TiffError::LimitsExceeded)
} else {
Ok(DecodingResult::F64(vec![0.0; size]))
}
Self::new(size, limits, DecodingResult::F64)
}
fn new_f16(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
if size > limits.decoding_buffer_size / std::mem::size_of::<u16>() {
Err(TiffError::LimitsExceeded)
} else {
Ok(DecodingResult::F16(vec![f16::ZERO; size]))
}
Self::new(size, limits, DecodingResult::F16)
}
fn new_i8(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
if size > limits.decoding_buffer_size / std::mem::size_of::<i8>() {
Err(TiffError::LimitsExceeded)
} else {
Ok(DecodingResult::I8(vec![0; size]))
}
Self::new(size, limits, DecodingResult::I8)
}
fn new_i16(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
if size > limits.decoding_buffer_size / 2 {
Err(TiffError::LimitsExceeded)
} else {
Ok(DecodingResult::I16(vec![0; size]))
}
Self::new(size, limits, DecodingResult::I16)
}
fn new_i32(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
if size > limits.decoding_buffer_size / 4 {
Err(TiffError::LimitsExceeded)
} else {
Ok(DecodingResult::I32(vec![0; size]))
}
Self::new(size, limits, DecodingResult::I32)
}
fn new_i64(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
if size > limits.decoding_buffer_size / 8 {
Err(TiffError::LimitsExceeded)
} else {
Ok(DecodingResult::I64(vec![0; size]))
}
Self::new(size, limits, DecodingResult::I64)
}
/// Get a view of this buffer starting from the nth _sample_ of the current type.
pub fn as_buffer(&mut self, start: usize) -> DecodingBuffer<'_> {
match *self {
DecodingResult::U8(ref mut buf) => DecodingBuffer::U8(&mut buf[start..]),
@@ -182,9 +179,25 @@ pub enum DecodingBuffer<'a> {
}
impl<'a> DecodingBuffer<'a> {
fn as_bytes_mut(&mut self) -> &mut [u8] {
pub fn as_bytes(&self) -> &[u8] {
match self {
DecodingBuffer::U8(ref mut buf) => buf,
DecodingBuffer::U8(buf) => buf,
DecodingBuffer::I8(buf) => bytecast::i8_as_ne_bytes(buf),
DecodingBuffer::U16(buf) => bytecast::u16_as_ne_bytes(buf),
DecodingBuffer::I16(buf) => bytecast::i16_as_ne_bytes(buf),
DecodingBuffer::U32(buf) => bytecast::u32_as_ne_bytes(buf),
DecodingBuffer::I32(buf) => bytecast::i32_as_ne_bytes(buf),
DecodingBuffer::U64(buf) => bytecast::u64_as_ne_bytes(buf),
DecodingBuffer::I64(buf) => bytecast::i64_as_ne_bytes(buf),
DecodingBuffer::F16(buf) => bytecast::f16_as_ne_bytes(buf),
DecodingBuffer::F32(buf) => bytecast::f32_as_ne_bytes(buf),
DecodingBuffer::F64(buf) => bytecast::f64_as_ne_bytes(buf),
}
}
pub fn as_bytes_mut(&mut self) -> &mut [u8] {
match self {
DecodingBuffer::U8(buf) => buf,
DecodingBuffer::I8(buf) => bytecast::i8_as_ne_mut_bytes(buf),
DecodingBuffer::U16(buf) => bytecast::u16_as_ne_mut_bytes(buf),
DecodingBuffer::I16(buf) => bytecast::i16_as_ne_mut_bytes(buf),
@@ -197,6 +210,43 @@ impl<'a> DecodingBuffer<'a> {
DecodingBuffer::F64(buf) => bytecast::f64_as_ne_mut_bytes(buf),
}
}
pub fn byte_len(&self) -> usize {
self.as_bytes().len()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DecodingSampleType {
U8,
U16,
U32,
U64,
F16,
F32,
F64,
I8,
I16,
I32,
I64,
}
impl DecodingSampleType {
fn extent_for_bytes(self, bytes: usize) -> DecodingExtent {
match self {
DecodingSampleType::U8 => DecodingExtent::U8(bytes),
DecodingSampleType::U16 => DecodingExtent::U16(bytes.div_ceil(2)),
DecodingSampleType::U32 => DecodingExtent::U32(bytes.div_ceil(4)),
DecodingSampleType::U64 => DecodingExtent::U64(bytes.div_ceil(8)),
DecodingSampleType::I8 => DecodingExtent::I8(bytes),
DecodingSampleType::I16 => DecodingExtent::I16(bytes.div_ceil(2)),
DecodingSampleType::I32 => DecodingExtent::I32(bytes.div_ceil(4)),
DecodingSampleType::I64 => DecodingExtent::I64(bytes.div_ceil(8)),
DecodingSampleType::F16 => DecodingExtent::F16(bytes.div_ceil(2)),
DecodingSampleType::F32 => DecodingExtent::F32(bytes.div_ceil(4)),
DecodingSampleType::F64 => DecodingExtent::F64(bytes.div_ceil(8)),
}
}
}
/// Information on the byte buffer that should be supplied to the decoder.
@@ -205,11 +255,131 @@ 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,
/// The interpretation of each sample in the image.
///
/// We only support a uniform sample layout. Detailed information for mixed colors may be added
/// in the future and will become available by explicit query. The same goes for the bit-depth
/// of samples that must also be uniform.
pub sample_format: SampleFormat,
/// The type representation for each sample. Only available for depths and formats which the
/// library can describe.
pub sample_type: Option<DecodingSampleType>,
/// Minimum number of bytes to represent a row of image data of the requested content.
pub row_stride: Option<NonZeroUsize>,
/// Number of planes in the image.
pub planes: usize,
/// Number of bytes used to represent one plane.
pub plane_stride: Option<NonZeroUsize>,
/// Number of bytes of data when reading all planes.
pub complete_len: usize,
}
impl BufferLayoutPreference {
fn from_planes(layout: &image::PlaneLayout) -> Self {
BufferLayoutPreference {
len: layout.readout.plane_stride,
row_stride: core::num::NonZeroUsize::new(layout.readout.row_stride),
planes: layout.plane_offsets.len(),
plane_stride: core::num::NonZeroUsize::new(layout.readout.plane_stride),
complete_len: layout.total_bytes,
sample_format: layout.readout.sample_format,
sample_type: Self::sample_type(layout.readout.sample_format, layout.readout.color),
}
}
fn sample_type(sample_format: SampleFormat, color: ColorType) -> Option<DecodingSampleType> {
Some(match sample_format {
SampleFormat::Uint => match color.bit_depth() {
n if n <= 8 => DecodingSampleType::U8,
n if n <= 16 => DecodingSampleType::U16,
n if n <= 32 => DecodingSampleType::U32,
n if n <= 64 => DecodingSampleType::U64,
_ => return None,
},
SampleFormat::IEEEFP => match color.bit_depth() {
16 => DecodingSampleType::F16,
32 => DecodingSampleType::F32,
64 => DecodingSampleType::F64,
_ => return None,
},
SampleFormat::Int => match color.bit_depth() {
n if n <= 8 => DecodingSampleType::I8,
n if n <= 16 => DecodingSampleType::I16,
n if n <= 32 => DecodingSampleType::I32,
n if n <= 64 => DecodingSampleType::I64,
_ => return None,
},
_other => {
return None;
}
})
}
}
impl image::ReadoutLayout {
// FIXME: when planes are not homogenous (i.e. subsampled or differing depths) then the
// `readout_for_size` or `to_plane_layout` needs a parameter to determine the planes being
// read instead of assuming a constant repeated size for them.
fn result_extent_for_planes(
self: &image::ReadoutLayout,
planes: core::ops::Range<u16>,
) -> TiffResult<DecodingExtent> {
let buffer = self.to_plane_layout()?;
// The layout is for all planes. So restrict ourselves to the planes that were requested.
let offset = match buffer.plane_offsets.get(usize::from(planes.start)) {
Some(n) => *n,
None => {
return Err(TiffError::UsageError(UsageError::InvalidPlaneIndex(
planes.start,
)))
}
};
let end = match buffer.plane_offsets.get(usize::from(planes.end)) {
Some(n) => *n,
None => buffer.total_bytes,
};
let buffer_bytes = end - offset;
let bits_per_sample = self.color.bit_depth();
let Some(sample_type) = BufferLayoutPreference::sample_type(self.sample_format, self.color)
else {
if matches!(
self.sample_format,
SampleFormat::Uint | SampleFormat::Int | SampleFormat::IEEEFP
) {
return Err(TiffError::UnsupportedError(
TiffUnsupportedError::UnsupportedSampleDepth(bits_per_sample),
));
} else {
return Err(TiffError::UnsupportedError(
TiffUnsupportedError::UnsupportedSampleFormat(vec![self.sample_format]),
));
}
};
Ok(sample_type.extent_for_bytes(buffer_bytes))
}
#[inline(always)]
fn assert_min_layout<T>(&self, buffer: &[T]) -> TiffResult<()> {
if core::mem::size_of_val(buffer) < self.plane_stride {
Err(TiffError::UsageError(
UsageError::InsufficientOutputBufferSize {
needed: self.plane_stride,
provided: buffer.len(),
},
))
} else {
Ok(())
}
}
}
/// The count and matching discriminant for a `DecodingBuffer`.
@@ -266,17 +436,19 @@ impl DecodingExtent {
.map_err(overflow)
}
fn assert_layout(self, buffer: usize) -> TiffResult<()> {
let needed_bytes = self.preferred_layout()?.size();
if buffer < needed_bytes {
Err(TiffError::UsageError(
UsageError::InsufficientOutputBufferSize {
needed: needed_bytes,
provided: buffer,
},
))
} else {
Ok(())
fn sample_type(&self) -> DecodingSampleType {
match *self {
DecodingExtent::U8(_) => DecodingSampleType::U8,
DecodingExtent::U16(_) => DecodingSampleType::U16,
DecodingExtent::U32(_) => DecodingSampleType::U32,
DecodingExtent::U64(_) => DecodingSampleType::U64,
DecodingExtent::F16(_) => DecodingSampleType::F16,
DecodingExtent::F32(_) => DecodingSampleType::F32,
DecodingExtent::F64(_) => DecodingSampleType::F64,
DecodingExtent::I8(_) => DecodingSampleType::I8,
DecodingExtent::I16(_) => DecodingSampleType::I16,
DecodingExtent::I32(_) => DecodingSampleType::I32,
DecodingExtent::I64(_) => DecodingSampleType::I64,
}
}
}
@@ -367,7 +539,7 @@ pub struct IfdDecoder<'lt> {
inner: tag_reader::TagReader<'lt, dyn tag_reader::EntryDecoder + 'lt>,
}
fn rev_hpredict_nsamp(buf: &mut [u8], bit_depth: u8, samples: usize) {
fn rev_hpredict_nsamp(buf: &mut [u8], bit_depth: u8, samples: u16) {
fn one_byte_predict<const N: usize>(buf: &mut [u8]) {
for i in N..buf.len() {
buf[i] = buf[i].wrapping_add(buf[i - N]);
@@ -390,6 +562,8 @@ fn rev_hpredict_nsamp(buf: &mut [u8], bit_depth: u8, samples: usize) {
}
}
let samples = usize::from(samples);
match (bit_depth, samples) {
// Note we can't use `windows` or so due to the overlap between each iteration. We split
// the cases by the samples / lookback constant so that each is optimized individually.
@@ -456,7 +630,9 @@ fn rev_hpredict_nsamp(buf: &mut [u8], bit_depth: u8, samples: usize) {
}
}
fn predict_f32(input: &mut [u8], output: &mut [u8], samples: usize) {
fn predict_f32(input: &mut [u8], output: &mut [u8], samples: u16) {
let samples = usize::from(samples);
for i in samples..input.len() {
input[i] = input[i].wrapping_add(input[i - samples]);
}
@@ -471,7 +647,9 @@ fn predict_f32(input: &mut [u8], output: &mut [u8], samples: usize) {
}
}
fn predict_f16(input: &mut [u8], output: &mut [u8], samples: usize) {
fn predict_f16(input: &mut [u8], output: &mut [u8], samples: u16) {
let samples = usize::from(samples);
for i in samples..input.len() {
input[i] = input[i].wrapping_add(input[i - samples]);
}
@@ -484,7 +662,9 @@ fn predict_f16(input: &mut [u8], output: &mut [u8], samples: usize) {
}
}
fn predict_f64(input: &mut [u8], output: &mut [u8], samples: usize) {
fn predict_f64(input: &mut [u8], output: &mut [u8], samples: u16) {
let samples = usize::from(samples);
for i in samples..input.len() {
input[i] = input[i].wrapping_add(input[i - samples]);
}
@@ -506,7 +686,7 @@ fn predict_f64(input: &mut [u8], output: &mut [u8], samples: usize) {
fn fix_endianness_and_predict(
buf: &mut [u8],
bit_depth: u8,
samples: usize,
samples: u16,
byte_order: ByteOrder,
predictor: Predictor,
) {
@@ -977,112 +1157,21 @@ impl<R: Read + Seek> Decoder<R> {
Ok(u32::try_from(self.image().chunk_offsets.len())?)
}
pub fn read_chunk_to_buffer(
&mut self,
mut buffer: DecodingBuffer,
chunk_index: u32,
output_width: usize,
) -> TiffResult<()> {
let color = self.image.color_or_fallback();
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);
self.read_chunk_to_bytes(buffer.as_bytes_mut(), chunk_index, output_row_stride)
}
fn read_chunk_to_bytes(
&mut self,
buffer: &mut [u8],
chunk_index: u32,
output_row_stride: u64,
layout: &image::ReadoutLayout,
) -> TiffResult<()> {
let offset = self.image.chunk_file_range(chunk_index)?.0;
self.goto_offset_u64(offset)?;
self.image.expand_chunk(
&mut self.value_reader,
buffer,
output_row_stride.try_into()?,
chunk_index,
)?;
self.image
.expand_chunk(&mut self.value_reader, buffer, layout, chunk_index)?;
Ok(())
}
fn result_buffer(&self, width: usize, height: usize) -> TiffResult<DecodingResult> {
self.result_extent(width, height)?
.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<DecodingExtent> {
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)
.div_ceil(8)
.try_into()
.map_err(|_| TiffError::LimitsExceeded)?
};
let color = self.image.color_or_fallback();
let samples = self.image.samples_per_out_texel(color);
let buffer_size = row_samples
.checked_mul(height)
.and_then(|x| x.checked_mul(samples.into()))
.ok_or(TiffError::LimitsExceeded)?;
Ok(match self.image().sample_format {
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),
n if n <= 64 => DecodingExtent::U64(buffer_size),
n => {
return Err(TiffError::UnsupportedError(
TiffUnsupportedError::UnsupportedBitsPerChannel(n),
))
}
},
SampleFormat::IEEEFP => match bits_per_sample {
16 => DecodingExtent::F16(buffer_size),
32 => DecodingExtent::F32(buffer_size),
64 => DecodingExtent::F64(buffer_size),
n => {
return Err(TiffError::UnsupportedError(
TiffUnsupportedError::UnsupportedBitsPerChannel(n),
))
}
},
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),
n if n <= 64 => DecodingExtent::I64(buffer_size),
n => {
return Err(TiffError::UnsupportedError(
TiffUnsupportedError::UnsupportedBitsPerChannel(n),
))
}
},
format => {
return Err(TiffUnsupportedError::UnsupportedSampleFormat(vec![format]).into())
}
})
}
/// 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
@@ -1090,41 +1179,78 @@ impl<R: Read + Seek> Decoder<R> {
/// channels of individual *bit* length and format each.
///
/// See [`Self::colortype`] to describe the sample types.
///
/// # Bugs
///
/// When the image is stored as a planar configuration, this method will currently only
/// indicate the layout needed to read the first data plane. This will be fixed in a future
/// major version of `tiff`.
pub fn image_chunk_buffer_layout(
&mut self,
chunk_index: u32,
) -> TiffResult<BufferLayoutPreference> {
let data_dims = self.image().chunk_data_dimensions(chunk_index)?;
let layout = self
.result_extent(data_dims.0 as usize, data_dims.1 as usize)?
.preferred_layout()?;
let readout = self.image().readout_for_size(data_dims.0, data_dims.1)?;
let row_stride = self.image.minimum_row_stride(data_dims);
let extent = readout.result_extent_for_planes(0..1)?;
let sample_type = extent.sample_type();
let layout = extent.preferred_layout()?;
let row_stride = core::num::NonZeroUsize::new(readout.minimum_row_stride);
let plane_stride = core::num::NonZeroUsize::new(readout.plane_stride);
Ok(BufferLayoutPreference {
len: layout.size(),
row_stride,
planes: 1,
plane_stride,
complete_len: layout.size(),
sample_format: self.image().sample_format,
sample_type: Some(sample_type),
})
}
/// Return the layout preferred to read several planes corresponding to the specified region.
///
/// This is similar to [`Self::image_chunk_buffer_layout`] but can read chunks from all planes
/// at the corresponding coordinates of the image.
///
/// # Bugs
///
/// Sub-sampled images are not yet supported properly.
pub fn image_coding_unit_layout(
&mut self,
code_unit: TiffCodingUnit,
) -> TiffResult<BufferLayoutPreference> {
match self.image().planar_config {
PlanarConfiguration::Chunky => return self.image_chunk_buffer_layout(code_unit.0),
PlanarConfiguration::Planar => {}
}
let (width, height) = self.image().chunk_data_dimensions(code_unit.0)?;
let layout = self
.image()
.readout_for_size(width, height)?
.to_plane_layout()?;
if code_unit.0 >= layout.readout.chunks_per_plane {
return Err(TiffError::UsageError(UsageError::InvalidCodingUnit(
code_unit.0,
layout.readout.chunks_per_plane,
)));
}
Ok(BufferLayoutPreference::from_planes(&layout))
}
/// Read the specified chunk (at index `chunk_index`) and return the binary data as a Vector.
///
/// # 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`.
/// Note that for planar images each chunk contains only one sample of the underlying data.
pub fn read_chunk(&mut self, chunk_index: u32) -> TiffResult<DecodingResult> {
let data_dims = self.image().chunk_data_dimensions(chunk_index)?;
let (width, height) = self.image().chunk_data_dimensions(chunk_index)?;
let mut result = self.result_buffer(data_dims.0 as usize, data_dims.1 as usize)?;
let readout = self.image().readout_for_size(width, height)?;
self.read_chunk_to_buffer(result.as_buffer(0), chunk_index, data_dims.0 as usize)?;
let mut result = readout
.result_extent_for_planes(0..1)?
.to_result_buffer(&self.value_reader.limits)?;
self.read_chunk_to_bytes(result.as_buffer(0).as_bytes_mut(), chunk_index, &readout)?;
Ok(result)
}
@@ -1135,22 +1261,95 @@ impl<R: Read + Seek> Decoder<R> {
/// call to [`Self::image_chunk_buffer_layout`]. Note that the alignment may be arbitrary, but
/// an alignment smaller than the preferred alignment may perform worse.
///
/// Note that for planar images each chunk contains only one sample of the underlying data.
pub fn read_chunk_bytes(&mut self, chunk_index: u32, buffer: &mut [u8]) -> TiffResult<()> {
let (width, height) = self.image().chunk_data_dimensions(chunk_index)?;
let layout = self.image().readout_for_size(width, height)?;
layout.assert_min_layout(buffer)?;
self.read_chunk_to_bytes(buffer, chunk_index, &layout)?;
Ok(())
}
/// Read the specified chunk (at index `chunk_index`) into a provide buffer.
///
/// It will re-allocate the buffer into the correct type and size, within the decoder's
/// configured limits, and then pass it to the underlying method. This is essentially a
/// type-safe wrapper around the raw [`Self::read_chunk_to_bytes`] method.
///
/// Note that for planar images each chunk contains only one sample of the underlying data.
pub fn read_chunk_to_buffer(
&mut self,
buffer: &mut DecodingResult,
chunk_index: u32,
output_width: usize,
) -> TiffResult<()> {
let (width, height) = self.image().chunk_data_dimensions(chunk_index)?;
let mut layout = self.image().readout_for_size(width, height)?;
layout.set_row_stride(output_width)?;
let extent = layout.result_extent_for_planes(0..1)?;
buffer.resize_to_extent(extent, &self.value_reader.limits)?;
self.read_chunk_to_bytes(buffer.as_buffer(0).as_bytes_mut(), chunk_index, &layout)?;
Ok(())
}
/// Read chunks corresponding to several planes of a region of pixels.
///
/// For non planar images this is equivalent to [`Self::read_chunk_bytes`] as there is only one
/// plane in the image. For planar images the planes are stored consecutively into the output
/// buffer. Returns an error if not enough space for at least one plane is provided. Otherwise
/// reads all planes that can be stored completely in the provided output buffer.
///
/// A region is a rectangular assortment of pixels in the image, depending on the chunk type
/// either strips or tiles. Borrowing terminology from JPEG we call the collection of all
/// chunks from all planes that encode samples from the same region a "coding unit".
///
/// # 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`.
pub fn read_chunk_bytes(&mut self, chunk_index: u32, buffer: &mut [u8]) -> TiffResult<()> {
let data_dims = self.image().chunk_data_dimensions(chunk_index)?;
/// Sub-sampled images are not yet supported properly.
pub fn read_coding_unit_bytes(
&mut self,
slice: TiffCodingUnit,
buffer: &mut [u8],
) -> TiffResult<()> {
let (width, height) = self.image().chunk_data_dimensions(slice.0)?;
let readout = self.image().readout_for_size(width, height)?;
let extent = self.result_extent(data_dims.0 as usize, data_dims.1 as usize)?;
extent.assert_layout(buffer.len())?;
let ref layout @ image::PlaneLayout {
ref plane_offsets,
// We assume that is correct, so really it can be ignored.
total_bytes: _,
ref readout,
} = readout.to_plane_layout()?;
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);
if slice.0 >= readout.chunks_per_plane {
return Err(TiffError::UsageError(UsageError::InvalidCodingUnit(
slice.0,
readout.chunks_per_plane,
)));
}
self.read_chunk_to_bytes(buffer, chunk_index, output_row_stride)?;
// No subsamples planes support, for now.
let used_plane_offsets = usize::from(layout.used_planes(buffer)?);
debug_assert!(used_plane_offsets >= 1, "Should have errored");
for (idx, &plane_offset) in plane_offsets[..used_plane_offsets].iter().enumerate() {
let chunk = slice.0 + idx as u32 * readout.chunks_per_plane;
self.goto_offset_u64(self.image().chunk_offsets[chunk as usize])?;
self.image.expand_chunk(
&mut self.value_reader,
&mut buffer[plane_offset..],
readout,
chunk,
)?;
}
Ok(())
}
@@ -1183,115 +1382,122 @@ impl<R: Read + Seek> Decoder<R> {
/// indicate the layout needed to read the first data plane. This will be fixed in a future
/// major version of `tiff`.
pub fn image_buffer_layout(&mut self) -> TiffResult<BufferLayoutPreference> {
let data_dims @ (width, height) = (self.image.width, self.image.height);
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,
})
let layout = self.image().readout_for_image()?.to_plane_layout()?;
Ok(BufferLayoutPreference::from_planes(&layout))
}
/// Decodes the entire image and return it as a Vector
///
/// # Examples
///
/// This method is deprecated. For replacement usage see `examples/decode.rs`.
///
/// # 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`.
///
/// # 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.
pub fn read_image(&mut self) -> TiffResult<DecodingResult> {
let width = self.image().width;
let height = self.image().height;
let readout = self.image().readout_for_image()?;
let mut result = readout
.result_extent_for_planes(0..1)?
.to_result_buffer(&self.value_reader.limits)?;
let mut result = self.result_buffer(width as usize, height as usize)?;
self.read_image_bytes(result.as_buffer(0).as_bytes_mut())?;
Ok(result)
}
/// Decodes the entire image into a provided buffer.
///
/// It will re-allocate the buffer into the correct type and size, within the decoder's
/// 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.
///
/// # Examples
///
/// ```
/// use tiff::decoder::{Decoder, DecodingResult, Limits};
///
/// let mut result = DecodingResult::I8(vec![]);
///
/// let mut reader = /* */
/// # Decoder::new(std::io::Cursor::new(include_bytes!(concat!(
/// # env!("CARGO_MANIFEST_DIR"), "/tests/images/tiled-gray-i1.tif"
/// # )))).unwrap();
///
/// reader.read_image_to_buffer(&mut result)?;
///
/// # Ok::<_, tiff::TiffError>(())
/// ```
pub fn read_image_to_buffer(
&mut self,
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)?;
result.resize_to_extent(extent, &self.value_reader.limits)?;
self.read_image_bytes(result.as_buffer(0).as_bytes_mut())?;
Ok(layout)
}
/// Decodes the entire image into a provided buffer.
///
/// Returns a [`TiffError::UsageError`] if the chunk is smaller than the size indicated with a
/// call to [`Self::image_buffer_layout`]. Note that the alignment may be arbitrary, but an
/// alignment smaller than the preferred alignment may perform worse.
///
/// # Bugs
/// # Error
///
/// 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`.
/// Returns an error if the buffer fits less than one plane. In particular, for non-planar
/// images returns an error if the buffer does not fit the required size.
pub fn read_image_bytes(&mut self, buffer: &mut [u8]) -> TiffResult<()> {
let width = self.image().width;
let height = self.image().height;
let readout = self.image().readout_for_image()?;
let extent = self.result_extent(width as usize, height as usize)?;
extent.assert_layout(buffer.len())?;
let ref layout @ image::PlaneLayout {
ref plane_offsets,
// We assume that is correct, so really it can be ignored.
total_bytes: _,
ref readout,
} = readout.to_plane_layout()?;
if width == 0 || height == 0 {
return Ok(());
}
let used_plane_offsets = usize::from(layout.used_planes(buffer)?);
debug_assert!(used_plane_offsets >= 1, "Should have errored");
let chunk_dimensions = self.image().chunk_dimensions()?;
let chunk_dimensions = (
chunk_dimensions.0.min(width),
chunk_dimensions.1.min(height),
);
if chunk_dimensions.0 == 0 || chunk_dimensions.1 == 0 {
return Err(TiffError::FormatError(
TiffFormatError::InconsistentSizesEncountered,
));
}
let color = self.image().colortype()?;
let samples = self.image().samples_per_out_texel(color);
if samples == 0 {
return Err(TiffError::FormatError(
TiffFormatError::InconsistentSizesEncountered,
));
}
let output_row_bits = (width as u64 * self.image.bits_per_sample as u64)
.checked_mul(samples as u64)
.ok_or(TiffError::LimitsExceeded)?;
let output_row_stride: usize = output_row_bits.div_ceil(8).try_into()?;
let chunk_row_bits = (chunk_dimensions.0 as u64 * self.image.bits_per_sample as u64)
.checked_mul(samples as u64)
.ok_or(TiffError::LimitsExceeded)?;
let chunk_row_bytes: usize = chunk_row_bits.div_ceil(8).try_into()?;
let chunks_across = ((width - 1) / chunk_dimensions.0 + 1) as usize;
if chunks_across > 1 && chunk_row_bits % 8 != 0 {
return Err(TiffError::UnsupportedError(
TiffUnsupportedError::MisalignedTileBoundaries,
));
}
let image_chunks = self.image().chunk_offsets.len() / self.image().strips_per_pixel();
// For multi-band images, only the first band is read.
// Possible improvements:
// * pass requested band as parameter
// * collect bands to a RGB encoding result in case of RGB bands
for chunk in 0..image_chunks {
self.goto_offset_u64(self.image().chunk_offsets[chunk])?;
for chunk in 0..readout.chunks_per_plane {
let x = (chunk % readout.chunks_across) as usize;
let y = (chunk / readout.chunks_across) as usize;
let buffer_offset = y * readout.chunk_col_stride + x * readout.chunk_row_stride;
for (idx, &plane_offset) in plane_offsets[..used_plane_offsets].iter().enumerate() {
let chunk = chunk + idx as u32 * readout.chunks_per_plane;
self.goto_offset_u64(self.image().chunk_offsets[chunk as usize])?;
let x = chunk % chunks_across;
let y = chunk / chunks_across;
let buffer_offset =
y * output_row_stride * chunk_dimensions.1 as usize + x * chunk_row_bytes;
self.image.expand_chunk(
&mut self.value_reader,
&mut buffer[buffer_offset..],
output_row_stride,
chunk as u32,
&mut buffer[plane_offset..][buffer_offset..],
readout,
chunk,
)?;
}
}
Ok(())
}
+8 -2
View File
@@ -221,6 +221,8 @@ fn encoder_on_short_writes() {
};
let imgdata = vec![42u8; 100 * 100];
let mut data = crate::decoder::DecodingResult::U8(vec![]);
for comp in [
super::Compression::Uncompressed,
super::Compression::Packbits,
@@ -240,8 +242,12 @@ fn encoder_on_short_writes() {
write.inner.set_position(0);
let mut tiff_reader = crate::decoder::Decoder::new(&mut write.inner).unwrap();
let data = tiff_reader.read_image().unwrap();
let _layout = tiff_reader.read_image_to_buffer(&mut data).unwrap();
assert!(matches!(data, crate::decoder::DecodingResult::U8(v) if v == imgdata));
assert!(
matches!(&data, crate::decoder::DecodingResult::U8(v) if *v == imgdata),
"{data:?} @ {}",
data.as_buffer(0).byte_len()
);
}
}
+9
View File
@@ -175,6 +175,12 @@ quick_error! {
InvalidChunkIndex(index: u32) {
display("invalid chunk index ({index}) requested")
}
InvalidPlaneIndex(index: u16) {
display("invalid plane index ({index}) requested")
}
InvalidCodingUnit(index: u32, have: u32) {
display("out of bounds coding unit ({index}) requested, only {have} available")
}
PredictorCompressionMismatch {
display("requested predictor is not compatible with the requested compression")
}
@@ -187,6 +193,9 @@ quick_error! {
InsufficientOutputBufferSize { needed: usize, provided: usize } {
display("the borrowed output buffer is not large enough for the decoded data, needed {needed} but have {provided}")
}
InsufficientOutputRowStride { needed: usize, requested: usize } {
display("the provided output row stride would alias rows of decoded data, needed {needed} but have {requested}")
}
ZeroIfdPointer {
display("the offset 0 can not point to a valid IFD")
}
+76
View File
@@ -0,0 +1,76 @@
use tiff::decoder::{Decoder, TiffCodingUnit};
use std::fs::File;
use std::path::PathBuf;
const TEST_IMAGE_DIR: &str = "./tests/images/";
#[test]
fn read_all_planes() {
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 tif = Decoder::new(io).unwrap();
let layout = tif.image_buffer_layout().unwrap();
assert_eq!(layout.planes, 3);
let plane_stride = layout.plane_stride.map_or(0, |x| x.get());
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
// depend on the file itself that is being used.
const PLANE_EXCPECTED_SUMS: [u32; 3] = [15417630, 13007788, 11103530];
for (plane, expected) in (0..layout.planes).zip(PLANE_EXCPECTED_SUMS) {
let plane_data = &buffer[plane_stride * plane..][..layout.len];
let sum: u32 = plane_data.iter().copied().map(u32::from).sum();
assert_eq!(sum, expected);
}
}
#[test]
fn read_first_coding_unit() {
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 tif = Decoder::new(io).unwrap();
let layout = tif.image_coding_unit_layout(TiffCodingUnit(0)).unwrap();
assert_eq!(layout.planes, 3);
let plane_stride = layout.plane_stride.map_or(0, |x| x.get());
eprintln!("{:?}", layout);
let mut buffer = vec![0u8; layout.complete_len];
struct Expected {
plane_sums: [u32; 3],
unit: TiffCodingUnit,
}
// Mainly you can see: these are different and non-zero. Otherwise these magic constants just
// depend on the file itself that is being used. Also this varies by what unit we are reading.
const PLANE_EXCPECTED_SUMS: [Expected; 2] = [
Expected {
plane_sums: [856853, 767686, 688532],
unit: TiffCodingUnit(0),
},
Expected {
plane_sums: [883679, 797329, 711086],
unit: TiffCodingUnit(1),
},
];
for expected in PLANE_EXCPECTED_SUMS {
tif.read_coding_unit_bytes(expected.unit, &mut buffer)
.unwrap();
for (plane, expected) in (0..layout.planes).zip(expected.plane_sums) {
let plane_data = &buffer[plane_stride * plane..][..layout.len];
let sum: u32 = plane_data.iter().copied().map(u32::from).sum();
assert_eq!(sum, expected);
}
}
}
+15 -5
View File
@@ -81,7 +81,10 @@ fn encode_decode() {
decoder.get_tag(Tag::Artist).unwrap(),
ifd::Value::Ascii("Image-tiff".into())
);
if let DecodingResult::U8(img_res) = decoder.read_image().unwrap() {
let mut buffer = tiff::decoder::DecodingResult::U8(vec![]);
let _layout = decoder.read_image_to_buffer(&mut buffer).unwrap();
if let tiff::decoder::DecodingResult::U8(img_res) = buffer {
assert_eq!(image_data, img_res);
} else {
panic!("Wrong data type");
@@ -161,7 +164,9 @@ fn encode_decode_big() {
decoder.get_tag(Tag::Artist).unwrap(),
ifd::Value::Ascii("Image-tiff".into())
);
if let DecodingResult::U8(img_res) = decoder.read_image().unwrap() {
let mut buffer = tiff::decoder::DecodingResult::U8(vec![]);
let _layout = decoder.read_image_to_buffer(&mut buffer).unwrap();
if let DecodingResult::U8(img_res) = buffer {
assert_eq!(image_data, img_res);
} else {
panic!("Wrong data type");
@@ -245,7 +250,10 @@ macro_rules! test_roundtrip {
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(decoder.colortype().unwrap(), expected_type);
let image_data = match decoder.read_image().unwrap() {
let mut buffer = tiff::decoder::DecodingResult::U8(vec![]);
decoder.read_image_to_buffer(&mut buffer).unwrap();
let image_data = match buffer {
DecodingResult::$buffer(res) => res,
_ => panic!("Wrong data type"),
};
@@ -259,9 +267,11 @@ macro_rules! test_roundtrip {
}
file.seek(SeekFrom::Start(0)).unwrap();
{
let mut buffer = tiff::decoder::DecodingResult::U8(vec![]);
let mut decoder = Decoder::new(&mut file).unwrap();
if let DecodingResult::$buffer(img_res) = decoder.read_image().unwrap() {
assert_eq!(image_data, img_res);
decoder.read_image_to_buffer(&mut buffer).unwrap();
if let DecodingResult::$buffer(img_res) = buffer {
assert_eq!(*image_data, img_res);
} else {
panic!("Wrong data type");
}