Merge pull request #325 from image-rs/decode-values-into-buffer

Decode tag values into buffer
This commit is contained in:
Aurelia Molzer authored and GitHub committed 2026-01-11 19:19:41 +00:00
commit cf680424d9
4 files changed
+326 -11

No files matched your search

+122 -4
View File
@@ -6,7 +6,7 @@ use std::mem;
use std::str; use std::str;
use super::stream::{ByteOrder, EndianReader}; use super::stream::{ByteOrder, EndianReader};
use crate::tags::{IfdPointer, Tag, Type}; use crate::tags::{IfdPointer, Tag, Type, ValueBuffer};
use crate::{TiffError, TiffFormatError, TiffResult}; use crate::{TiffError, TiffFormatError, TiffResult};
use self::Value::{ use self::Value::{
@@ -579,7 +579,7 @@ impl Entry {
// Case 4: there is more than one value, and it doesn't fit in the offset field. // Case 4: there is more than one value, and it doesn't fit in the offset field.
let mut v; let mut v;
self.set_reader_offset(bo, bigtiff, reader)?; self.set_reader_offset_relative(bigtiff, reader, 0)?;
match self.type_ { match self.type_ {
Type::BYTE | Type::UNDEFINED => { Type::BYTE | Type::UNDEFINED => {
@@ -737,6 +737,113 @@ impl Entry {
Ok(List(v)) Ok(List(v))
} }
pub(crate) fn buffered_value<R: Read + Seek>(
&self,
buf: &mut ValueBuffer,
limits: &super::Limits,
bigtiff: bool,
reader: &mut EndianReader<R>,
) -> TiffResult<()> {
if self.count == 0 {
buf.assume_type(self.type_, 0, reader.byte_order);
return Ok(());
}
let value_bytes = self.buffer_with_capacity(buf, limits)?;
// Case 1: the value fits in the offset field.
if value_bytes <= 4 || bigtiff && value_bytes <= 8 {
let src = &self.offset[..value_bytes];
buf.raw_bytes_mut()[..value_bytes].copy_from_slice(src);
buf.assume_type(self.type_, self.count, reader.byte_order);
return Ok(());
}
// Case 2: the value is stored in the reader at an offset.
self.set_reader_offset_relative(bigtiff, reader, 0)?;
// In case of an error we set the type and endianess.
buf.assume_type(self.type_, 0, reader.byte_order);
let target = &mut buf.raw_bytes_mut()[..value_bytes];
// FIXME: if the read fails we have already grown to full size, which is not great.
reader.inner().read_exact(target)?;
buf.assume_type(self.type_, self.count, reader.byte_order);
Ok(())
}
pub(crate) fn raw_value_at<R: Read + Seek>(
&self,
buf: &mut [u8],
bigtiff: bool,
reader: &mut EndianReader<R>,
at: u64,
) -> TiffResult<usize> {
if self.count == 0 {
return Ok(0);
}
// We have no limits to handle, we do not allocate.
let value_bytes = self.type_.value_bytes(self.count)?;
// No bytes to fill into the buffer.
if at >= value_bytes {
return Ok(0);
}
// Case 1: the value fits in the offset field.
if value_bytes <= 4 || bigtiff && value_bytes <= 8 {
// `at < value_bytes` and `value_bytes <= 8` so casting is mathematical
let src = &self.offset[..value_bytes as usize][at as usize..];
let len = src.len().min(buf.len());
buf[..len].copy_from_slice(&src[..len]);
return Ok(value_bytes as usize);
}
// Case 2: the value is stored in the reader at an offset. We will find the offset
// encoded in the entry, apply the relative start position and seek there.
self.set_reader_offset_relative(bigtiff, reader, at)?;
let remainder = value_bytes - at;
let len = usize::try_from(remainder)
.unwrap_or(usize::MAX)
.min(buf.len());
let target = &mut buf[..len];
reader.inner().read_exact(target)?;
// Design note: in a previous draft we would consume the rest of the bytes of this value
// here (into a stack buffer if need be) to verify the stream itself. But in the end we
// have `Seek` so we better verify this by seeking over the rest of the bytes, finding if
// the stream continues that far. Even that is maybe bad if we wanted to provide a
// async-adaptor that `WouldBlock` errors to fill back a read window then the seek is
// poison to that, too.
// So a really simple choice: The caller is responsible for handling the fact that this did
// not verify the whole value. Attempt a 1-byte read at the end of the value instead?
Ok(len)
}
// Returns `Ok(bytes)` if our value's bytes through type and count fit into `usize` and are
// within the limits. Extends the buffer to that many bytes.
fn buffer_with_capacity(
&self,
buf: &mut ValueBuffer,
limits: &super::Limits,
) -> TiffResult<usize> {
let bytes = self.type_.value_bytes(self.count())?;
let allowed_length = usize::try_from(bytes)
.ok()
.filter(|&n| n <= limits.decoding_buffer_size)
.ok_or(TiffError::LimitsExceeded)?;
buf.prepare_length(allowed_length);
Ok(allowed_length)
}
fn vec_with_capacity( fn vec_with_capacity(
value_count: u64, value_count: u64,
limits: &super::Limits, limits: &super::Limits,
@@ -750,21 +857,32 @@ impl Entry {
Ok(Vec::with_capacity(value_count)) Ok(Vec::with_capacity(value_count))
} }
fn set_reader_offset<R>( /// Seek to an offset within a value stored in the offset defined by this entry.
fn set_reader_offset_relative<R>(
&self, &self,
bo: ByteOrder,
bigtiff: bool, bigtiff: bool,
reader: &mut EndianReader<R>, reader: &mut EndianReader<R>,
at: u64,
) -> TiffResult<()> ) -> TiffResult<()>
where where
R: Read + Seek, R: Read + Seek,
{ {
let bo = reader.byte_order;
let offset = if bigtiff { let offset = if bigtiff {
self.offset_field_reader(bo).read_u64()? self.offset_field_reader(bo).read_u64()?
} else { } else {
self.offset_field_reader(bo).read_u32()?.into() self.offset_field_reader(bo).read_u32()?.into()
}; };
// FIXME: `at` should be within `self.type_.value_bytes(self.count)` and that itself should
// be within the bounds of the stream. But we do not check this eagerly so this below will
// fail sometimes differently for exotic streams, depending on the method by which we read
// (at once or through multiple raw into-byte-slice reads).
let offset = offset.checked_add(at).ok_or(TiffError::FormatError(
TiffFormatError::InconsistentSizesEncountered,
))?;
reader.goto_offset(offset)?; reader.goto_offset(offset)?;
Ok(()) Ok(())
+97 -1
View File
@@ -5,7 +5,7 @@ use std::num::NonZeroUsize;
use crate::tags::{ use crate::tags::{
CompressionMethod, IfdPointer, PhotometricInterpretation, PlanarConfiguration, Predictor, CompressionMethod, IfdPointer, PhotometricInterpretation, PlanarConfiguration, Predictor,
SampleFormat, Tag, Type, SampleFormat, Tag, Type, ValueBuffer,
}; };
use crate::{ use crate::{
bytecast, ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, bytecast, ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError,
@@ -1717,12 +1717,41 @@ impl<R: Seek + Read> ValueReader<R> {
} }
impl IfdDecoder<'_> { impl IfdDecoder<'_> {
/// Retrieve the IFD entry for a given tag, if it exists.
///
/// The entry contains the metadata of the value, that is its type and count from which we can
/// calculate a total byte size.
pub fn find_entry(&self, tag: Tag) -> Option<ifd::Entry> {
self.inner.ifd.get(tag).cloned()
}
/// Tries to retrieve a tag. /// Tries to retrieve a tag.
/// Return `Ok(None)` if the tag is not present. /// Return `Ok(None)` if the tag is not present.
pub fn find_tag(&mut self, tag: Tag) -> TiffResult<Option<ifd::Value>> { pub fn find_tag(&mut self, tag: Tag) -> TiffResult<Option<ifd::Value>> {
self.inner.find_tag(tag) self.inner.find_tag(tag)
} }
/// Retrieve a tag and reproduce its bytes into the provided buffer.
///
/// The buffer is unmodified if the tag is not present.
pub fn find_tag_buf(
&mut self,
tag: Tag,
buf: &mut ValueBuffer,
) -> TiffResult<Option<ifd::Entry>> {
self.inner.find_tag_buf(tag, buf)
}
/// Read bytes of a tag's value into a byte buffer.
pub fn find_tag_bytes(
&mut self,
tag: Tag,
buf: &mut [u8],
offset: u64,
) -> TiffResult<Option<usize>> {
self.inner.find_tag_raw(tag, buf, offset)
}
/// Tries to retrieve a tag and convert it to the desired unsigned type. /// Tries to retrieve a tag and convert it to the desired unsigned type.
pub fn find_tag_unsigned<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<Option<T>> { pub fn find_tag_unsigned<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<Option<T>> {
self.find_tag(tag)? self.find_tag(tag)?
@@ -1836,3 +1865,70 @@ impl<'l> IfdDecoder<'l> {
}) })
} }
} }
#[cfg(test)]
mod tests {
use super::Decoder;
use crate::{
bytecast,
tags::{ByteOrder, Tag, ValueBuffer},
};
#[test]
fn equivalence_of_tag_readers() {
let file = std::fs::File::open(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/images/int8_rgb.tif"
))
.unwrap();
let mut decoder = Decoder::new(file).unwrap();
let file_bo = decoder.byte_order();
let mut ifd = decoder.image_ifd();
{
let value = ifd
.find_tag(Tag::BitsPerSample)
.unwrap()
.expect("must have BitsPerSample");
let samples = value.into_u16_vec().unwrap();
assert_eq!(samples.as_slice(), [8, 8, 8]);
}
{
let mut value = ValueBuffer::from_value(&[0u16; 4]);
let _entry = ifd
.find_tag_buf(Tag::BitsPerSample, &mut value)
.unwrap()
.expect("must have BitsPerSample");
value.set_byte_order(ByteOrder::native());
assert_eq!(value.as_bytes(), bytecast::u16_as_ne_bytes(&[8, 8, 8]));
}
{
let mut by_bytes = [0u16; 4];
let entry = ifd
.find_entry(Tag::BitsPerSample)
.expect("must have BitsPerSample");
let byte_len = ifd
.find_tag_bytes(
Tag::BitsPerSample,
bytecast::u16_as_ne_mut_bytes(&mut by_bytes),
0,
)
.unwrap()
.expect("must have BitsPerSample");
assert_eq!(byte_len, 3 * std::mem::size_of::<u16>());
file_bo.convert(
entry.field_type(),
bytecast::u16_as_ne_mut_bytes(&mut by_bytes[..3]),
ByteOrder::native(),
);
assert_eq!(&by_bytes[..3], &[8, 8, 8]);
}
}
}
+50 -2
View File
@@ -1,7 +1,9 @@
use std::io::{Read, Seek}; use std::io::{Read, Seek};
use crate::{tags::Tag, Directory}; use crate::{
use crate::{TiffError, TiffFormatError, TiffResult}; tags::{Tag, ValueBuffer},
Directory, TiffError, TiffFormatError, TiffResult,
};
use super::ifd::{Entry, Value}; use super::ifd::{Entry, Value};
use super::ValueReader; use super::ValueReader;
@@ -14,12 +16,25 @@ pub(crate) struct TagReader<'lt, R: EntryDecoder + ?Sized> {
pub(crate) trait EntryDecoder { pub(crate) trait EntryDecoder {
/// Turn an entry into a value by fetching the required bytes in the stream. /// Turn an entry into a value by fetching the required bytes in the stream.
fn entry_val(&mut self, entry: &Entry) -> TiffResult<Value>; fn entry_val(&mut self, entry: &Entry) -> TiffResult<Value>;
/// Fill a `ValueBuffer` by fetching the required bytes.
fn entry_buf(&mut self, entry: &Entry, _: &mut ValueBuffer) -> TiffResult<()>;
/// Fill the prefix of the buffer and return the actual bytes. Start interpreting the inner
/// value at `offset`.
fn entry_raw(&mut self, entry: &Entry, _: &mut [u8], offset: u64) -> TiffResult<usize>;
} }
impl<R: Seek + Read> EntryDecoder for ValueReader<R> { impl<R: Seek + Read> EntryDecoder for ValueReader<R> {
fn entry_val(&mut self, entry: &Entry) -> TiffResult<Value> { fn entry_val(&mut self, entry: &Entry) -> TiffResult<Value> {
entry.val(&self.limits, self.bigtiff, &mut self.reader) entry.val(&self.limits, self.bigtiff, &mut self.reader)
} }
fn entry_buf(&mut self, entry: &Entry, buf: &mut ValueBuffer) -> TiffResult<()> {
entry.buffered_value(buf, &self.limits, self.bigtiff, &mut self.reader)
}
fn entry_raw(&mut self, entry: &Entry, buf: &mut [u8], offset: u64) -> TiffResult<usize> {
entry.raw_value_at(buf, self.bigtiff, &mut self.reader, offset)
}
} }
impl<'a, R: EntryDecoder + ?Sized> TagReader<'a, R> { impl<'a, R: EntryDecoder + ?Sized> TagReader<'a, R> {
@@ -32,6 +47,39 @@ impl<'a, R: EntryDecoder + ?Sized> TagReader<'a, R> {
self.decoder.entry_val(&entry).map(Some) self.decoder.entry_val(&entry).map(Some)
} }
/// Find a tag and fill the provided buffer with its raw data.
///
/// Returns the entry of the tag if found. This signals that the buffer was overwritten.
/// Otherwise the buffer is preserved as-is.
pub(crate) fn find_tag_buf(
&mut self,
tag: Tag,
buf: &mut ValueBuffer,
) -> TiffResult<Option<Entry>> {
let entry = match self.ifd.get(tag) {
None => return Ok(None),
Some(entry) => entry.clone(),
};
self.decoder.entry_buf(&entry, buf)?;
Ok(Some(entry))
}
pub(crate) fn find_tag_raw(
&mut self,
tag: Tag,
buf: &mut [u8],
at: u64,
) -> TiffResult<Option<usize>> {
let entry = match self.ifd.get(tag) {
None => return Ok(None),
Some(entry) => entry.clone(),
};
let len = self.decoder.entry_raw(&entry, buf, at)?;
Ok(Some(len))
}
pub(crate) fn require_tag(&mut self, tag: Tag) -> TiffResult<Value> { pub(crate) fn require_tag(&mut self, tag: Tag) -> TiffResult<Value> {
match self.find_tag(tag)? { match self.find_tag(tag)? {
Some(val) => Ok(val), Some(val) => Ok(val),
+57 -4
View File
@@ -346,6 +346,10 @@ pub struct ValueBuffer {
/// The type of the value. /// The type of the value.
ty: Type, ty: Type,
/// The number of items, as `bytes` may be oversized while holding bytes that are initialized
/// but not used by any value.
count: u64,
/// The byte order of the value. /// The byte order of the value.
byte_order: ByteOrder, byte_order: ByteOrder,
} }
@@ -358,6 +362,7 @@ impl ValueBuffer {
ValueBuffer { ValueBuffer {
bytes: vec![], bytes: vec![],
ty, ty,
count: 0,
byte_order: ByteOrder::native(), byte_order: ByteOrder::native(),
} }
} }
@@ -367,6 +372,7 @@ impl ValueBuffer {
ValueBuffer { ValueBuffer {
bytes: value.data().into_owned(), bytes: value.data().into_owned(),
ty: <T as TiffValue>::FIELD_TYPE, ty: <T as TiffValue>::FIELD_TYPE,
count: value.count() as u64,
byte_order: ByteOrder::native(), byte_order: ByteOrder::native(),
} }
} }
@@ -381,26 +387,73 @@ impl ValueBuffer {
/// The count of items in the value. /// The count of items in the value.
pub fn count(&self) -> u64 { pub fn count(&self) -> u64 {
(self.bytes.len() / usize::from(self.ty.byte_len())) as u64 debug_assert!({
self.ty
.value_bytes(self.count)
.is_ok_and(|n| n < self.bytes.len() as u64)
});
self.count
} }
/// View the underlying raw bytes of this value. /// View the underlying raw bytes of this value.
pub fn as_bytes(&self) -> &[u8] { pub fn as_bytes(&self) -> &[u8] {
&self.bytes &self.bytes[..self.assumed_len_from_count()]
} }
/// View the underlying mutable raw bytes of this value. /// View the underlying mutable raw bytes of this value.
pub fn as_bytes_mut(&mut self) -> &mut [u8] { pub fn as_bytes_mut(&mut self) -> &mut [u8] {
&mut self.bytes let len = self.assumed_len_from_count();
&mut self.bytes[..len]
} }
/// Change the byte order of the value representation. /// Change the byte order of the value representation.
pub fn set_byte_order(&mut self, byte_order: ByteOrder) { pub fn set_byte_order(&mut self, byte_order: ByteOrder) {
let len = self.assumed_len_from_count();
self.byte_order self.byte_order
.convert(self.ty, self.bytes.as_mut_slice(), byte_order); .convert(self.ty, &mut self.bytes[..len], byte_order);
self.byte_order = byte_order; self.byte_order = byte_order;
} }
/// Prepare the internal for a value `to_len` bytes long.
///
/// Shrinks the allocation if it is far too large or extends it if it is too small. In either
/// case ensures that at least `to_len` bytes are initialized for [`Self::raw_bytes_mut`].
pub(crate) fn prepare_length(&mut self, to_len: usize) {
if to_len > self.bytes.len() {
self.bytes.resize(to_len, 0);
}
if self.bytes.len() < to_len / 2 {
self.bytes.truncate(to_len);
self.bytes.shrink_to_fit();
}
}
/// Internal method to change the type and count while re-interpreting the byte buffer.
///
/// Should only be called after writing bytes to the internal buffer prepared with
/// `Self::prepare_length`.
pub(crate) fn assume_type(&mut self, ty: Type, count: u64, bo: ByteOrder) {
debug_assert!({
ty.value_bytes(count)
.is_ok_and(|n| n < self.bytes.len() as u64)
});
self.byte_order = bo;
self.ty = ty;
self.count = count;
}
pub(crate) fn raw_bytes_mut(&mut self) -> &mut [u8] {
&mut self.bytes
}
fn assumed_len_from_count(&self) -> usize {
usize::from(self.ty.byte_len()) * self.count as usize
}
} }
/// Byte order of the TIFF file. /// Byte order of the TIFF file.