diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index a6661c3..1c95821 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -11,7 +11,7 @@ use std::{ use crate::{ decoder::ifd::Entry, error::{TiffResult, UsageError}, - tags::{CompressionMethod, ResolutionUnit, SampleFormat, Tag, Type}, + tags::{CompressionMethod, IfdPointer, ResolutionUnit, SampleFormat, Tag, Type}, Directory, TiffError, TiffFormatError, }; @@ -170,10 +170,29 @@ impl TiffEncoder { } /// Create a [`DirectoryEncoder`] to encode an ifd directory. + #[deprecated = "`image_directory` replaced the old behavior and clarifies the intent"] + #[doc(hidden)] pub fn new_directory(&mut self) -> TiffResult> { Self::chain_directory(&mut self.writer, &mut self.last_ifd_chain) } + /// Create a [`DirectoryEncoder`] to encode an ifd directory. + /// + /// The caller is responsible for ensuring that the directory is a valid image in the main TIFF + /// IFD sequence. To encode additional directories that are not linked into the sequence, use + /// [`Self::extra_directory`][TiffEncoder::extra_directory]. + pub fn image_directory(&mut self) -> TiffResult> { + Self::chain_directory(&mut self.writer, &mut self.last_ifd_chain) + } + + /// Create a [`DirectoryEncoder`] to encode an ifd directory. + /// + /// The directory is not linked into the sequence of directories. For instance, encode Exif + /// directories or SubIfd directories with this method. + pub fn extra_directory(&mut self) -> TiffResult> { + Self::unchained_directory(&mut self.writer) + } + /// Create an [`ImageEncoder`] to encode an image one slice at a time. pub fn new_image( &mut self, @@ -207,6 +226,10 @@ impl TiffEncoder { let last_ifd = *last_ifd_chain; DirectoryEncoder::new(writer, Some(last_ifd), Some(last_ifd_chain)) } + + fn unchained_directory(writer: &mut TiffWriter) -> TiffResult> { + DirectoryEncoder::new(writer, None, None) + } } /// Low level interface to encode ifd directories. @@ -225,6 +248,24 @@ pub struct DirectoryEncoder<'a, W: 'a + Write + Seek, K: TiffKind> { dropped: bool, } +/// 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. + /// + /// 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> { /// Construct a directory writer appending to data, assuming the writer is currently positioned /// immediately after a previously written IFD to which to append. @@ -268,6 +309,35 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { Ok(()) } + /// Write some data to the tiff file, the offset of the data is returned. + /// + /// This could be used to write tiff strips. + pub fn write_data(&mut self, value: T) -> TiffResult { + let offset = self.writer.offset(); + value.write(self.writer)?; + Ok(offset) + } + + /// Define the parent directory. + /// + /// Each directory has an offset based link to its successor, forming a linked list in the + /// 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) { + self.chained_ifd_pos = Some(offset.ifd_chain); + } + + /// Write out the ifd directory itself. + pub fn finish(mut self) -> TiffResult<()> { + self.finish_internal()?; + Ok(()) + } + + pub fn finish_with_offsets(mut self) -> TiffResult> { + self.finish_internal() + } + fn write_directory(&mut self) -> TiffResult { // Start by turning all buffered unwritten values into entries. let offset = self.writer.offset(); @@ -327,27 +397,23 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { }) } - /// Write some data to the tiff file, the offset of the data is returned. - /// - /// This could be used to write tiff strips. - pub fn write_data(&mut self, value: T) -> TiffResult { - let offset = self.writer.offset(); - value.write(self.writer)?; - Ok(offset) - } - /// Provides the number of bytes written by the underlying TiffWriter during the last call. fn last_written(&self) -> u64 { 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)?; @@ -355,19 +421,21 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { K::write_offset(self.writer, 0)?; + let ifd_chain = NonZeroU64::new(self.writer.previous_ifd_pointer::()) + .expect("IFD chain field is at a non-zero offset"); + if let Some(prior) = self.write_chain.take() { - *prior = NonZeroU64::new(self.writer.previous_ifd_pointer::()) - .expect("IFD chain field is at a non-zero offset"); + *prior = ifd_chain; } self.dropped = true; - Ok(()) - } - - /// Write out the ifd directory. - pub fn finish(mut self) -> TiffResult<()> { - self.finish_internal() + Ok(DirectoryOffset { + pointer: IfdPointer(ifd_pointer), + offset, + ifd_chain, + kind: PhantomData, + }) } } @@ -643,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( @@ -662,7 +730,8 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T, /// Write out image and ifd directory. pub fn finish(mut self) -> TiffResult<()> { - self.finish_internal() + self.finish_internal()?; + Ok(()) } } 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); + } +}