diff --git a/README.md b/README.md index a5fc0a5..3612654 100644 --- a/README.md +++ b/README.md @@ -16,25 +16,25 @@ TIFF decoding and encoding library in pure Rust ### Formats This table lists photometric interpretations and sample formats which are supported for encoding and decoding. The entries are `ColorType` variants for which sample bit depths are supported. Only samples where all bit depths are equal are currently supported. For example, `RGB(8)` means that the bit depth [8, 8, 8] is supported and will be interpreted as an 8 bit per channel RGB color type. -| `PhotometricInterpretation` | UINT Format | IEEEFP Format | -|-|-|-| -| `WhiteIsZero` | Gray(8\|16\|32\|64) | Gray(32\|64) | -| `BlackIsZero` | Gray(8\|16\|32\|64) | Gray(32\|64) | -| `RGB` | RGB(8\|16\|32\|64), RGBA(8\|16\|32\|64) | RGB(32\|64), RGBA(32\|64) | -| `RGBPalette` | | | -| `Mask` | | | -| `CMYK` | CMYK(8\|16\|32\|64) | CMYK(32\|64) | -| `YCbCr` | | | -| `CIELab` | | | +| `PhotometricInterpretation` | UINT Format | IEEEFP Format | +| --------------------------- | --------------------------------------- | ------------------------- | +| `WhiteIsZero` | Gray(8\|16\|32\|64) | Gray(32\|64) | +| `BlackIsZero` | Gray(8\|16\|32\|64) | Gray(32\|64) | +| `RGB` | RGB(8\|16\|32\|64), RGBA(8\|16\|32\|64) | RGB(32\|64), RGBA(32\|64) | +| `RGBPalette` | | | +| `Mask` | | | +| `CMYK` | CMYK(8\|16\|32\|64) | CMYK(32\|64) | +| `YCbCr` | | | +| `CIELab` | | | ### Compressions -| | Decoding | Encoding | -|-|-|-| -| None | ✓ | ✓ | -| LZW | ✓ | | -| Deflate | ✓ | | -| PackBits | ✓ | | +| | Decoding | Encoding | +| -------- | -------- | -------- | +| None | ✓ | ✓ | +| LZW | ✓ | ✓ | +| Deflate | ✓ | ✓ | +| PackBits | ✓ | | ## Not yet supported diff --git a/benches/lzw.rs b/benches/lzw.rs index 64782d9..47f513b 100644 --- a/benches/lzw.rs +++ b/benches/lzw.rs @@ -33,7 +33,7 @@ fn main() { def.data, |b, input| b.iter(|| read_image(input)), ); - }; + } let mut c = Criterion::default().configure_from_args(); let mut group = c.benchmark_group("tiff-lzw"); diff --git a/src/encoder/compression/deflate.rs b/src/encoder/compression/deflate.rs new file mode 100644 index 0000000..de6532f --- /dev/null +++ b/src/encoder/compression/deflate.rs @@ -0,0 +1,108 @@ +use std::{ + convert::TryInto, + io::{Seek, Write}, +}; + +use flate2::{write::ZlibEncoder, Compression as FlateCompression}; + +use crate::{ + encoder::{ + colortype::ColorType, compression::Compression, DirectoryEncoder, TiffKind, TiffValue, + }, + tags::CompressionMethod, + TiffResult, +}; + +/// The Deflate algorithm used to compress image data in TIFF files. +#[derive(Debug, Clone)] +pub struct Deflate { + level: FlateCompression, + buffer: Vec, +} + +/// The level of compression used by the Deflate algorithm. +/// It allows trading compression ratio for compression speed. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[non_exhaustive] +pub enum DeflateLevel { + /// The fastest possible compression mode. + Fast = 1, + /// The conserative choice between speed and ratio. + Balanced = 6, + /// The best compression available with Deflate. + Best = 9, +} + +impl Default for DeflateLevel { + fn default() -> Self { + DeflateLevel::Balanced + } +} + +impl Deflate { + /// Lets be greedy and allocate more bytes in advance. We will likely encode longer image strips. + const DEFAULT_BUFFER_SIZE: usize = 256; + + /// Create a new deflate compr+essor with a specific level of compression. + pub fn with_level(level: DeflateLevel) -> Self { + Self { + buffer: Vec::with_capacity(Self::DEFAULT_BUFFER_SIZE), + level: FlateCompression::new(level as u32), + } + } +} + +impl Default for Deflate { + fn default() -> Self { + Self::with_level(DeflateLevel::default()) + } +} + +impl Compression for Deflate { + const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::Deflate; + + fn write_to<'a, T: ColorType, K: TiffKind, W: 'a + Write + Seek>( + &mut self, + encoder: &mut DirectoryEncoder<'a, W, K>, + value: &[T::Inner], + ) -> TiffResult<(K::OffsetType, K::OffsetType)> + where + [T::Inner]: TiffValue, + { + let data = value.data(); + { + let mut encoder = ZlibEncoder::new(&mut self.buffer, self.level); + encoder.write_all(&data)?; + encoder.finish()?; + } + + let compressed_byte_count = self.buffer.len().try_into()?; + let offset = encoder + .write_data(self.buffer.as_slice()) + .and_then(K::convert_offset)?; + + // Clear the buffer for the next compression. + self.buffer.clear(); + + Ok((offset, compressed_byte_count)) + } +} + +#[cfg(test)] +mod tests { + use crate::encoder::compression::tests::{compress, TEST_DATA}; + + #[test] + fn test_deflate() { + const EXPECTED_COMPRESSED_DATA: [u8; 64] = [ + 0x78, 0x9C, 0x15, 0xC7, 0xD1, 0x0D, 0x80, 0x20, 0x0C, 0x04, 0xD0, 0x55, 0x6E, 0x02, + 0xA7, 0x71, 0x81, 0xA6, 0x41, 0xDA, 0x28, 0xD4, 0xF4, 0xD0, 0xF9, 0x81, 0xE4, 0xFD, + 0xBC, 0xD3, 0x9C, 0x58, 0x04, 0x1C, 0xE9, 0xBD, 0xE2, 0x8A, 0x84, 0x5A, 0xD1, 0x7B, + 0xE7, 0x97, 0xF4, 0xF8, 0x08, 0x8D, 0xF6, 0x66, 0x21, 0x3D, 0x3A, 0xE4, 0xA9, 0x91, + 0x3E, 0xAC, 0xF1, 0x98, 0xB9, 0x70, 0x17, 0x13, + ]; + + let compressed_data = compress(TEST_DATA, super::Deflate::default()); + assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA); + } +} diff --git a/src/encoder/compression/lzw.rs b/src/encoder/compression/lzw.rs new file mode 100644 index 0000000..45806c2 --- /dev/null +++ b/src/encoder/compression/lzw.rs @@ -0,0 +1,79 @@ +use std::{ + convert::TryInto, + io::{Seek, Write}, +}; + +use weezl::encode::Encoder as LZWEncoder; + +use crate::{ + encoder::{ + colortype::ColorType, compression::Compression, DirectoryEncoder, TiffKind, TiffValue, + }, + tags::CompressionMethod, + TiffResult, +}; + +/// The LZW algorithm used to compress image data in TIFF files. +#[derive(Debug, Clone)] +pub struct Lzw { + buffer: Vec, +} + +impl Default for Lzw { + fn default() -> Self { + // Lets be greedy and allocate more bytes in advance. We will likely encode longer image strips. + const DEFAULT_BUFFER_SIZE: usize = 256; + Self { + buffer: Vec::with_capacity(DEFAULT_BUFFER_SIZE), + } + } +} + +impl Compression for Lzw { + const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::LZW; + + fn write_to<'a, T: ColorType, K: TiffKind, W: 'a + Write + Seek>( + &mut self, + encoder: &mut DirectoryEncoder<'a, W, K>, + value: &[T::Inner], + ) -> TiffResult<(K::OffsetType, K::OffsetType)> + where + [T::Inner]: TiffValue, + { + let bytes = value.data(); + let compressed_byte_count = { + let mut encoder = LZWEncoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8); + let result = encoder.into_vec(&mut self.buffer).encode_all(&bytes); + result.status.map(|_| result.consumed_out) + }? + .try_into()?; + + let offset = encoder + .write_data(self.buffer.as_slice()) + .and_then(K::convert_offset)?; + + // Clear the buffer for the next compression. + self.buffer.clear(); + + Ok((offset, compressed_byte_count)) + } +} + +#[cfg(test)] +mod tests { + use crate::encoder::compression::tests::{compress, TEST_DATA}; + + #[test] + fn test_lzw() { + const EXPECTED_COMPRESSED_DATA: [u8; 63] = [ + 0x80, 0x15, 0x0D, 0x06, 0x93, 0x98, 0x82, 0x08, 0x20, 0x30, 0x88, 0x0E, 0x67, 0x43, + 0x91, 0xA4, 0xDC, 0x67, 0x10, 0x19, 0x8D, 0xE7, 0x21, 0x01, 0x8C, 0xD0, 0x65, 0x31, + 0x9A, 0xE1, 0xD1, 0x03, 0xB1, 0x86, 0x1A, 0x6F, 0x3A, 0xC1, 0x4C, 0x66, 0xF3, 0x69, + 0xC0, 0xE4, 0x65, 0x39, 0x9C, 0xCD, 0x26, 0xF3, 0x74, 0x20, 0xD8, 0x67, 0x89, 0x9A, + 0x4E, 0x86, 0x83, 0x69, 0xCC, 0x5D, 0x01, + ]; + + let compressed_data = compress(TEST_DATA, super::Lzw::default()); + assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA); + } +} diff --git a/src/encoder/compression/mod.rs b/src/encoder/compression/mod.rs new file mode 100644 index 0000000..2b1aa5e --- /dev/null +++ b/src/encoder/compression/mod.rs @@ -0,0 +1,58 @@ +use std::io::prelude::*; + +use crate::{ + encoder::{ColorType, DirectoryEncoder, TiffKind, TiffValue}, + error::TiffResult, + tags::CompressionMethod, +}; + +mod deflate; +mod lzw; +mod uncompressed; + +pub use self::deflate::{Deflate, DeflateLevel}; +pub use self::lzw::Lzw; +pub use self::uncompressed::Uncompressed; + +/// An algorithm used for compression with associated optional buffers and/or configurations. +pub trait Compression: Default { + /// The corresponding tag to the algorithm. + const COMPRESSION_METHOD: CompressionMethod; + + /// Write the data of a specific color type to the given encoder and return the offset and byte count, respectively. + fn write_to<'a, T: ColorType, K: TiffKind, W: 'a + Write + Seek>( + &mut self, + encoder: &mut DirectoryEncoder<'a, W, K>, + value: &[T::Inner], + ) -> TiffResult<(K::OffsetType, K::OffsetType)> + where + [T::Inner]: TiffValue; +} + +#[cfg(test)] +mod tests { + use crate::encoder::{colortype::Gray8, compression::Compression, TiffEncoder}; + use std::io::Cursor; + + pub const TEST_DATA: &'static [u8] = + b"This is a string for checking various compression algorithms."; + + pub fn compress(data: &[u8], compressor: C) -> Vec { + let mut buffer = Vec::new(); + + // Compress the data + { + let mut encoder = TiffEncoder::new(Cursor::new(&mut buffer)).unwrap(); + let encoder = encoder + .new_image_with_compression::(data.len() as u32, 1, compressor) + .unwrap(); + encoder.write_data(&data).unwrap(); + } + + // Remove the meta data written by the encoder + buffer.drain(..8); + buffer.drain(buffer.len() - 178..); + + buffer + } +} diff --git a/src/encoder/compression/uncompressed.rs b/src/encoder/compression/uncompressed.rs new file mode 100644 index 0000000..17ca285 --- /dev/null +++ b/src/encoder/compression/uncompressed.rs @@ -0,0 +1,44 @@ +use std::{ + convert::TryInto, + io::{Seek, Write}, +}; + +use crate::{ + encoder::{ + colortype::ColorType, compression::Compression, DirectoryEncoder, TiffKind, TiffValue, + }, + tags::CompressionMethod, + TiffResult, +}; + +/// The default algorithm which does not compress at all. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct Uncompressed; + +impl Compression for Uncompressed { + const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::None; + + fn write_to<'a, T: ColorType, K: TiffKind, W: 'a + Write + Seek>( + &mut self, + encoder: &mut DirectoryEncoder<'a, W, K>, + value: &[T::Inner], + ) -> TiffResult<(K::OffsetType, K::OffsetType)> + where + [T::Inner]: TiffValue, + { + let byte_count = value.len().try_into()?; + let offset = encoder.write_data(value).and_then(K::convert_offset)?; + Ok((offset, byte_count)) + } +} + +#[cfg(test)] +mod tests { + use crate::encoder::compression::tests::{compress, TEST_DATA}; + + #[test] + fn test_no_compression() { + let compressed_data = compress(TEST_DATA, super::Uncompressed); + assert_eq!(TEST_DATA, compressed_data); + } +} diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index 2881d46..8521d1e 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -12,14 +12,16 @@ use std::{ use crate::{ error::TiffResult, - tags::{self, ResolutionUnit, Tag}, + tags::{ResolutionUnit, Tag}, }; pub mod colortype; +pub mod compression; mod tiff_value; mod writer; use self::colortype::*; +use self::compression::*; use self::writer::*; /// Encoder for Tiff and BigTiff files. @@ -98,11 +100,22 @@ impl TiffEncoder { &mut self, width: u32, height: u32, - ) -> TiffResult> { + ) -> TiffResult> { let encoder = DirectoryEncoder::new(&mut self.writer)?; ImageEncoder::new(encoder, width, height) } + /// Create an [`ImageEncoder`] to encode an image one slice at a time. + pub fn new_image_with_compression( + &mut self, + width: u32, + height: u32, + compression: D, + ) -> TiffResult> { + let encoder = DirectoryEncoder::new(&mut self.writer)?; + ImageEncoder::with_compression(encoder, width, height, compression) + } + /// Convenience function to write an entire image from memory. pub fn write_image( &mut self, @@ -117,6 +130,23 @@ impl TiffEncoder { let image: ImageEncoder = ImageEncoder::new(encoder, width, height)?; image.write_data(data) } + + /// Convenience function to write an entire image from memory with a given compression. + pub fn write_image_with_compression( + &mut self, + width: u32, + height: u32, + compression: D, + data: &[C::Inner], + ) -> TiffResult<()> + where + [C::Inner]: TiffValue, + { + let encoder = DirectoryEncoder::new(&mut self.writer)?; + let image: ImageEncoder = + ImageEncoder::with_compression(encoder, width, height, compression)?; + image.write_data(data) + } } /// Low level interface to encode ifd directories. @@ -277,7 +307,13 @@ impl<'a, W: Write + Seek, K: TiffKind> Drop for DirectoryEncoder<'a, W, K> { /// # } /// ``` /// You can also call write_data function wich will encode by strip and finish -pub struct ImageEncoder<'a, W: 'a + Write + Seek, C: ColorType, K: TiffKind> { +pub struct ImageEncoder< + 'a, + W: 'a + Write + Seek, + C: ColorType, + K: TiffKind, + D: Compression = Uncompressed, +> { encoder: DirectoryEncoder<'a, W, K>, strip_idx: u64, strip_count: u64, @@ -288,11 +324,26 @@ pub struct ImageEncoder<'a, W: 'a + Write + Seek, C: ColorType, K: TiffKind> { strip_offsets: Vec, strip_byte_count: Vec, dropped: bool, + compression: D, _phantom: ::std::marker::PhantomData, } -impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T, K> { - fn new(mut encoder: DirectoryEncoder<'a, W, K>, width: u32, height: u32) -> TiffResult { +impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression> + ImageEncoder<'a, W, T, K, D> +{ + fn new(encoder: DirectoryEncoder<'a, W, K>, width: u32, height: u32) -> TiffResult + where + D: Default, + { + Self::with_compression(encoder, width, height, D::default()) + } + + fn with_compression( + mut encoder: DirectoryEncoder<'a, W, K>, + width: u32, + height: u32, + compression: D, + ) -> TiffResult { let row_samples = u64::from(width) * u64::try_from(::BITS_PER_SAMPLE.len())?; let row_bytes = row_samples * u64::from(::BYTE_LEN); @@ -304,7 +355,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T, encoder.write_tag(Tag::ImageWidth, width)?; encoder.write_tag(Tag::ImageLength, height)?; - encoder.write_tag(Tag::Compression, tags::CompressionMethod::None.to_u16())?; + encoder.write_tag(Tag::Compression, D::COMPRESSION_METHOD.to_u16())?; encoder.write_tag(Tag::BitsPerSample, ::BITS_PER_SAMPLE)?; let sample_format: Vec<_> = ::SAMPLE_FORMAT.iter().map(|s| s.to_u16()).collect(); @@ -332,6 +383,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T, strip_offsets: Vec::new(), strip_byte_count: Vec::new(), dropped: false, + compression, _phantom: ::std::marker::PhantomData, }) } @@ -354,7 +406,6 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T, where [T::Inner]: TiffValue, { - // TODO: Compression let samples = self.next_strip_sample_count(); if u64::try_from(value.len())? != samples { return Err(io::Error::new( @@ -364,9 +415,12 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T, .into()); } - let offset = self.encoder.write_data(value)?; - self.strip_offsets.push(K::convert_offset(offset)?); - self.strip_byte_count.push(value.bytes().try_into()?); + // Write the (possible compressed) data to the encoder. + let (offset, byte_count) = self + .compression + .write_to::(&mut self.encoder, value)?; + self.strip_offsets.push(offset); + self.strip_byte_count.push(byte_count); self.strip_idx += 1; Ok(()) @@ -475,7 +529,9 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T, } } -impl<'a, W: Write + Seek, C: ColorType, K: TiffKind> Drop for ImageEncoder<'a, W, C, K> { +impl<'a, W: Write + Seek, C: ColorType, K: TiffKind, D: Compression> Drop + for ImageEncoder<'a, W, C, K, D> +{ fn drop(&mut self) { if !self.dropped { let _ = self.finish_internal(); diff --git a/src/encoder/tiff_value.rs b/src/encoder/tiff_value.rs index 1009acb..8813d21 100644 --- a/src/encoder/tiff_value.rs +++ b/src/encoder/tiff_value.rs @@ -1,4 +1,4 @@ -use std::io::Write; +use std::{borrow::Cow, io::Write}; use crate::{bytecast, tags::Type, TiffError, TiffFormatError, TiffResult}; @@ -12,7 +12,18 @@ pub trait TiffValue { fn bytes(&self) -> usize { self.count() * usize::from(Self::BYTE_LEN) } - fn write(&self, writer: &mut TiffWriter) -> TiffResult<()>; + + /// Access this value as an contiguous sequence of bytes. + /// If their is no trivial representation, allocate it on the heap. + fn data(&self) -> Cow<[u8]>; + + /// Write this value to a TiffWriter. + /// While the default implementation will work in all cases, it may require unnecessary allocations. + /// The written bytes of any custom implementation MUST be the same as yielded by `self.data()`. + fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { + writer.write_bytes(&self.data())?; + Ok(()) + } } impl TiffValue for [u8] { @@ -23,9 +34,8 @@ impl TiffValue for [u8] { self.len() } - fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { - writer.write_bytes(self)?; - Ok(()) + fn data(&self) -> Cow<[u8]> { + Cow::Borrowed(self) } } @@ -37,10 +47,8 @@ impl TiffValue for [i8] { self.len() } - fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { - let slice = bytecast::i8_as_ne_bytes(self); - writer.write_bytes(slice)?; - Ok(()) + fn data(&self) -> Cow<[u8]> { + Cow::Borrowed(bytecast::i8_as_ne_bytes(self)) } } @@ -52,10 +60,8 @@ impl TiffValue for [u16] { self.len() } - fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { - let slice = bytecast::u16_as_ne_bytes(self); - writer.write_bytes(slice)?; - Ok(()) + fn data(&self) -> Cow<[u8]> { + Cow::Borrowed(bytecast::u16_as_ne_bytes(self)) } } @@ -67,10 +73,8 @@ impl TiffValue for [i16] { self.len() } - fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { - let slice = bytecast::i16_as_ne_bytes(self); - writer.write_bytes(slice)?; - Ok(()) + fn data(&self) -> Cow<[u8]> { + Cow::Borrowed(bytecast::i16_as_ne_bytes(self)) } } @@ -82,10 +86,8 @@ impl TiffValue for [u32] { self.len() } - fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { - let slice = bytecast::u32_as_ne_bytes(self); - writer.write_bytes(slice)?; - Ok(()) + fn data(&self) -> Cow<[u8]> { + Cow::Borrowed(bytecast::u32_as_ne_bytes(self)) } } @@ -97,10 +99,8 @@ impl TiffValue for [i32] { self.len() } - fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { - let slice = bytecast::i32_as_ne_bytes(self); - writer.write_bytes(slice)?; - Ok(()) + fn data(&self) -> Cow<[u8]> { + Cow::Borrowed(bytecast::i32_as_ne_bytes(self)) } } @@ -112,10 +112,8 @@ impl TiffValue for [u64] { self.len() } - fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { - let slice = bytecast::u64_as_ne_bytes(self); - writer.write_bytes(slice)?; - Ok(()) + fn data(&self) -> Cow<[u8]> { + Cow::Borrowed(bytecast::u64_as_ne_bytes(self)) } } @@ -127,10 +125,8 @@ impl TiffValue for [i64] { self.len() } - fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { - let slice = bytecast::i64_as_ne_bytes(self); - writer.write_bytes(slice)?; - Ok(()) + fn data(&self) -> Cow<[u8]> { + Cow::Borrowed(bytecast::i64_as_ne_bytes(self)) } } @@ -142,11 +138,9 @@ impl TiffValue for [f32] { self.len() } - fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { - // We write using nativeedian so this sould be safe - let slice = bytecast::f32_as_ne_bytes(self); - writer.write_bytes(slice)?; - Ok(()) + fn data(&self) -> Cow<[u8]> { + // We write using native endian so this should be safe + Cow::Borrowed(bytecast::f32_as_ne_bytes(self)) } } @@ -158,75 +152,9 @@ impl TiffValue for [f64] { self.len() } - fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { - // We write using nativeedian so this sould be safe - let slice = bytecast::f64_as_ne_bytes(self); - writer.write_bytes(slice)?; - Ok(()) - } -} - -impl TiffValue for [Ifd] { - const BYTE_LEN: u8 = 4; - const FIELD_TYPE: Type = Type::IFD; - - fn count(&self) -> usize { - self.len() - } - - fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { - for x in self { - x.write(writer)?; - } - Ok(()) - } -} - -impl TiffValue for [Ifd8] { - const BYTE_LEN: u8 = 8; - const FIELD_TYPE: Type = Type::IFD8; - - fn count(&self) -> usize { - self.len() - } - - fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { - for x in self { - x.write(writer)?; - } - Ok(()) - } -} - -impl TiffValue for [Rational] { - const BYTE_LEN: u8 = 8; - const FIELD_TYPE: Type = Type::RATIONAL; - - fn count(&self) -> usize { - self.len() - } - - fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { - for x in self { - x.write(writer)?; - } - Ok(()) - } -} - -impl TiffValue for [SRational] { - const BYTE_LEN: u8 = 8; - const FIELD_TYPE: Type = Type::SRATIONAL; - - fn count(&self) -> usize { - self.len() - } - - fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { - for x in self { - x.write(writer)?; - } - Ok(()) + fn data(&self) -> Cow<[u8]> { + // We write using native endian so this should be safe + Cow::Borrowed(bytecast::f64_as_ne_bytes(self)) } } @@ -242,6 +170,10 @@ impl TiffValue for u8 { writer.write_u8(*self)?; Ok(()) } + + fn data(&self) -> Cow<[u8]> { + Cow::Owned(vec![*self]) + } } impl TiffValue for i8 { @@ -256,6 +188,10 @@ impl TiffValue for i8 { writer.write_i8(*self)?; Ok(()) } + + fn data(&self) -> Cow<[u8]> { + Cow::Owned(self.to_ne_bytes().to_vec()) + } } impl TiffValue for u16 { @@ -270,6 +206,10 @@ impl TiffValue for u16 { writer.write_u16(*self)?; Ok(()) } + + fn data(&self) -> Cow<[u8]> { + Cow::Owned(self.to_ne_bytes().to_vec()) + } } impl TiffValue for i16 { @@ -284,6 +224,10 @@ impl TiffValue for i16 { writer.write_i16(*self)?; Ok(()) } + + fn data(&self) -> Cow<[u8]> { + Cow::Owned(self.to_ne_bytes().to_vec()) + } } impl TiffValue for u32 { @@ -298,6 +242,10 @@ impl TiffValue for u32 { writer.write_u32(*self)?; Ok(()) } + + fn data(&self) -> Cow<[u8]> { + Cow::Owned(self.to_ne_bytes().to_vec()) + } } impl TiffValue for i32 { @@ -312,6 +260,10 @@ impl TiffValue for i32 { writer.write_i32(*self)?; Ok(()) } + + fn data(&self) -> Cow<[u8]> { + Cow::Owned(self.to_ne_bytes().to_vec()) + } } impl TiffValue for u64 { @@ -326,6 +278,10 @@ impl TiffValue for u64 { writer.write_u64(*self)?; Ok(()) } + + fn data(&self) -> Cow<[u8]> { + Cow::Owned(self.to_ne_bytes().to_vec()) + } } impl TiffValue for i64 { @@ -340,6 +296,10 @@ impl TiffValue for i64 { writer.write_i64(*self)?; Ok(()) } + + fn data(&self) -> Cow<[u8]> { + Cow::Owned(self.to_ne_bytes().to_vec()) + } } impl TiffValue for f32 { @@ -354,6 +314,10 @@ impl TiffValue for f32 { writer.write_f32(*self)?; Ok(()) } + + fn data(&self) -> Cow<[u8]> { + Cow::Owned(self.to_ne_bytes().to_vec()) + } } impl TiffValue for f64 { @@ -368,6 +332,10 @@ impl TiffValue for f64 { writer.write_f64(*self)?; Ok(()) } + + fn data(&self) -> Cow<[u8]> { + Cow::Owned(self.to_ne_bytes().to_vec()) + } } impl TiffValue for Ifd { @@ -382,6 +350,13 @@ impl TiffValue for Ifd { writer.write_u32(self.0)?; Ok(()) } + + fn data(&self) -> Cow<[u8]> { + Cow::Owned({ + let dword: [u8; 4] = self.0.to_ne_bytes(); + dword.to_vec() + }) + } } impl TiffValue for Ifd8 { @@ -396,6 +371,13 @@ impl TiffValue for Ifd8 { writer.write_u64(self.0)?; Ok(()) } + + fn data(&self) -> Cow<[u8]> { + Cow::Owned({ + let qword: [u8; 8] = self.0.to_ne_bytes(); + qword.to_vec() + }) + } } impl TiffValue for Rational { @@ -411,6 +393,14 @@ impl TiffValue for Rational { writer.write_u32(self.d)?; Ok(()) } + + fn data(&self) -> Cow<[u8]> { + Cow::Owned({ + let first_dword: [u8; 4] = self.n.to_ne_bytes(); + let second_dword: [u8; 4] = self.d.to_ne_bytes(); + [first_dword, second_dword].concat() + }) + } } impl TiffValue for SRational { @@ -426,6 +416,14 @@ impl TiffValue for SRational { writer.write_i32(self.d)?; Ok(()) } + + fn data(&self) -> Cow<[u8]> { + Cow::Owned({ + let first_dword: [u8; 4] = self.n.to_ne_bytes(); + let second_dword: [u8; 4] = self.d.to_ne_bytes(); + [first_dword, second_dword].concat() + }) + } } impl TiffValue for str { @@ -445,6 +443,17 @@ impl TiffValue for str { Err(TiffError::FormatError(TiffFormatError::InvalidTag)) } } + + fn data(&self) -> Cow<[u8]> { + Cow::Owned({ + if self.is_ascii() && !self.bytes().any(|b| b == 0) { + let bytes: &[u8] = self.as_bytes(); + [bytes, &[0]].concat() + } else { + vec![] + } + }) + } } impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T { @@ -458,8 +467,45 @@ impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T { fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { (*self).write(writer) } + + fn data(&self) -> Cow<[u8]> { + T::data(self) + } } +macro_rules! impl_tiff_value_for_contiguous_sequence { + ($inner_type:ty; $bytes:expr; $field_type:expr) => { + impl $crate::encoder::TiffValue for [$inner_type] { + const BYTE_LEN: u8 = $bytes; + const FIELD_TYPE: Type = $field_type; + + fn count(&self) -> usize { + self.len() + } + + fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { + for x in self { + x.write(writer)?; + } + Ok(()) + } + + fn data(&self) -> Cow<[u8]> { + let mut buf: Vec = Vec::with_capacity(Self::BYTE_LEN as usize * self.len()); + for x in self { + buf.extend_from_slice(&x.data()); + } + Cow::Owned(buf) + } + } + }; +} + +impl_tiff_value_for_contiguous_sequence!(Ifd; 4; Type::IFD); +impl_tiff_value_for_contiguous_sequence!(Ifd8; 8; Type::IFD8); +impl_tiff_value_for_contiguous_sequence!(Rational; 8; Type::RATIONAL); +impl_tiff_value_for_contiguous_sequence!(SRational; 8; Type::SRATIONAL); + /// Type to represent tiff values of type `IFD` #[derive(Clone)] pub struct Ifd(pub u32); diff --git a/src/error.rs b/src/error.rs index ea6819d..0f51891 100644 --- a/src/error.rs +++ b/src/error.rs @@ -10,6 +10,8 @@ use crate::tags::{ }; use crate::ColorType; +use crate::weezl::LzwError; + /// Tiff error kinds. #[derive(Debug)] pub enum TiffError { @@ -31,6 +33,9 @@ pub enum TiffError { /// The image does not support the requested operation UsageError(UsageError), + + /// An error has occurred during data compression + CompressionError, } /// The image is not formatted properly. @@ -228,6 +233,7 @@ impl fmt::Display for TiffError { TiffError::LimitsExceeded => write!(fmt, "The Decoder limits are exceeded"), TiffError::IntSizeError => write!(fmt, "Platform or format size limits exceeded"), TiffError::UsageError(ref e) => write!(fmt, "Usage error: {}", e), + TiffError::CompressionError => write!(fmt, "Compression failed"), } } } @@ -241,6 +247,7 @@ impl Error for TiffError { TiffError::LimitsExceeded => "Decoder limits exceeded", TiffError::IntSizeError => "Platform or format size limits exceeded", TiffError::UsageError(..) => "Invalid usage", + TiffError::CompressionError => "Compression failed", } } @@ -288,5 +295,11 @@ impl From for TiffError { } } +impl From for TiffError { + fn from(_err: LzwError) -> TiffError { + TiffError::CompressionError + } +} + /// Result of an image decoding/encoding process pub type TiffResult = Result; diff --git a/tests/encode_images_with_compression.rs b/tests/encode_images_with_compression.rs new file mode 100644 index 0000000..c1aadc8 --- /dev/null +++ b/tests/encode_images_with_compression.rs @@ -0,0 +1,137 @@ +extern crate tiff; + +use std::io::Cursor; +use tiff::{ + decoder::{Decoder, DecodingResult}, + encoder::{colortype, compression::*, TiffEncoder}, + tags::Tag, +}; + +trait TestImage { + const WIDTH: u32; + const HEIGHT: u32; + type PixelType; + + fn generate() -> Vec; +} + +struct TestImageColor; +impl TestImage for TestImageColor { + const WIDTH: u32 = 1; + const HEIGHT: u32 = 7; + type PixelType = u16; + + fn generate() -> Vec { + let mut data: Vec = Vec::with_capacity((Self::WIDTH * Self::HEIGHT) as usize * 3); + for x in 0..Self::WIDTH { + for y in 0..Self::HEIGHT { + let val = (x + y) % Self::PixelType::MAX as u32; + data.extend(std::iter::repeat(val as Self::PixelType).take(3)); + } + } + assert_eq!(data.len() as u32, Self::WIDTH * Self::HEIGHT * 3); + data + } +} + +struct TestImageGrayscale; +impl TestImage for TestImageGrayscale { + const WIDTH: u32 = 21; + const HEIGHT: u32 = 10; + type PixelType = u8; + + fn generate() -> Vec { + let mut data: Vec = Vec::with_capacity((Self::WIDTH * Self::HEIGHT) as usize); + for x in 0..Self::WIDTH { + for y in 0..Self::HEIGHT { + let val = (x + y) % Self::PixelType::MAX as u32; + data.push(val as Self::PixelType); + } + } + assert_eq!(data.len() as u32, Self::WIDTH * Self::HEIGHT); + data + } +} + +fn encode_decode_with_compression(compression: C) { + let mut data = Cursor::new(Vec::new()); + + let image_data_rgb = TestImageColor::generate(); + let image_data_gray = TestImageGrayscale::generate(); + + // Encode tiff with compression + { + // Create a multipage image with 2 images + let mut encoder = TiffEncoder::new(&mut data).unwrap(); + + // Write first colored image + let image_rgb = encoder + .new_image_with_compression::( + TestImageColor::WIDTH, + TestImageColor::HEIGHT, + compression.clone(), + ) + .unwrap(); + image_rgb.write_data(&image_data_rgb).unwrap(); + + // Write second grayscale image + let image_gray = encoder + .new_image_with_compression::( + TestImageGrayscale::WIDTH, + TestImageGrayscale::HEIGHT, + compression, + ) + .unwrap(); + image_gray.write_data(&image_data_gray).unwrap(); + } + + // Decode tiff + data.set_position(0); + { + let mut decoder = Decoder::new(data).unwrap(); + + // Check the RGB image + assert_eq!( + decoder + .get_tag(Tag::ImageWidth) + .unwrap() + .into_u32() + .unwrap(), + TestImageColor::WIDTH + ); + assert_eq!( + match decoder.read_image() { + Ok(DecodingResult::U16(image_data)) => image_data, + unexpected => panic!("Descoding RGB failed: {:?}", unexpected), + }, + image_data_rgb + ); + + // Check the grayscale image + decoder.next_image().unwrap(); + assert_eq!( + match decoder.read_image() { + Ok(DecodingResult::U8(image_data)) => image_data, + unexpected => panic!("Decoding grayscale failed: {:?}", unexpected), + }, + image_data_gray + ); + } +} + +#[test] +fn encode_decode_without_compression() { + encode_decode_with_compression(Uncompressed); +} + +#[test] +fn encode_decode_with_lzw() { + encode_decode_with_compression(Lzw::default()); +} + +#[test] +fn encode_decode_with_deflate() { + encode_decode_with_compression(Deflate::with_level(DeflateLevel::Fast)); + encode_decode_with_compression(Deflate::with_level(DeflateLevel::Balanced)); + encode_decode_with_compression(Deflate::with_level(DeflateLevel::Best)); +}