Merge pull request #283 from image-rs/write-directory

Write extra directories
This commit is contained in:
Aurelia Molzer authored and GitHub committed 2025-07-14 00:01:39 +02:00
commit cc75d051af
2 files changed
+146 -21

No files matched your search

+90 -21
View File
@@ -11,7 +11,7 @@ use std::{
use crate::{ use crate::{
decoder::ifd::Entry, decoder::ifd::Entry,
error::{TiffResult, UsageError}, error::{TiffResult, UsageError},
tags::{CompressionMethod, ResolutionUnit, SampleFormat, Tag, Type}, tags::{CompressionMethod, IfdPointer, ResolutionUnit, SampleFormat, Tag, Type},
Directory, TiffError, TiffFormatError, Directory, TiffError, TiffFormatError,
}; };
@@ -170,10 +170,29 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
} }
/// Create a [`DirectoryEncoder`] to encode an ifd directory. /// 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<DirectoryEncoder<'_, W, K>> { pub fn new_directory(&mut self) -> TiffResult<DirectoryEncoder<'_, W, K>> {
Self::chain_directory(&mut self.writer, &mut self.last_ifd_chain) 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<DirectoryEncoder<'_, W, K>> {
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<DirectoryEncoder<'_, W, K>> {
Self::unchained_directory(&mut self.writer)
}
/// Create an [`ImageEncoder`] to encode an image one slice at a time. /// Create an [`ImageEncoder`] to encode an image one slice at a time.
pub fn new_image<C: ColorType>( pub fn new_image<C: ColorType>(
&mut self, &mut self,
@@ -207,6 +226,10 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
let last_ifd = *last_ifd_chain; let last_ifd = *last_ifd_chain;
DirectoryEncoder::new(writer, Some(last_ifd), Some(last_ifd_chain)) DirectoryEncoder::new(writer, Some(last_ifd), Some(last_ifd_chain))
} }
fn unchained_directory(writer: &mut TiffWriter<W>) -> TiffResult<DirectoryEncoder<'_, W, K>> {
DirectoryEncoder::new(writer, None, None)
}
} }
/// Low level interface to encode ifd directories. /// Low level interface to encode ifd directories.
@@ -225,6 +248,24 @@ pub struct DirectoryEncoder<'a, W: 'a + Write + Seek, K: TiffKind> {
dropped: bool, dropped: bool,
} }
/// The offset of an encoded directory in the file.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct DirectoryOffset<K: TiffKind> {
/// 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<K>,
}
impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { 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 /// Construct a directory writer appending to data, assuming the writer is currently positioned
/// immediately after a previously written IFD to which to append. /// 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(()) 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<T: TiffValue>(&mut self, value: T) -> TiffResult<u64> {
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<K>) {
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<DirectoryOffset<K>> {
self.finish_internal()
}
fn write_directory(&mut self) -> TiffResult<u64> { fn write_directory(&mut self) -> TiffResult<u64> {
// Start by turning all buffered unwritten values into entries. // Start by turning all buffered unwritten values into entries.
let offset = self.writer.offset(); 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<T: TiffValue>(&mut self, value: T) -> TiffResult<u64> {
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. /// Provides the number of bytes written by the underlying TiffWriter during the last call.
fn last_written(&self) -> u64 { fn last_written(&self) -> u64 {
self.writer.last_written() self.writer.last_written()
} }
fn finish_internal(&mut self) -> TiffResult<()> { fn finish_internal(&mut self) -> TiffResult<DirectoryOffset<K>> {
let ifd_pointer = self.write_directory()?; let ifd_pointer = self.write_directory()?;
let offset = K::convert_offset(ifd_pointer)?;
if let Some(prior) = self.chained_ifd_pos { if let Some(prior) = self.chained_ifd_pos {
let curr_pos = self.writer.offset(); let curr_pos = self.writer.offset();
self.writer.goto_offset(prior.get())?; 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)?; K::write_offset(self.writer, ifd_pointer)?;
self.writer.goto_offset(curr_pos)?; 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)?; K::write_offset(self.writer, 0)?;
if let Some(prior) = self.write_chain.take() { let ifd_chain = NonZeroU64::new(self.writer.previous_ifd_pointer::<K>())
*prior = NonZeroU64::new(self.writer.previous_ifd_pointer::<K>())
.expect("IFD chain field is at a non-zero offset"); .expect("IFD chain field is at a non-zero offset");
if let Some(prior) = self.write_chain.take() {
*prior = ifd_chain;
} }
self.dropped = true; self.dropped = true;
Ok(()) Ok(DirectoryOffset {
} pointer: IfdPointer(ifd_pointer),
offset,
/// Write out the ifd directory. ifd_chain,
pub fn finish(mut self) -> TiffResult<()> { kind: PhantomData,
self.finish_internal() })
} }
} }
@@ -643,7 +711,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
Ok(()) Ok(())
} }
fn finish_internal(&mut self) -> TiffResult<()> { fn finish_internal(&mut self) -> TiffResult<DirectoryOffset<K>> {
self.encoder self.encoder
.write_tag(Tag::StripOffsets, K::convert_slice(&self.strip_offsets))?; .write_tag(Tag::StripOffsets, K::convert_slice(&self.strip_offsets))?;
self.encoder.write_tag( 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. /// Write out image and ifd directory.
pub fn finish(mut self) -> TiffResult<()> { pub fn finish(mut self) -> TiffResult<()> {
self.finish_internal() self.finish_internal()?;
Ok(())
} }
} }
+56
View File
@@ -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::<colortype::Gray8>(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::<colortype::Gray8>(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);
}
}