From ab0ce47230f0f1816c1e96da19f84b6b12c12f31 Mon Sep 17 00:00:00 2001 From: "A. Molzer" <5550310+197g@users.noreply.github.com> Date: Mon, 14 Jul 2025 11:01:46 +0200 Subject: [PATCH] Track non-image Directory sequence in Reader A bit nasty, since we are storing the current Directory within the Image instead of a separate attribute so we can not have one without the other directly. Instead we prioritize with another attribute. --- src/decoder/image.rs | 16 +++++++++++-- src/decoder/mod.rs | 57 ++++++++++++++++++++++++++++++++++---------- src/directory.rs | 2 +- tests/subsubifds.rs | 5 +--- 4 files changed, 60 insertions(+), 20 deletions(-) diff --git a/src/decoder/image.rs b/src/decoder/image.rs index bff237a..61f1375 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -86,7 +86,19 @@ impl Image { decoder: &mut ValueReader, ifd: Directory, ) -> TiffResult { - let mut tag_reader = TagReader { decoder, ifd: &ifd }; + let img = Self::from_ref(decoder, &ifd)?; + + Ok(Image { + ifd: Some(ifd), + ..img + }) + } + + pub(crate) fn from_ref( + decoder: &mut ValueReader, + ifd: &Directory, + ) -> TiffResult { + let mut tag_reader = TagReader { decoder, ifd }; let width = tag_reader.require_tag(Tag::ImageWidth)?.into_u32()?; let height = tag_reader.require_tag(Tag::ImageLength)?.into_u32()?; @@ -288,7 +300,7 @@ impl Image { }; Ok(Image { - ifd: Some(ifd), + ifd: None, width, height, bits_per_sample: bits_per_sample[0], diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 2710cff..eddbc76 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -349,6 +349,10 @@ where ifd_offsets: Vec, /// Map from the ifd into the `ifd_offsets` ordered list. seen_ifds: cycles::IfdCycles, + /// The directory, if we have not yet read it as an image. + /// This is prioritized _over_ the image. Hence it must _not_ be set if we are currently + /// reading a true image IFD that is instead stored in the `image` attribute. + non_image_ifd: Option, image: Image, } @@ -624,6 +628,7 @@ impl Decoder { chunk_offsets: Vec::new(), chunk_bytes: Vec::new(), }, + non_image_ifd: None, }; decoder.next_image()?; Ok(decoder) @@ -654,6 +659,7 @@ impl Decoder { /// Loads the IFD at the specified index in the list, if one exists pub fn seek_to_image(&mut self, ifd_index: usize) -> TiffResult<()> { let ifd = self.seek_to_directory(ifd_index)?; + self.non_image_ifd = None; self.image = Image::from_reader(&mut self.value_reader, ifd)?; Ok(()) } @@ -699,6 +705,7 @@ impl Decoder { /// recovered by calling [`Self::restart_at_image`] with a valid offset and a successful seek. pub fn restart_at_image(&mut self, offset: IfdPointer) -> TiffResult<()> { let ifd = self.restart_at_offset(offset)?; + self.non_image_ifd = None; self.image = Image::from_reader(&mut self.value_reader, ifd)?; Ok(()) } @@ -709,8 +716,10 @@ impl Decoder { /// interpret the directory in the sequence as an image. Instead, it may be used to read a /// sequence of auxiliary IFDs that are not necessarily images. For instance, a directory /// referred to in the SubIfd tag may be a thumbnail - pub fn restart_at_directory(&mut self, offset: IfdPointer) -> TiffResult { - self.restart_at_offset(offset) + pub fn restart_at_directory(&mut self, offset: IfdPointer) -> TiffResult<()> { + let ifd = self.restart_at_offset(offset)?; + self.non_image_ifd = Some(ifd); + Ok(()) } fn restart_at_offset(&mut self, offset: IfdPointer) -> TiffResult { @@ -726,6 +735,7 @@ impl Decoder { fn next_ifd(&mut self) -> TiffResult { let Some(next_ifd) = self.next_ifd.take() else { self.current_ifd = None; + self.non_image_ifd = None; return Err(TiffError::FormatError( TiffFormatError::ImageFileDirectoryNotFound, @@ -753,6 +763,7 @@ impl Decoder { /// To determine whether there are more images call `TIFFDecoder::more_images` instead. pub fn next_image(&mut self) -> TiffResult<()> { let ifd = self.next_ifd()?; + self.non_image_ifd = None; self.image = Image::from_reader(&mut self.value_reader, ifd)?; Ok(()) } @@ -761,8 +772,10 @@ impl Decoder { /// /// If there is no further image in the TIFF file a format error is returned. To determine /// whether there are more images call `TIFFDecoder::more_directories` instead. - pub fn next_directory(&mut self) -> TiffResult { - self.next_ifd() + pub fn next_directory(&mut self) -> TiffResult<()> { + let ifd = self.next_ifd()?; + self.non_image_ifd = Some(ifd); + Ok(()) } /// Interpret the current directory as an image. @@ -775,8 +788,15 @@ impl Decoder { TiffFormatError::ImageFileDirectoryNotFound, ))?; - let ifd = self.read_directory(current_ifd)?; - self.image = Image::from_reader(&mut self.value_reader, ifd)?; + if let Some(ifd) = &self.non_image_ifd { + self.image = Image::from_ref(&mut self.value_reader, ifd)?; + // Definitely sets an IFD but this works without unwraps. + self.image.ifd = self.non_image_ifd.take(); + } else { + // Probably re-reading an image but that's fine. + let ifd = self.read_directory(current_ifd)?; + self.image = Image::from_reader(&mut self.value_reader, ifd)?; + } Ok(()) } @@ -1283,11 +1303,22 @@ impl Decoder { } /// Get the IFD decoder for our current image IFD. - fn image_ifd(&mut self) -> IfdDecoder<'_> { + fn current_directory_ifd(&mut self) -> IfdDecoder<'_> { + // Special fallback. We do not want to error handle not having read a current directory, in + // particular as the behavior having a directory without tags will produce these errors + // anyways (most likely). Note that an empty directory is invalid in a TIFF. + static NO_IFD: Directory = Directory::empty(); + + let ifd = self + .non_image_ifd + .as_ref() + .or(self.image.ifd.as_ref()) + .unwrap_or(&NO_IFD); + IfdDecoder { inner: tag_reader::TagReader { decoder: &mut self.value_reader, - ifd: self.image.ifd.as_ref().unwrap(), + ifd, }, } } @@ -1328,25 +1359,25 @@ impl Decoder { /// Tries to retrieve a tag from the current image directory. /// Return `Ok(None)` if the tag is not present. pub fn find_tag(&mut self, tag: Tag) -> TiffResult> { - self.image_ifd().find_tag(tag) + self.current_directory_ifd().find_tag(tag) } /// Tries to retrieve a tag in the current image directory and convert it to the desired /// unsigned type. pub fn find_tag_unsigned>(&mut self, tag: Tag) -> TiffResult> { - self.image_ifd().find_tag_unsigned(tag) + self.current_directory_ifd().find_tag_unsigned(tag) } /// Tries to retrieve a tag from the current image directory and convert it to the desired /// unsigned type. Returns an error if the tag is not present. pub fn get_tag_unsigned>(&mut self, tag: Tag) -> TiffResult { - self.image_ifd().get_tag_unsigned(tag) + self.current_directory_ifd().get_tag_unsigned(tag) } /// Tries to retrieve a tag from the current image directory. /// Returns an error if the tag is not present pub fn get_tag(&mut self, tag: Tag) -> TiffResult { - self.image_ifd().get_tag(tag) + self.current_directory_ifd().get_tag(tag) } pub fn get_tag_u32(&mut self, tag: Tag) -> TiffResult { @@ -1401,7 +1432,7 @@ impl Decoder { } pub fn tag_iter(&mut self) -> impl Iterator> + '_ { - self.image_ifd().tag_iter() + self.current_directory_ifd().tag_iter() } } diff --git a/src/directory.rs b/src/directory.rs index 6a2d0ff..ea150f0 100644 --- a/src/directory.rs +++ b/src/directory.rs @@ -29,7 +29,7 @@ pub struct Directory { impl Directory { /// Create a directory in an initial state without entries. Note that an empty directory can /// not be encoded in a file, it must contain at least one entry. - pub fn empty() -> Self { + pub const fn empty() -> Self { Directory { entries: BTreeMap::new(), next_ifd: None, diff --git a/tests/subsubifds.rs b/tests/subsubifds.rs index 9933ba0..8630f03 100644 --- a/tests/subsubifds.rs +++ b/tests/subsubifds.rs @@ -123,10 +123,7 @@ fn decode_subifds() { loop { len += 1; - let position = decoder.ifd_pointer().unwrap(); - let ifd = decoder.read_directory(position).unwrap(); - - if let Ok(subifd) = decoder.read_directory_tags(&ifd).get_tag(Tag::SubIfd) { + if let Ok(subifd) = decoder.get_tag(Tag::SubIfd) { let prelen = subifds.len(); subifds.extend(subifd.into_ifd_vec().expect("Failed to decode SubIfd tag")); eprintln!("New roots: {:?}", &subifds[prelen..]);