From f6e2aeffe57a3ad7592e3eeaf86d890c5229a4c1 Mon Sep 17 00:00:00 2001 From: "A. Molzer" <5550310+197g@users.noreply.github.com> Date: Sun, 13 Jul 2025 03:06:40 +0200 Subject: [PATCH] Encode values immediately This currently avoids buffering the collection, but not buffering them individually where we still format it into a byte vector. The main drive here is making the DirectoryEncoder less dependent on the values. That is, it should be concerned with the Entry representation of values only. A few decoders expect all values to strictly follow the image directory and the data layout of such files should be controllable by the user. This could be achieved by encoding dummy entries which are later corrected once the actual item is known by seeking back and writing the directory anew. By no longer assuming values to be available on finishing a directory we also prepare an interface that encodes an existing directory. --- src/encoder/mod.rs | 24 +++++++++++------------- tests/encode_images.rs | 4 ++-- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index 735b0c9..a6661c3 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -2,7 +2,6 @@ pub use tiff_value::*; use std::{ cmp, - collections::BTreeMap, io::{self, Seek, Write}, marker::PhantomData, mem, @@ -220,9 +219,9 @@ pub struct DirectoryEncoder<'a, W: 'a + Write + Seek, K: TiffKind> { chained_ifd_pos: Option, /// An output to write the `next` field offset on completion. write_chain: Option<&'a mut NonZeroU64>, + kind: PhantomData, // We use BTreeMap to make sure tags are written in correct order directory: Directory, - ifd: BTreeMap>, dropped: bool, } @@ -235,43 +234,42 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { chain_into: Option<&'a mut NonZeroU64>, ) -> TiffResult { writer.pad_word_boundary()?; // TODO: Do we need to adjust this for BigTiff? - Ok(DirectoryEncoder { + Ok(Self { writer, chained_ifd_pos, write_chain: chain_into, + kind: PhantomData, directory: Directory::empty(), - ifd: BTreeMap::new(), dropped: false, }) } /// Write a single ifd tag. pub fn write_tag(&mut self, tag: Tag, value: T) -> TiffResult<()> { + // Encodes the value if necessary. In the current bytes all integers are taken as native + // endian and thus transparent but this keeps the interface generic. let mut bytes = Vec::with_capacity(value.bytes()); { let mut writer = TiffWriter::new(&mut bytes); value.write(&mut writer)?; } - self.ifd.insert( - tag.to_u16(), - DirectoryEntry { + let entry = Self::write_value( + &mut self.writer, + &DirectoryEntry { data_type: ::FIELD_TYPE, count: value.count().try_into()?, data: bytes, }, - ); + )?; + + self.directory.extend([(tag, entry)]); Ok(()) } fn write_directory(&mut self) -> TiffResult { // Start by turning all buffered unwritten values into entries. - for (&tag, value) in &self.ifd { - let entry = Self::write_value(&mut self.writer, value)?; - self.directory.extend([(Tag::Unknown(tag), entry)]); - } - let offset = self.writer.offset(); K::write_entry_count(self.writer, self.directory.len())?; diff --git a/tests/encode_images.rs b/tests/encode_images.rs index 2f17d7d..fb71ca5 100644 --- a/tests/encode_images.rs +++ b/tests/encode_images.rs @@ -57,7 +57,7 @@ fn encode_decode() { ), (Tag::Compression, ifd::Value::Short(1)), (Tag::PhotometricInterpretation, ifd::Value::Short(2)), - (Tag::StripOffsets, ifd::Value::Unsigned(8)), + (Tag::StripOffsets, ifd::Value::Unsigned(47)), (Tag::SamplesPerPixel, ifd::Value::Short(3)), (Tag::RowsPerStrip, ifd::Value::Unsigned(3334)), (Tag::StripByteCounts, ifd::Value::Unsigned(30000)), @@ -137,7 +137,7 @@ fn encode_decode_big() { ), (Tag::Compression, ifd::Value::Short(1)), (Tag::PhotometricInterpretation, ifd::Value::Short(2)), - (Tag::StripOffsets, ifd::Value::UnsignedBig(16)), + (Tag::StripOffsets, ifd::Value::UnsignedBig(27)), (Tag::SamplesPerPixel, ifd::Value::Short(3)), (Tag::RowsPerStrip, ifd::Value::Unsigned(3334)), (Tag::StripByteCounts, ifd::Value::UnsignedBig(30000)),