diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index 93256d1..e67d76b 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -12,8 +12,8 @@ use crate::{ decoder::ifd::Entry, error::{TiffResult, UsageError}, tags::{ - CompressionMethod, ExtraSamples, IfdPointer, PhotometricInterpretation, ResolutionUnit, - SampleFormat, Tag, Type, + ByteOrder, CompressionMethod, ExtraSamples, IfdPointer, PhotometricInterpretation, + ResolutionUnit, SampleFormat, Tag, Type, ValueBuffer, }, Directory, TiffError, TiffFormatError, }; @@ -291,6 +291,21 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { }) } + /// Write a tag-value pair with prepared byte data. + /// + /// Note that the library will _not_ attempt to verify that the data type or the count of the + /// buffered value is permissible for the given tag. The data will be associated with the tag + /// exactly as-is. + /// + /// An error is returned if the byte order of the presented value does not match the byte order + /// of the underlying file (i.e. the [native byte order](`crate::tags::ByteOrder::native`)). + pub fn write_tag_value(&mut self, tag: Tag, value: &ValueBuffer) -> TiffResult<()> { + let entry = self.write_entry_buf(value)?; + self.directory.extend([(tag, entry)]); + + Ok(()) + } + /// 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 @@ -301,12 +316,12 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { value.write(&mut writer)?; } - let entry = Self::write_value( + let entry = Self::write_entry_inner( self.writer, - &DirectoryEntry { + DirectoryEntry { data_type: ::FIELD_TYPE, count: value.count().try_into()?, - data: bytes, + data: bytes.into(), }, )?; @@ -317,13 +332,70 @@ 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. + /// This could be used to write tiff strips. All of the data will be written into the file as a + /// slice of bytes. It can not be used as an entry in a directory directly, where very short + /// values are instead represented in the offset field directly. Use [`Self::write_entry`] + /// instead. pub fn write_data(&mut self, value: T) -> TiffResult { let offset = self.writer.offset(); value.write(self.writer)?; Ok(offset) } + /// Write some data directly to the tiff file, the offset of the data is returned. + /// + /// All of the data will be written into the file as a slice of bytes. It can not be used as an + /// entry in a directory directly, where very short values are instead represented in the + /// offset field directly. Use [`Self::write_entry_buf`] instead. + /// + /// An error is returned if the byte order of the presented value does not match the byte order + /// of the underlying file (i.e. the [native byte order](`crate::tags::ByteOrder::native`)). + pub fn write_data_buf(&mut self, value: &ValueBuffer) -> TiffResult { + self.check_value_byteorder(value)?; + let offset = self.writer.offset(); + self.writer.write_bytes(value.as_bytes())?; + Ok(offset) + } + + /// Write a directory value into the file. + /// + /// Returns an [`Entry`]. If the value is short enough it will *not* be written in the file but + /// stored in the entry's offset field. + pub fn write_entry(&mut self, value: T) -> TiffResult { + Self::write_entry_inner( + self.writer, + DirectoryEntry { + data_type: T::FIELD_TYPE, + count: K::convert_offset(value.count() as u64)?, + // FIXME: not optimal we always allocate here. But the only other method we have + // available is `T::write(&mut TiffWriter)` and we must pass that into + // `write_entry_inner` but then have a combinatorial explosion of instantiations + // which is absurd. + data: value.data(), + }, + ) + } + + /// Write a directory value into the file. + /// + /// An error is returned if the byte order of the presented value does not match the byte order + /// of the underlying file (i.e. the [native byte order](`crate::tags::ByteOrder::native`)). + /// + /// Returns an [`Entry`]. If the value is short enough it will *not* be written in the file but + /// stored in the entry's offset field. + pub fn write_entry_buf(&mut self, value: &ValueBuffer) -> TiffResult { + self.check_value_byteorder(value)?; + + Self::write_entry_inner( + self.writer, + DirectoryEntry { + data_type: value.data_type(), + count: K::convert_offset(value.count())?, + data: value.as_bytes().into(), + }, + ) + } + /// Insert previously written key-value entries. /// /// It is the caller's responsibility to ensure that the data referred to by the entries is @@ -377,17 +449,21 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { Ok(offset) } - fn write_value( + // Does it really make sense that this would be a (potentially) compressible write? For actual + // directory values their size must match the data written so there's going to be a silent + // problem if that actually occurs. + fn write_entry_inner( writer: &mut TiffWriter, - value: &DirectoryEntry, + value: DirectoryEntry, ) -> TiffResult { - let &DirectoryEntry { + let DirectoryEntry { data: ref bytes, ref count, data_type, } = value; let in_entry_bytes = mem::size_of::(); + let mut offset_bytes = [0; 8]; if bytes.len() > in_entry_bytes { @@ -406,6 +482,7 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { // and some oversight, we can not clone the `count: K::OffsetType` and thus not convert it. // Instead, write it to a buffer... let mut count_bytes = [0; 8]; + debug_assert!(in_entry_bytes == 4 || in_entry_bytes == 8); // Nominally Cow but we only expect `Cow::Borrowed`. count_bytes[..count.bytes()].copy_from_slice(&count.data()); @@ -419,6 +496,15 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { }) } + fn check_value_byteorder(&self, value: &ValueBuffer) -> TiffResult<()> { + // FIXME: we should enable writing files with any chosen byte order. + if value.byte_order() != ByteOrder::native() { + Err(TiffError::UsageError(UsageError::ByteOrderMismatch)) + } else { + Ok(()) + } + } + /// Provides the number of bytes written by the underlying TiffWriter during the last call. fn last_written(&self) -> u64 { self.writer.last_written() @@ -824,10 +910,10 @@ impl<'a, W: Write + Seek, C: ColorType, K: TiffKind> Drop for ImageEncoder<'a, W } } -struct DirectoryEntry { +struct DirectoryEntry<'data, S> { data_type: Type, count: S, - data: Vec, + data: std::borrow::Cow<'data, [u8]>, } /// Trait to abstract over Tiff/BigTiff differences. diff --git a/src/encoder/tiff_value.rs b/src/encoder/tiff_value.rs index b40d0f4..38eeac3 100644 --- a/src/encoder/tiff_value.rs +++ b/src/encoder/tiff_value.rs @@ -16,7 +16,12 @@ use super::writer::TiffWriter; pub trait TiffValue { const BYTE_LEN: u8; const FIELD_TYPE: Type; + + // FIXME: If we want this to mean 'count' in the sense of `Entry` it should return `u32` or + // `u64`, probably the latter with `convert_offset` taking care of the check against `u32::MAX` + // for classic tiff. fn count(&self) -> usize; + fn bytes(&self) -> usize { self.count() * usize::from(Self::BYTE_LEN) } diff --git a/src/error.rs b/src/error.rs index eb4fc86..94dc468 100644 --- a/src/error.rs +++ b/src/error.rs @@ -208,6 +208,9 @@ quick_error! { ReconfiguredAfterImageWrite { display("attempted to reconfigure the encoder after image writing has started") } + ByteOrderMismatch { + display("attempted to use data with a byte order that does not match the required one") + } } } diff --git a/src/tags.rs b/src/tags.rs index 8dc8716..e78dde5 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -1,3 +1,5 @@ +use crate::encoder::TiffValue; + macro_rules! tags { { // Permit arbitrary meta items, which include documentation. @@ -336,6 +338,71 @@ pub enum ExtraSamples(u16) { } } +/// A value represented as in-memory bytes with flexible byteorder. +pub struct ValueBuffer { + /// The raw bytes of the value. + bytes: Vec, + + /// The type of the value. + ty: Type, + + /// The byte order of the value. + byte_order: ByteOrder, +} + +impl ValueBuffer { + /// A value with a count of zero. + /// + /// The byte order is set to the native byte order of the platform. + pub fn empty(ty: Type) -> Self { + ValueBuffer { + bytes: vec![], + ty, + byte_order: ByteOrder::native(), + } + } + + /// Create a value with native byte order from in-memory data. + pub fn from_value(value: &T) -> Self { + ValueBuffer { + bytes: value.data().into_owned(), + ty: ::FIELD_TYPE, + byte_order: ByteOrder::native(), + } + } + + pub fn byte_order(&self) -> ByteOrder { + self.byte_order + } + + pub fn data_type(&self) -> Type { + self.ty + } + + /// The count of items in the value. + pub fn count(&self) -> u64 { + (self.bytes.len() / usize::from(self.ty.byte_len())) as u64 + } + + /// View the underlying raw bytes of this value. + pub fn as_bytes(&self) -> &[u8] { + &self.bytes + } + + /// View the underlying mutable raw bytes of this value. + pub fn as_bytes_mut(&mut self) -> &mut [u8] { + &mut self.bytes + } + + /// Change the byte order of the value representation. + pub fn set_byte_order(&mut self, byte_order: ByteOrder) { + self.byte_order + .convert(self.ty, self.bytes.as_mut_slice(), byte_order); + + self.byte_order = byte_order; + } +} + /// Byte order of the TIFF file. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ByteOrder {