diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index 47c62e6..61ace70 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -6,7 +6,7 @@ use std::{ io::{self, Seek, Write}, marker::PhantomData, mem, - num::TryFromIntError, + num::{NonZeroU64, TryFromIntError}, }; use crate::{ @@ -107,6 +107,8 @@ pub struct TiffEncoder { kind: PhantomData, predictor: Predictor, compression: Compression, + /// The offset of the last main image directory's `next` field. + last_ifd_chain: NonZeroU64, } /// Constructor functions to create standard Tiff files. @@ -135,16 +137,19 @@ impl TiffEncoder { impl TiffEncoder { /// Creates a new Tiff or BigTiff encoder, inferred from the return type. pub fn new_generic(writer: W) -> TiffResult { - let mut encoder = TiffEncoder { - writer: TiffWriter::new(writer), + let mut writer = TiffWriter::new(writer); + K::write_header(&mut writer)?; + + let last_ifd_chain = NonZeroU64::new(writer.previous_ifd_pointer::()) + .expect("Header is at a non-zero offset"); + + Ok(TiffEncoder { + writer, kind: PhantomData, predictor: Predictor::None, compression: Compression::Uncompressed, - }; - - K::write_header(&mut encoder.writer)?; - - Ok(encoder) + last_ifd_chain, + }) } /// Set the predictor to use @@ -165,8 +170,8 @@ impl TiffEncoder { } /// Create a [`DirectoryEncoder`] to encode an ifd directory. - pub fn new_directory(&mut self) -> TiffResult> { - DirectoryEncoder::new(&mut self.writer) + pub fn new_directory(&mut self) -> TiffResult> { + Self::chain_directory(&mut self.writer, &mut self.last_ifd_chain) } /// Create an [`ImageEncoder`] to encode an image one slice at a time. @@ -175,7 +180,7 @@ impl TiffEncoder { width: u32, height: u32, ) -> TiffResult> { - let encoder = DirectoryEncoder::new(&mut self.writer)?; + let encoder = Self::chain_directory(&mut self.writer, &mut self.last_ifd_chain)?; ImageEncoder::new(encoder, width, height, self.compression, self.predictor) } @@ -189,11 +194,19 @@ impl TiffEncoder { where [C::Inner]: TiffValue, { - let encoder = DirectoryEncoder::new(&mut self.writer)?; + let encoder = Self::chain_directory(&mut self.writer, &mut self.last_ifd_chain)?; let image: ImageEncoder = ImageEncoder::new(encoder, width, height, self.compression, self.predictor)?; image.write_data(data) } + + fn chain_directory<'lt>( + writer: &'lt mut TiffWriter, + last_ifd_chain: &'lt mut NonZeroU64, + ) -> TiffResult> { + let last_ifd = *last_ifd_chain; + DirectoryEncoder::new(writer, Some(last_ifd), Some(last_ifd_chain)) + } } /// Low level interface to encode ifd directories. @@ -202,22 +215,30 @@ impl TiffEncoder { /// Encoding can silently fail while this is dropping. pub struct DirectoryEncoder<'a, W: 'a + Write + Seek, K: TiffKind> { writer: &'a mut TiffWriter, - dropped: bool, + /// The position of the previous directory's `next` field, if any. + chained_ifd_pos: Option, + /// An output to write the `next` field offset on completion. + write_chain: Option<&'a mut NonZeroU64>, // We use BTreeMap to make sure tags are written in correct order - ifd_pointer_pos: u64, ifd: BTreeMap>, + dropped: bool, } impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { - fn new(writer: &'a mut TiffWriter) -> TiffResult { - // the previous word is the IFD offset position - let ifd_pointer_pos = writer.offset() - mem::size_of::() as u64; + /// Construct a directory writer appending to data, assuming the writer is currently positioned + /// immediately after a previously written IFD to which to append. + fn new( + writer: &'a mut TiffWriter, + chained_ifd_pos: Option, + chain_into: Option<&'a mut NonZeroU64>, + ) -> TiffResult { writer.pad_word_boundary()?; // TODO: Do we need to adjust this for BigTiff? Ok(DirectoryEncoder { writer, - dropped: false, - ifd_pointer_pos, + chained_ifd_pos, + write_chain: chain_into, ifd: BTreeMap::new(), + dropped: false, }) } @@ -300,13 +321,23 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { fn finish_internal(&mut self) -> TiffResult<()> { let ifd_pointer = self.write_directory()?; - let curr_pos = self.writer.offset(); - self.writer.goto_offset(self.ifd_pointer_pos)?; - K::write_offset(self.writer, ifd_pointer)?; - self.writer.goto_offset(curr_pos)?; + if let Some(prior) = self.chained_ifd_pos { + let curr_pos = self.writer.offset(); + + self.writer.goto_offset(prior.get())?; + K::write_offset(self.writer, ifd_pointer)?; + + self.writer.goto_offset(curr_pos)?; + } + K::write_offset(self.writer, 0)?; + 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"); + } + self.dropped = true; Ok(()) diff --git a/src/encoder/writer.rs b/src/encoder/writer.rs index 46ead93..f45038b 100644 --- a/src/encoder/writer.rs +++ b/src/encoder/writer.rs @@ -69,6 +69,10 @@ impl TiffWriter { self.offset } + pub(crate) fn previous_ifd_pointer(&self) -> u64 { + self.offset() - core::mem::size_of::() as u64 + } + pub fn last_written(&self) -> u64 { self.byte_count }