From 7b1683687ed3912601fa2099bb4b8c768e28f0e7 Mon Sep 17 00:00:00 2001 From: "A. Molzer" <5550310+197g@users.noreply.github.com> Date: Sun, 13 Jul 2025 22:59:53 +0200 Subject: [PATCH] Split Encoder::write_directory There are two kinds of directories: those that are image directories directly in the TIFF file's main sequence, and directories that follow the structure but represent auxiliary data. The method will link the directory into the main sequence but those images have additional requirement on their IFDs which is not clear to the caller. We make that explicit and also provide a way to write auxiliary directories. --- src/encoder/mod.rs | 95 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 74 insertions(+), 21 deletions(-) diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index a6661c3..449ca76 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,15 @@ 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. + pub offset: IfdPointer, + /// The offset of its sequence link field, in our private representation. + ifd_chain: NonZeroU64, +} + 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 +300,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,21 +388,12 @@ 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()?; if let Some(prior) = self.chained_ifd_pos { @@ -355,19 +407,19 @@ 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 { + offset: IfdPointer(ifd_pointer), + ifd_chain, + }) } } @@ -643,7 +695,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 +714,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(()) } }