From f93a6380e63dc0104b0366b7d86e914d27a06a9c Mon Sep 17 00:00:00 2001 From: "A. Molzer" <5550310+197g@users.noreply.github.com> Date: Tue, 31 Mar 2026 10:56:30 +0200 Subject: [PATCH] Extra utils for reading tags --- src/decoder/mod.rs | 31 +++++++++++++++++ src/decoder/value.rs | 82 ++++++++++++++++++++++++++++++++------------ 2 files changed, 91 insertions(+), 22 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 0240126..ea16145 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -852,6 +852,15 @@ impl Decoder { self.value_reader.reader.byte_order } + /// Check if this file is a BigTIFF or a standard TIFF. + pub fn variant(&self) -> TiffVariant { + if self.value_reader.bigtiff { + TiffVariant::BigTiff + } else { + TiffVariant::Standard + } + } + #[inline] pub fn read_ifd_offset(&mut self) -> Result { if self.value_reader.bigtiff { @@ -945,10 +954,32 @@ impl Decoder { Ok(val) } + /// Create a byte reader for the value of an arbitrary IFD entry. + /// + /// Small values are stored inline inside the offset field of the entry. The threshold for this + /// depends on the [`Self::variant`] of the file. Interpret entries that have not been + /// extracted from an IFD of the current decoder with care. + /// + /// The reader returned form this method reads raw bytes. Values retrieved in this way are not + /// yet converted to the host endianess. + /// + /// Returns an error if the entry count exceeds the possible offset values or if the offset + /// of the entry would overflow the offset in the underlying stream. Otherwise returns a reader + /// that yields the bytes of the value. pub fn read_entry(&mut self, entry: ifd::Entry) -> TiffResult> { EntryBytesReader::from_entry(&mut self.value_reader, entry) } + /// Utility to create a reader like [`Self::read_entry`], from the current IFD. + /// + /// Returns an `Ok(None)` if the tag is not found in the current directory. + pub fn read_tag(&mut self, tag: Tag) -> TiffResult>> { + match self.current_ifd().find_entry(tag) { + None => Ok(None), + Some(entry) => self.read_entry(entry).map(Some), + } + } + /// Reads a TIFF IFA offset/value field #[inline] pub fn read_offset_u64(&mut self) -> Result<[u8; 8], io::Error> { diff --git a/src/decoder/value.rs b/src/decoder/value.rs index 6c8d781..7434d71 100644 --- a/src/decoder/value.rs +++ b/src/decoder/value.rs @@ -94,9 +94,7 @@ impl<'lt, R> EntryBytesReader<'lt, R> { impl Read for EntryBytesReader<'_, R> { fn read(&mut self, buf: &mut [u8]) -> io::Result { match &mut self.variant { - Variant::Immediate { data, .. } => { - data.read(buf) - } + Variant::Immediate { data, .. } => data.read(buf), Variant::InFile { remainder, .. } => { let bound = usize::try_from(*remainder).unwrap_or(usize::MAX); let avail = buf.len().min(bound); @@ -112,9 +110,7 @@ impl Read for EntryBytesReader<'_, R> { fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { match &mut self.variant { - Variant::Immediate { data, .. } => { - data.read_exact(buf) - } + Variant::Immediate { data, .. } => data.read_exact(buf), Variant::InFile { remainder, .. } => { let bound = usize::try_from(*remainder).unwrap_or(usize::MAX); @@ -197,7 +193,7 @@ impl Seek for EntryBytesReader<'_, R> { #[cfg(test)] mod tests { - use crate::{decoder::Decoder, encoder, tags::Tag}; + use crate::{decoder::Decoder, encoder, tags}; use std::io::{Read as _, Seek as _, SeekFrom}; #[test] @@ -211,22 +207,21 @@ mod tests { let mut decoder = Decoder::open(file).unwrap(); decoder.next_directory().unwrap(); - let strips = decoder - .current_ifd() - .find_entry(Tag::StripByteCounts) - .expect("some strip byte count is present"); - let mut buffer = crate::tags::ValueBuffer::default(); decoder .current_ifd() - .find_tag_buf(Tag::StripByteCounts, &mut buffer) + .find_tag_buf(tags::Tag::StripByteCounts, &mut buffer) .expect("some strip byte count is present"); - let mut reader = decoder.read_entry(strips)?; + let mut reader = decoder + .read_tag(tags::Tag::StripByteCounts)? + .expect("some strip byte count is present"); let mut data = vec![]; reader.read_to_end(&mut data)?; + + // `ValueBuffer` also stores its data in the file endianess until converted. assert_eq!(buffer.as_bytes(), data); reader.seek(SeekFrom::Start(0))?; @@ -247,7 +242,7 @@ mod tests { let mut encoder = encoder::TiffEncoder::new(&mut data)?; let mut dir = encoder.image_directory()?; - dir.write_tag(Tag::Artist, ARTIST)?; + dir.write_tag(tags::Tag::Artist, ARTIST)?; dir.finish()?; data.into_inner() @@ -257,12 +252,9 @@ mod tests { let mut decoder = Decoder::open(file).unwrap(); decoder.next_directory().unwrap(); - let artist = decoder - .current_ifd() - .find_entry(Tag::Artist) - .expect("some artist is present"); - - let mut reader = decoder.read_entry(artist)?; + let mut reader = decoder + .read_tag(tags::Tag::Artist)? + .expect("the artist is present?"); let mut value = vec![]; reader.read_to_end(&mut value)?; @@ -274,12 +266,58 @@ mod tests { assert_eq!(reader.seek(SeekFrom::End(0))?, ARTIST.len() as u64 + 1); assert!(reader.seek(SeekFrom::End(1)).is_err()); - reader.seek(SeekFrom::End(-5))?; + // Check relative seeks by comparing against the "code" + let code_offset = reader.seek(SeekFrom::End(-5))?; let mut is_code = [0u8; 4]; reader.read_exact(&mut is_code)?; + assert_eq!(is_code, *value.last_chunk::<4>().unwrap()); + assert_eq!(reader.seek(SeekFrom::Start(0))?, 0); + assert_eq!(reader.seek(SeekFrom::Current(0))?, 0); + assert!(reader.seek(SeekFrom::Current(-1)).is_err()); + + reader.seek(SeekFrom::Start(code_offset))?; + reader.read_exact(&mut is_code)?; assert_eq!(is_code, *value.last_chunk::<4>().unwrap()); Ok(()) } + + /// Check that the reader from an `offset` field also works. + #[test] + fn read_from_offset() -> Result<(), crate::error::TiffError> { + let data = { + let mut data = std::io::Cursor::new(vec![]); + let mut encoder = encoder::TiffEncoder::new(&mut data)?; + + let mut dir = encoder.image_directory()?; + dir.write_tag( + tags::Tag::PlanarConfiguration, + tags::PlanarConfiguration::Planar, + )?; + dir.finish()?; + + data.into_inner() + }; + + let file = std::io::Cursor::new(data); + let mut decoder = Decoder::open(file).unwrap(); + decoder.next_directory().unwrap(); + let bo = decoder.byte_order(); + + let mut reader = decoder + .read_tag(tags::Tag::PlanarConfiguration)? + .expect("some artist is present"); + + let mut buf = [0; 2]; + reader.read_exact(&mut buf)?; + + bo.convert(tags::Type::SHORT, &mut buf, tags::ByteOrder::BigEndian); + assert_eq!(buf, [0, 2]); + + // Check relative seeks, this item is only two bytes long. + assert!(reader.seek(SeekFrom::End(-3)).is_err()); + + Ok(()) + } }