Sample types in layouts, resize DecoderResult
By adding the type interpretation to `BufferLayoutPreference` (simply computed from the same fields that are trivial to fetch in `ReadoutLayout` with the rest) we can allocate an appropriate `DecoderResult` without any `Decoder` instance. We still want a Limits instance though. This lets a caller read _typed_ data from the `_bytes` methods which perform less allocations. It also lets them read multiple planes in a more convenient interface. See, `read_image` will keep its planar treatment bug for a little bit so you don't accidentally break during an upgrade. This also implies less code is being monomorphized, just a bonus.
This commit is contained in:
1 parent
af8a8d660f
commit
6d56d014a1
3 files changed
+163
-73
No files matched your search
+6
-1
@@ -10,12 +10,17 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let io = std::io::BufReader::new(file);
|
let io = std::io::BufReader::new(file);
|
||||||
let mut reader = Decoder::new(io)?;
|
let mut reader = Decoder::new(io)?;
|
||||||
|
|
||||||
|
let mut data = DecodingResult::I8(vec![]);
|
||||||
|
let limits = tiff::decoder::Limits::unlimited();
|
||||||
|
|
||||||
for i in 0u32.. {
|
for i in 0u32.. {
|
||||||
let layout = reader.image_buffer_layout()?;
|
let layout = reader.image_buffer_layout()?;
|
||||||
let colortype = reader.colortype()?;
|
let colortype = reader.colortype()?;
|
||||||
let dimensions = reader.dimensions()?;
|
let dimensions = reader.dimensions()?;
|
||||||
|
|
||||||
let mut data = reader.read_image()?;
|
data.resize_to(&layout, &limits)?;
|
||||||
|
reader.read_image_bytes(data.as_buffer(0).as_bytes_mut())?;
|
||||||
|
|
||||||
debug_planes(i, &mut data, &layout, &colortype, dimensions);
|
debug_planes(i, &mut data, &layout, &colortype, dimensions);
|
||||||
|
|
||||||
if !reader.more_images() {
|
if !reader.more_images() {
|
||||||
|
|||||||
@@ -98,6 +98,8 @@ pub(crate) struct ReadoutLayout {
|
|||||||
pub color: ColorType,
|
pub color: ColorType,
|
||||||
/// The number of bytes from one row to another.
|
/// The number of bytes from one row to another.
|
||||||
pub minimum_row_stride: usize,
|
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.
|
/// Number of bytes to advance in output per row.
|
||||||
pub row_stride: usize,
|
pub row_stride: usize,
|
||||||
@@ -659,6 +661,7 @@ impl Image {
|
|||||||
planar_config: self.planar_config,
|
planar_config: self.planar_config,
|
||||||
color,
|
color,
|
||||||
minimum_row_stride,
|
minimum_row_stride,
|
||||||
|
sample_format: self.sample_format,
|
||||||
row_stride: data_row_bytes,
|
row_stride: data_row_bytes,
|
||||||
chunk_row_stride: chunk_row_bytes,
|
chunk_row_stride: chunk_row_bytes,
|
||||||
chunk_col_stride,
|
chunk_col_stride,
|
||||||
|
|||||||
+154
-72
@@ -54,6 +54,17 @@ pub enum DecodingResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DecodingResult {
|
impl DecodingResult {
|
||||||
|
/// Reallocate the buffer to decode all planes of the indicated layout.
|
||||||
|
pub fn resize_to(
|
||||||
|
&mut self,
|
||||||
|
buffer: &BufferLayoutPreference,
|
||||||
|
limits: &Limits,
|
||||||
|
) -> Result<(), TiffError> {
|
||||||
|
let extent = DecodingExtent::U8(buffer.complete_len);
|
||||||
|
*self = extent.to_result_buffer(limits)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn new<T: Default + Copy>(
|
fn new<T: Default + Copy>(
|
||||||
size: usize,
|
size: usize,
|
||||||
limits: &Limits,
|
limits: &Limits,
|
||||||
@@ -192,6 +203,39 @@ impl<'a> DecodingBuffer<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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.
|
/// 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
|
/// This is relevant for [`Decoder::read_image_bytes`] and [`Decoder::read_chunk_bytes`] where the
|
||||||
@@ -202,6 +246,15 @@ impl<'a> DecodingBuffer<'a> {
|
|||||||
pub struct BufferLayoutPreference {
|
pub struct BufferLayoutPreference {
|
||||||
/// Minimum byte size of the buffer to read image data.
|
/// Minimum byte size of the buffer to read image data.
|
||||||
pub len: usize,
|
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.
|
/// Minimum number of bytes to represent a row of image data of the requested content.
|
||||||
pub row_stride: Option<NonZeroUsize>,
|
pub row_stride: Option<NonZeroUsize>,
|
||||||
/// Number of planes in the image.
|
/// Number of planes in the image.
|
||||||
@@ -220,11 +273,87 @@ impl BufferLayoutPreference {
|
|||||||
planes: layout.plane_offsets.len(),
|
planes: layout.plane_offsets.len(),
|
||||||
plane_stride: core::num::NonZeroUsize::new(layout.readout.plane_stride),
|
plane_stride: core::num::NonZeroUsize::new(layout.readout.plane_stride),
|
||||||
complete_len: layout.total_bytes,
|
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 {
|
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)]
|
#[inline(always)]
|
||||||
fn assert_min_layout<T>(&self, buffer: &[T]) -> TiffResult<()> {
|
fn assert_min_layout<T>(&self, buffer: &[T]) -> TiffResult<()> {
|
||||||
if core::mem::size_of_val(buffer) < self.plane_stride {
|
if core::mem::size_of_val(buffer) < self.plane_stride {
|
||||||
@@ -293,6 +422,22 @@ impl DecodingExtent {
|
|||||||
}
|
}
|
||||||
.map_err(overflow)
|
.map_err(overflow)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
@@ -1030,73 +1175,6 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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(
|
|
||||||
&self,
|
|
||||||
read: &image::ReadoutLayout,
|
|
||||||
planes: core::ops::Range<u16>,
|
|
||||||
) -> TiffResult<DecodingExtent> {
|
|
||||||
let buffer = read.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 = read.color.bit_depth();
|
|
||||||
|
|
||||||
Ok(match self.image().sample_format {
|
|
||||||
SampleFormat::Uint => match bits_per_sample {
|
|
||||||
n if n <= 8 => DecodingExtent::U8(buffer_bytes),
|
|
||||||
n if n <= 16 => DecodingExtent::U16(buffer_bytes / 2),
|
|
||||||
n if n <= 32 => DecodingExtent::U32(buffer_bytes / 4),
|
|
||||||
n if n <= 64 => DecodingExtent::U64(buffer_bytes / 8),
|
|
||||||
n => {
|
|
||||||
return Err(TiffError::UnsupportedError(
|
|
||||||
TiffUnsupportedError::UnsupportedBitsPerChannel(n),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
SampleFormat::IEEEFP => match bits_per_sample {
|
|
||||||
16 => DecodingExtent::F16(buffer_bytes / 2),
|
|
||||||
32 => DecodingExtent::F32(buffer_bytes / 4),
|
|
||||||
64 => DecodingExtent::F64(buffer_bytes / 8),
|
|
||||||
n => {
|
|
||||||
return Err(TiffError::UnsupportedError(
|
|
||||||
TiffUnsupportedError::UnsupportedBitsPerChannel(n),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
SampleFormat::Int => match bits_per_sample {
|
|
||||||
n if n <= 8 => DecodingExtent::I8(buffer_bytes),
|
|
||||||
n if n <= 16 => DecodingExtent::I16(buffer_bytes / 2),
|
|
||||||
n if n <= 32 => DecodingExtent::I32(buffer_bytes / 4),
|
|
||||||
n if n <= 64 => DecodingExtent::I64(buffer_bytes / 8),
|
|
||||||
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 preferred to read the specified chunk with [`Self::read_chunk_bytes`].
|
||||||
///
|
///
|
||||||
/// Returns the layout without being specific as to the underlying type for forward
|
/// Returns the layout without being specific as to the underlying type for forward
|
||||||
@@ -1117,7 +1195,9 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
let data_dims = self.image().chunk_data_dimensions(chunk_index)?;
|
let data_dims = self.image().chunk_data_dimensions(chunk_index)?;
|
||||||
let readout = self.image().readout_for_size(data_dims.0, data_dims.1)?;
|
let readout = self.image().readout_for_size(data_dims.0, data_dims.1)?;
|
||||||
|
|
||||||
let layout = self.result_extent(&readout, 0..1)?.preferred_layout()?;
|
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 row_stride = core::num::NonZeroUsize::new(readout.minimum_row_stride);
|
||||||
let plane_stride = core::num::NonZeroUsize::new(readout.plane_stride);
|
let plane_stride = core::num::NonZeroUsize::new(readout.plane_stride);
|
||||||
@@ -1128,6 +1208,8 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
planes: 1,
|
planes: 1,
|
||||||
plane_stride,
|
plane_stride,
|
||||||
complete_len: layout.size(),
|
complete_len: layout.size(),
|
||||||
|
sample_format: self.image().sample_format,
|
||||||
|
sample_type: Some(sample_type),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1177,8 +1259,8 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
|
|
||||||
let readout = self.image().readout_for_size(width, height)?;
|
let readout = self.image().readout_for_size(width, height)?;
|
||||||
|
|
||||||
let mut result = self
|
let mut result = readout
|
||||||
.result_extent(&readout, 0..1)?
|
.result_extent_for_planes(0..1)?
|
||||||
.to_result_buffer(&self.value_reader.limits)?;
|
.to_result_buffer(&self.value_reader.limits)?;
|
||||||
|
|
||||||
self.read_chunk_to_bytes(result.as_buffer(0).as_bytes_mut(), chunk_index, &readout)?;
|
self.read_chunk_to_bytes(result.as_buffer(0).as_bytes_mut(), chunk_index, &readout)?;
|
||||||
@@ -1303,8 +1385,8 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
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()?;
|
||||||
|
|
||||||
let mut result = self
|
let mut result = readout
|
||||||
.result_extent(&readout, 0..1)?
|
.result_extent_for_planes(0..1)?
|
||||||
.to_result_buffer(&self.value_reader.limits)?;
|
.to_result_buffer(&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())?;
|
||||||
|
|||||||
Reference in new issue
Block a user