From ab4fb4e959927cd4fc2d559c20441751282881bb Mon Sep 17 00:00:00 2001 From: "A. Molzer" <5550310+197g@users.noreply.github.com> Date: Sun, 13 Jul 2025 23:28:24 +0200 Subject: [PATCH] Test and polish encoding extra directories --- src/encoder/mod.rs | 32 ++++++++++++++++++------ tests/encode_images.rs | 56 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 8 deletions(-) diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index 449ca76..1c95821 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -250,11 +250,20 @@ pub struct DirectoryEncoder<'a, W: 'a + Write + Seek, K: TiffKind> { /// The offset of an encoded directory in the file. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct DirectoryOffset { - /// The start of the directory, as a Tiff value. - pub offset: IfdPointer, +pub struct DirectoryOffset { + /// The start of the directory as a Tiff value. + /// + /// This is a bit of a wart in the strongly typed design of the encoder. The value _type_ must + /// itself know how to be represented in the Tiff file but that may differ based on endianess + /// as well as the offset size (`u32` or BigTIFF's `u64`). Thankfully we're allowed to + /// represent offsets with `LONG` or `IFD` in the usual case. + pub offset: K::OffsetType, + /// The start of the directory as a pure offset. + pub pointer: IfdPointer, /// The offset of its sequence link field, in our private representation. ifd_chain: NonZeroU64, + /// The kind of Tiff file the offset is for. + kind: PhantomData, } impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { @@ -315,7 +324,7 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { /// file. The encoder writes its own offset into the parent's field when [`Self::finish`] is /// called or it is dropped. This redefines the parent. An offset representation of the parent /// must be acquired by finishing it with [`Self::finish_with_offsets`]. - pub fn set_parent(&mut self, offset: &DirectoryOffset) { + pub fn set_parent(&mut self, offset: &DirectoryOffset) { self.chained_ifd_pos = Some(offset.ifd_chain); } @@ -325,7 +334,7 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { Ok(()) } - pub fn finish_with_offsets(mut self) -> TiffResult { + pub fn finish_with_offsets(mut self) -> TiffResult> { self.finish_internal() } @@ -393,13 +402,18 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { self.writer.last_written() } - fn finish_internal(&mut self) -> TiffResult { + fn finish_internal(&mut self) -> TiffResult> { let ifd_pointer = self.write_directory()?; + let offset = K::convert_offset(ifd_pointer)?; if let Some(prior) = self.chained_ifd_pos { let curr_pos = self.writer.offset(); self.writer.goto_offset(prior.get())?; + // Note how we are not writing the `offset` type itself here as doing so would need to + // go through the `TiffValue` trait—the type is not constrained by much. But for the + // trait we need a `TiffWriter` which comes with a bunch of additional state such as + // compressor etc. that we have no need for. K::write_offset(self.writer, ifd_pointer)?; self.writer.goto_offset(curr_pos)?; @@ -417,8 +431,10 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { self.dropped = true; Ok(DirectoryOffset { - offset: IfdPointer(ifd_pointer), + pointer: IfdPointer(ifd_pointer), + offset, ifd_chain, + kind: PhantomData, }) } } @@ -695,7 +711,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T, Ok(()) } - fn finish_internal(&mut self) -> TiffResult { + fn finish_internal(&mut self) -> TiffResult> { self.encoder .write_tag(Tag::StripOffsets, K::convert_slice(&self.strip_offsets))?; self.encoder.write_tag( diff --git a/tests/encode_images.rs b/tests/encode_images.rs index fb71ca5..af9f610 100644 --- a/tests/encode_images.rs +++ b/tests/encode_images.rs @@ -626,3 +626,59 @@ fn test_rows_per_strip() { } } } + +#[test] +fn test_auxiliary_directory() { + const ARTIST: &str = "Auxiliary-tiff"; + + let mut data = Cursor::new(Vec::new()); + let mut tiff = TiffEncoder::new(&mut data).unwrap(); + + { + let mut image_encoder = tiff.new_image::(1, 1).unwrap(); + image_encoder.write_strip(&[1]).unwrap(); + } + + let exif = { + let mut extra = tiff.extra_directory().unwrap(); + extra.write_tag(Tag::Artist, ARTIST).unwrap(); + extra.finish_with_offsets().unwrap() + }; + + { + let mut image_encoder = tiff.new_image::(2, 1).unwrap(); + image_encoder.write_strip(&[1, 2]).unwrap(); + let encoder = image_encoder.encoder(); + encoder.write_tag(Tag::ExifDirectory, exif.offset).unwrap(); + } + + drop(tiff); + + data.set_position(0); + let mut decoder = Decoder::new(&mut data).unwrap(); + + { + assert_eq!(decoder.dimensions().unwrap(), (1, 1)); + let _ = decoder.read_image().unwrap(); + } + + { + decoder.next_image().unwrap(); + assert_eq!(decoder.dimensions().unwrap(), (2, 1)); + let _ = decoder.read_image().unwrap(); + + let exif_location = decoder + .get_tag(Tag::ExifDirectory) + .expect("second directory missing Exif") + .into_ifd_pointer() + .expect("exif directory is not an IFD pointer"); + + let exif = decoder.read_directory(exif_location).unwrap(); + + let artist = decoder + .read_directory_tags(&exif) + .get_tag_ascii_string(Tag::Artist) + .expect("EXIF directory missing artist tag"); + assert_eq!(artist, ARTIST); + } +}