Expand requested plane when reading chunk
This commit is contained in:
1 parent
2eef9ac088
commit
65662c4834
2 files changed
+140
-73
No files matched your search
+92
-5
@@ -30,6 +30,23 @@ pub(crate) struct TileAttributes {
|
||||
pub tile_length: usize,
|
||||
}
|
||||
|
||||
pub(crate) struct PlaneLayout {
|
||||
/// Total chunks per plane.
|
||||
pub plane_chunks: usize,
|
||||
/// Buffer offset from one row of the image to its next, in bytes.
|
||||
pub output_row_stride: usize,
|
||||
/// Buffer offset from the start of one chunk to the next within a row.
|
||||
pub chunk_row_stride: usize,
|
||||
/// Buffer offset per strip of chunks.
|
||||
pub chunks_stride: usize,
|
||||
/// Number of chunks in one strip of chunks.
|
||||
pub chunks_across: usize,
|
||||
/// Number of bytes in a plane.
|
||||
pub plane_stride: usize,
|
||||
/// Buffer offset from one plane of output to the next.
|
||||
pub plane_offsets: Vec<usize>,
|
||||
}
|
||||
|
||||
impl TileAttributes {
|
||||
pub fn tiles_across(&self) -> usize {
|
||||
self.image_width.div_ceil(self.tile_width)
|
||||
@@ -594,6 +611,77 @@ impl Image {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the layout for each output plane (i.e. one plane for non-planar tiff).
|
||||
pub(crate) fn preferred_output_layout(&self) -> TiffResult<PlaneLayout> {
|
||||
let Image { width, height, .. } = *self;
|
||||
|
||||
let chunk_dimensions = self.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.colortype()?;
|
||||
let samples = self.samples_per_out_texel(color);
|
||||
|
||||
let bits_per_sample = u64::from(self.bits_per_sample);
|
||||
|
||||
let output_row_bits = (u64::from(width) * bits_per_sample)
|
||||
.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 * bits_per_sample)
|
||||
.checked_mul(samples as u64)
|
||||
.ok_or(TiffError::LimitsExceeded)?;
|
||||
let chunk_row_stride: usize = chunk_row_bits.div_ceil(8).try_into()?;
|
||||
|
||||
let chunks_stride = output_row_bits
|
||||
.div_ceil(8)
|
||||
.checked_mul(u64::from(chunk_dimensions.1))
|
||||
.ok_or(TiffError::LimitsExceeded)?
|
||||
.try_into()?;
|
||||
|
||||
let plane_stride = output_row_bits
|
||||
.div_ceil(8)
|
||||
.checked_mul(u64::from(height))
|
||||
.ok_or(TiffError::LimitsExceeded)?
|
||||
.try_into()?;
|
||||
|
||||
let plane_offsets = (0..=usize::MAX)
|
||||
.step_by(plane_stride)
|
||||
.take(usize::from(color.num_samples() / samples))
|
||||
.collect();
|
||||
|
||||
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.
|
||||
if chunks_across > 1 && chunk_row_bits % 8 != 0 {
|
||||
return Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::MisalignedTileBoundaries,
|
||||
));
|
||||
}
|
||||
|
||||
let plane_chunks = self.chunk_offsets.len() / self.strips_per_pixel();
|
||||
|
||||
Ok(PlaneLayout {
|
||||
output_row_stride,
|
||||
plane_chunks,
|
||||
chunks_stride,
|
||||
chunks_across,
|
||||
chunk_row_stride,
|
||||
plane_stride,
|
||||
plane_offsets,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn chunk_data_dimensions(&self, chunk_index: u32) -> TiffResult<(u32, u32)> {
|
||||
let dims = self.chunk_dimensions()?;
|
||||
|
||||
@@ -640,7 +728,6 @@ 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()?;
|
||||
@@ -740,17 +827,17 @@ impl Image {
|
||||
assert!(output_row_stride >= data_row_bytes);
|
||||
assert!(buf.len() >= output_row_stride * (data_dims.1 as usize - 1) + data_row_bytes);
|
||||
|
||||
let is_all_bits = samples == usize::from(data_samples);
|
||||
let is_output_chunk_rows = output_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];
|
||||
|
||||
+48
-68
@@ -210,6 +210,10 @@ pub struct BufferLayoutPreference {
|
||||
pub len: usize,
|
||||
/// 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>,
|
||||
}
|
||||
|
||||
/// The count and matching discriminant for a `DecodingBuffer`.
|
||||
@@ -1101,15 +1105,22 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
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 row_stride = self.image.minimum_row_stride(data_dims);
|
||||
|
||||
let plane_stride = row_stride
|
||||
.and_then(|v| v.get().checked_mul(data_dims.1 as usize))
|
||||
.and_then(core::num::NonZeroUsize::new);
|
||||
|
||||
Ok(BufferLayoutPreference {
|
||||
len: layout.size(),
|
||||
row_stride,
|
||||
planes: 1,
|
||||
plane_stride,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1183,17 +1194,13 @@ 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);
|
||||
let layout = self.image().preferred_output_layout()?;
|
||||
|
||||
Ok(BufferLayoutPreference {
|
||||
len: layout.size(),
|
||||
row_stride,
|
||||
len: layout.plane_stride,
|
||||
row_stride: core::num::NonZeroUsize::new(layout.output_row_stride),
|
||||
planes: layout.plane_offsets.len(),
|
||||
plane_stride: core::num::NonZeroUsize::new(layout.plane_stride),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1224,73 +1231,46 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
/// 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_image_bytes(&mut self, buffer: &mut [u8]) -> TiffResult<()> {
|
||||
let width = self.image().width;
|
||||
let height = self.image().height;
|
||||
let image::PlaneLayout {
|
||||
plane_chunks,
|
||||
output_row_stride,
|
||||
chunk_row_stride,
|
||||
chunks_stride,
|
||||
chunks_across,
|
||||
plane_stride,
|
||||
plane_offsets,
|
||||
} = self.image().preferred_output_layout()?;
|
||||
|
||||
let extent = self.result_extent(width as usize, height as usize)?;
|
||||
extent.assert_layout(buffer.len())?;
|
||||
// Find how many planes fit into the output buffer.
|
||||
let used_plane_offsets = plane_offsets
|
||||
.iter()
|
||||
.enumerate()
|
||||
// Find the first plane that would not fit completely at its offset.
|
||||
.skip_while(|(_, &offset)| buffer.len().checked_sub(plane_stride) >= Some(offset))
|
||||
.nth(0)
|
||||
// If all planes fit, use all of them.
|
||||
.map_or(plane_offsets.len(), |(idx, _)| idx);
|
||||
|
||||
if width == 0 || height == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
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..plane_chunks {
|
||||
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,
|
||||
)?;
|
||||
let buffer_offset = y * chunks_stride + x * chunk_row_stride;
|
||||
|
||||
for (idx, &plane_offset) in plane_offsets[..used_plane_offsets].iter().enumerate() {
|
||||
let chunk = chunk + idx * plane_chunks;
|
||||
self.goto_offset_u64(self.image().chunk_offsets[chunk])?;
|
||||
|
||||
self.image.expand_chunk(
|
||||
&mut self.value_reader,
|
||||
&mut buffer[plane_offset..][buffer_offset..],
|
||||
output_row_stride,
|
||||
chunk as u32,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
Reference in new issue
Block a user