From 99b2aa9b3aa2224dfc755d881c986ab076d3a693 Mon Sep 17 00:00:00 2001 From: Christopher Gundler Date: Tue, 21 Dec 2021 16:47:11 +0100 Subject: [PATCH 1/6] Add TIFF compression and corresponding tests --- README.md | 32 +-- benches/lzw.rs | 2 +- src/encoder/compression/deflate.rs | 108 ++++++++++ src/encoder/compression/lzw.rs | 79 +++++++ src/encoder/compression/mod.rs | 58 ++++++ src/encoder/compression/uncompressed.rs | 44 ++++ src/encoder/mod.rs | 78 ++++++- src/encoder/tiff_value.rs | 260 ++++++++++++++---------- src/error.rs | 13 ++ tests/encode_images_with_compression.rs | 137 +++++++++++++ 10 files changed, 676 insertions(+), 135 deletions(-) create mode 100644 src/encoder/compression/deflate.rs create mode 100644 src/encoder/compression/lzw.rs create mode 100644 src/encoder/compression/mod.rs create mode 100644 src/encoder/compression/uncompressed.rs create mode 100644 tests/encode_images_with_compression.rs 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)); +} From bc7be68d0fb7cc3111f88f3cd07bfde5c3386b21 Mon Sep 17 00:00:00 2001 From: Christopher Gundler Date: Wed, 22 Dec 2021 10:48:52 +0100 Subject: [PATCH 2/6] Simplify tests --- tests/encode_images_with_compression.rs | 141 +++++++++++++----------- 1 file changed, 78 insertions(+), 63 deletions(-) diff --git a/tests/encode_images_with_compression.rs b/tests/encode_images_with_compression.rs index c1aadc8..80f513a 100644 --- a/tests/encode_images_with_compression.rs +++ b/tests/encode_images_with_compression.rs @@ -1,88 +1,111 @@ extern crate tiff; -use std::io::Cursor; +use std::io::{Cursor, Seek, Write}; use tiff::{ decoder::{Decoder, DecodingResult}, - encoder::{colortype, compression::*, TiffEncoder}, - tags::Tag, + encoder::{ + colortype::{self, ColorType}, + compression::*, + TiffEncoder, TiffValue, + }, }; -trait TestImage { +trait TestImage: From::Inner>> { const WIDTH: u32; const HEIGHT: u32; - type PixelType; + type Color: ColorType; - fn generate() -> Vec; -} + fn reference_data(&self) -> &[::Inner]; + fn generate_pixel(x: u32, y: u32) -> [::Inner; NUM_CHANNELS]; -struct TestImageColor; -impl TestImage for TestImageColor { - const WIDTH: u32 = 1; - const HEIGHT: u32 = 7; - type PixelType = u16; + fn compress( + &self, + encoder: &mut TiffEncoder, + compression: C, + ) where + [::Inner]: TiffValue, + { + let image = encoder + .new_image_with_compression::(Self::WIDTH, Self::HEIGHT, compression) + .unwrap(); + image.write_data(self.reference_data()).unwrap(); + } - fn generate() -> Vec { - let mut data: Vec = Vec::with_capacity((Self::WIDTH * Self::HEIGHT) as usize * 3); + fn generate() -> Self { + assert_eq!( + Self::Color::BITS_PER_SAMPLE.len(), + NUM_CHANNELS, + "Incompatible color type" + ); + + let mut data = Vec::with_capacity((Self::WIDTH * Self::HEIGHT) as usize * NUM_CHANNELS); 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)); + data.extend(IntoIterator::into_iter(Self::generate_pixel(x, y))); } } - assert_eq!(data.len() as u32, Self::WIDTH * Self::HEIGHT * 3); - data + Self::from(data) } } -struct TestImageGrayscale; -impl TestImage for TestImageGrayscale { +struct TestImageColor(Vec); + +impl From> for TestImageColor { + fn from(value: Vec) -> Self { + Self(value) + } +} + +impl TestImage<3> for TestImageColor { + const WIDTH: u32 = 1; + const HEIGHT: u32 = 7; + type Color = colortype::RGB16; + + fn reference_data(&self) -> &[u16] { + &self.0 + } + + fn generate_pixel(x: u32, y: u32) -> [::Inner; 3] { + let val = (x + y) % ::Inner::MAX as u32; + [val as ::Inner; 3] + } +} + +struct TestImageGrayscale(Vec); + +impl From> for TestImageGrayscale { + fn from(value: Vec) -> Self { + Self(value) + } +} + +impl TestImage<1> for TestImageGrayscale { const WIDTH: u32 = 21; const HEIGHT: u32 = 10; - type PixelType = u8; + type Color = colortype::Gray8; - 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 reference_data(&self) -> &[u8] { + &self.0 + } + + fn generate_pixel(x: u32, y: u32) -> [::Inner; 1] { + let val = (x + y) % ::Inner::MAX as u32; + [val as ::Inner] } } 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(); + let image_rgb = TestImageColor::generate(); + let image_grayscale = 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(); + image_rgb.compress(&mut encoder, compression.clone()); + image_grayscale.compress(&mut encoder, compression); } // Decode tiff @@ -91,20 +114,12 @@ fn encode_decode_with_compression(compression: C) { 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 + image_rgb.reference_data() ); // Check the grayscale image @@ -114,7 +129,7 @@ fn encode_decode_with_compression(compression: C) { Ok(DecodingResult::U8(image_data)) => image_data, unexpected => panic!("Decoding grayscale failed: {:?}", unexpected), }, - image_data_gray + image_grayscale.reference_data() ); } } From 30bf5089830a5afed0daa76cc78e05e155f2ecbb Mon Sep 17 00:00:00 2001 From: Christopher Gundler Date: Wed, 22 Dec 2021 11:10:13 +0100 Subject: [PATCH 3/6] Avoid allocations --- src/encoder/tiff_value.rs | 40 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/src/encoder/tiff_value.rs b/src/encoder/tiff_value.rs index 8813d21..43653f4 100644 --- a/src/encoder/tiff_value.rs +++ b/src/encoder/tiff_value.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, io::Write}; +use std::{borrow::Cow, io::Write, slice::from_ref}; use crate::{bytecast, tags::Type, TiffError, TiffFormatError, TiffResult}; @@ -172,7 +172,7 @@ impl TiffValue for u8 { } fn data(&self) -> Cow<[u8]> { - Cow::Owned(vec![*self]) + Cow::Borrowed(from_ref(self)) } } @@ -190,7 +190,7 @@ impl TiffValue for i8 { } fn data(&self) -> Cow<[u8]> { - Cow::Owned(self.to_ne_bytes().to_vec()) + Cow::Borrowed(bytecast::i8_as_ne_bytes(from_ref(self))) } } @@ -208,7 +208,7 @@ impl TiffValue for u16 { } fn data(&self) -> Cow<[u8]> { - Cow::Owned(self.to_ne_bytes().to_vec()) + Cow::Borrowed(bytecast::u16_as_ne_bytes(from_ref(self))) } } @@ -226,7 +226,7 @@ impl TiffValue for i16 { } fn data(&self) -> Cow<[u8]> { - Cow::Owned(self.to_ne_bytes().to_vec()) + Cow::Borrowed(bytecast::i16_as_ne_bytes(from_ref(self))) } } @@ -244,7 +244,7 @@ impl TiffValue for u32 { } fn data(&self) -> Cow<[u8]> { - Cow::Owned(self.to_ne_bytes().to_vec()) + Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(self))) } } @@ -262,7 +262,7 @@ impl TiffValue for i32 { } fn data(&self) -> Cow<[u8]> { - Cow::Owned(self.to_ne_bytes().to_vec()) + Cow::Borrowed(bytecast::i32_as_ne_bytes(from_ref(self))) } } @@ -280,7 +280,7 @@ impl TiffValue for u64 { } fn data(&self) -> Cow<[u8]> { - Cow::Owned(self.to_ne_bytes().to_vec()) + Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(self))) } } @@ -298,7 +298,7 @@ impl TiffValue for i64 { } fn data(&self) -> Cow<[u8]> { - Cow::Owned(self.to_ne_bytes().to_vec()) + Cow::Borrowed(bytecast::i64_as_ne_bytes(from_ref(self))) } } @@ -316,7 +316,7 @@ impl TiffValue for f32 { } fn data(&self) -> Cow<[u8]> { - Cow::Owned(self.to_ne_bytes().to_vec()) + Cow::Borrowed(bytecast::f32_as_ne_bytes(from_ref(self))) } } @@ -334,7 +334,7 @@ impl TiffValue for f64 { } fn data(&self) -> Cow<[u8]> { - Cow::Owned(self.to_ne_bytes().to_vec()) + Cow::Borrowed(bytecast::f64_as_ne_bytes(from_ref(self))) } } @@ -352,10 +352,7 @@ impl TiffValue for Ifd { } fn data(&self) -> Cow<[u8]> { - Cow::Owned({ - let dword: [u8; 4] = self.0.to_ne_bytes(); - dword.to_vec() - }) + Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(&self.0))) } } @@ -373,10 +370,7 @@ impl TiffValue for Ifd8 { } fn data(&self) -> Cow<[u8]> { - Cow::Owned({ - let qword: [u8; 8] = self.0.to_ne_bytes(); - qword.to_vec() - }) + Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(&self.0))) } } @@ -396,8 +390,8 @@ impl TiffValue for Rational { 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(); + let first_dword = bytecast::u32_as_ne_bytes(from_ref(&self.n)); + let second_dword = bytecast::u32_as_ne_bytes(from_ref(&self.d)); [first_dword, second_dword].concat() }) } @@ -419,8 +413,8 @@ impl TiffValue for SRational { 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(); + let first_dword = bytecast::i32_as_ne_bytes(from_ref(&self.n)); + let second_dword = bytecast::i32_as_ne_bytes(from_ref(&self.d)); [first_dword, second_dword].concat() }) } From de95ac35a2ef2972bc76d3f3e6d5e7ebe6c13934 Mon Sep 17 00:00:00 2001 From: Christopher Gundler Date: Wed, 22 Dec 2021 12:29:39 +0100 Subject: [PATCH 4/6] Remove "CompressionError" --- src/error.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/error.rs b/src/error.rs index 0f51891..45ea5b0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -33,9 +33,6 @@ 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. @@ -233,7 +230,6 @@ 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"), } } } @@ -247,7 +243,6 @@ 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", } } @@ -296,8 +291,12 @@ impl From for TiffError { } impl From for TiffError { - fn from(_err: LzwError) -> TiffError { - TiffError::CompressionError + fn from(err: LzwError) -> TiffError { + match err { + LzwError::InvalidCode => TiffError::FormatError(TiffFormatError::Format(String::from( + "LZW compressed data corrupted", + ))), + } } } From 9f1e3d7a89eb4ac7881c8fd73ca77e3c28967c1f Mon Sep 17 00:00:00 2001 From: Pretorer <39985339+Pretorer@users.noreply.github.com> Date: Mon, 10 Jan 2022 11:55:08 +0100 Subject: [PATCH 5/6] moved compression into TiffWriter --- src/encoder/compression/deflate.rs | 67 ++++++------------- src/encoder/compression/lzw.rs | 74 ++++++--------------- src/encoder/compression/mod.rs | 76 +++++++++++----------- src/encoder/compression/uncompressed.rs | 43 ++++++------- src/encoder/mod.rs | 24 +++++-- src/encoder/writer.rs | 86 ++++++++++++++++++------- tests/encode_images_with_compression.rs | 2 +- 7 files changed, 179 insertions(+), 193 deletions(-) diff --git a/src/encoder/compression/deflate.rs b/src/encoder/compression/deflate.rs index de6532f..c198dbc 100644 --- a/src/encoder/compression/deflate.rs +++ b/src/encoder/compression/deflate.rs @@ -1,23 +1,11 @@ -use std::{ - convert::TryInto, - io::{Seek, Write}, -}; - +use crate::{encoder::compression::*, tags::CompressionMethod}; use flate2::{write::ZlibEncoder, Compression as FlateCompression}; - -use crate::{ - encoder::{ - colortype::ColorType, compression::Compression, DirectoryEncoder, TiffKind, TiffValue, - }, - tags::CompressionMethod, - TiffResult, -}; +use std::io::Write; /// The Deflate algorithm used to compress image data in TIFF files. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] pub struct Deflate { level: FlateCompression, - buffer: Vec, } /// The level of compression used by the Deflate algorithm. @@ -40,13 +28,9 @@ impl Default for DeflateLevel { } 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. + /// Create a new deflate compressor 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), } } @@ -61,36 +45,25 @@ impl Default for Deflate { 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()?; - } + fn get_algorithm(&self) -> Compressor { + Compressor::Deflate(self.clone()) + } +} - 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)) +impl CompressionAlgorithm for Deflate { + fn write_to(&mut self, writer: &mut W, bytes: &[u8]) -> Result { + let mut encoder = ZlibEncoder::new(writer, self.level); + encoder.write(&bytes)?; + encoder.try_finish()?; + Ok(encoder.total_out()) } } #[cfg(test)] mod tests { - use crate::encoder::compression::tests::{compress, TEST_DATA}; + use super::*; + use crate::encoder::compression::tests::TEST_DATA; + use std::io::Cursor; #[test] fn test_deflate() { @@ -102,7 +75,9 @@ mod tests { 0x3E, 0xAC, 0xF1, 0x98, 0xB9, 0x70, 0x17, 0x13, ]; - let compressed_data = compress(TEST_DATA, super::Deflate::default()); - assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA); + let mut compressed_data = Vec::::new(); + let mut writer = Cursor::new(&mut compressed_data); + Deflate::default().write_to(&mut writer, TEST_DATA).unwrap(); + assert_eq!(EXPECTED_COMPRESSED_DATA, compressed_data.as_slice()); } } diff --git a/src/encoder/compression/lzw.rs b/src/encoder/compression/lzw.rs index 45806c2..0e0f2aa 100644 --- a/src/encoder/compression/lzw.rs +++ b/src/encoder/compression/lzw.rs @@ -1,67 +1,33 @@ -use std::{ - convert::TryInto, - io::{Seek, Write}, -}; - +use crate::{encoder::compression::*, tags::CompressionMethod}; +use std::io::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), - } - } -} +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct Lzw; 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()?; + fn get_algorithm(&self) -> Compressor { + Compressor::Lzw(*self) + } +} - 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)) +impl CompressionAlgorithm for Lzw { + fn write_to(&mut self, writer: &mut W, bytes: &[u8]) -> Result { + let mut encoder = LZWEncoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8); + let result = encoder.into_stream(writer).encode_all(bytes); + let byte_count = result.bytes_written as u64; + result.status.map(|_| byte_count) } } #[cfg(test)] mod tests { - use crate::encoder::compression::tests::{compress, TEST_DATA}; + use super::*; + use crate::encoder::compression::tests::TEST_DATA; + use std::io::Cursor; #[test] fn test_lzw() { @@ -73,7 +39,9 @@ mod tests { 0x4E, 0x86, 0x83, 0x69, 0xCC, 0x5D, 0x01, ]; - let compressed_data = compress(TEST_DATA, super::Lzw::default()); - assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA); + let mut compressed_data = Vec::::new(); + let mut writer = Cursor::new(&mut compressed_data); + Lzw::default().write_to(&mut writer, TEST_DATA).unwrap(); + assert_eq!(EXPECTED_COMPRESSED_DATA, compressed_data.as_slice()); } } diff --git a/src/encoder/compression/mod.rs b/src/encoder/compression/mod.rs index 2b1aa5e..88dea24 100644 --- a/src/encoder/compression/mod.rs +++ b/src/encoder/compression/mod.rs @@ -1,10 +1,5 @@ -use std::io::prelude::*; - -use crate::{ - encoder::{ColorType, DirectoryEncoder, TiffKind, TiffValue}, - error::TiffResult, - tags::CompressionMethod, -}; +use crate::tags::CompressionMethod; +use std::io::{self, Write}; mod deflate; mod lzw; @@ -14,45 +9,48 @@ 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 { +/// An algorithm used for compression +pub trait CompressionAlgorithm { + /// The algorithm writes data directly into the writer. + /// It returns the total number of bytes written. + fn write_to(&mut self, writer: &mut W, bytes: &[u8]) -> Result; +} + +/// An algorithm used for compression with associated enums and optional configurations. +pub trait Compression: CompressionAlgorithm { /// 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; + /// Method to optain a type that can store each variant of comression algorithm. + fn get_algorithm(&self) -> Compressor; +} + +/// An enum to store each compression algorithm. +pub enum Compressor { + Uncompressed(Uncompressed), + Lzw(Lzw), + Deflate(Deflate), +} + +impl Default for Compressor { + /// The default compression strategy does not apply any compression. + fn default() -> Self { + Compressor::Uncompressed(Uncompressed::default()) + } +} + +impl CompressionAlgorithm for Compressor { + fn write_to(&mut self, writer: &mut W, bytes: &[u8]) -> Result { + match self { + Compressor::Uncompressed(algorithm) => algorithm.write_to(writer, bytes), + Compressor::Lzw(algorithm) => algorithm.write_to(writer, bytes), + Compressor::Deflate(algorithm) => algorithm.write_to(writer, bytes), + } + } } #[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 index 17ca285..900426f 100644 --- a/src/encoder/compression/uncompressed.rs +++ b/src/encoder/compression/uncompressed.rs @@ -1,15 +1,5 @@ -use std::{ - convert::TryInto, - io::{Seek, Write}, -}; - -use crate::{ - encoder::{ - colortype::ColorType, compression::Compression, DirectoryEncoder, TiffKind, TiffValue, - }, - tags::CompressionMethod, - TiffResult, -}; +use crate::{encoder::compression::*, tags::CompressionMethod}; +use std::io::Write; /// The default algorithm which does not compress at all. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] @@ -18,27 +8,30 @@ 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)) + fn get_algorithm(&self) -> Compressor { + Compressor::Uncompressed(*self) + } +} + +impl CompressionAlgorithm for Uncompressed { + fn write_to(&mut self, writer: &mut W, bytes: &[u8]) -> Result { + writer.write(bytes).map(|byte_count| byte_count as u64) } } #[cfg(test)] mod tests { - use crate::encoder::compression::tests::{compress, TEST_DATA}; + use super::*; + use crate::encoder::compression::tests::TEST_DATA; + use std::io::Cursor; #[test] fn test_no_compression() { - let compressed_data = compress(TEST_DATA, super::Uncompressed); + let mut compressed_data = Vec::::new(); + let mut writer = Cursor::new(&mut compressed_data); + Uncompressed::default() + .write_to(&mut writer, TEST_DATA) + .unwrap(); assert_eq!(TEST_DATA, compressed_data); } } diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index 8521d1e..7ca6f60 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -246,6 +246,11 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { Ok(offset) } + /// Provides the number of bytes written by the underlying TiffWriter during the last call. + fn last_written(&self) -> u64 { + self.writer.last_written() + } + fn finish_internal(&mut self) -> TiffResult<()> { let ifd_pointer = self.write_directory()?; let curr_pos = self.writer.offset(); @@ -383,7 +388,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression> strip_offsets: Vec::new(), strip_byte_count: Vec::new(), dropped: false, - compression, + compression: compression, _phantom: ::std::marker::PhantomData, }) } @@ -416,11 +421,11 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression> } // 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); + let offset = self.encoder.write_data(value)?; + let byte_count = self.encoder.last_written() as usize; + + self.strip_offsets.push(K::convert_offset(offset)?); + self.strip_byte_count.push(byte_count.try_into()?); self.strip_idx += 1; Ok(()) @@ -446,12 +451,19 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression> ) .into()); } + + self.encoder + .writer + .set_compression(self.compression.get_algorithm()); + let mut idx = 0; while self.next_strip_sample_count() > 0 { let sample_count = usize::try_from(self.next_strip_sample_count())?; self.write_strip(&data[idx..idx + sample_count])?; idx += sample_count; } + + self.encoder.writer.reset_compression(); self.finish()?; Ok(()) } diff --git a/src/encoder/writer.rs b/src/encoder/writer.rs index 5ba47d1..c5139e9 100644 --- a/src/encoder/writer.rs +++ b/src/encoder/writer.rs @@ -1,3 +1,4 @@ +use crate::encoder::compression::*; use crate::error::TiffResult; use std::io::{self, Seek, SeekFrom, Write}; @@ -42,87 +43,126 @@ pub fn write_bigtiff_header(writer: &mut TiffWriter) -> TiffResult< pub struct TiffWriter { writer: W, offset: u64, + byte_count: u64, + compressor: Compressor, } impl TiffWriter { pub fn new(writer: W) -> Self { - Self { writer, offset: 0 } + Self { + writer, + offset: 0, + byte_count: 0, + compressor: Compressor::default(), + } + } + + pub fn set_compression(&mut self, compressor: Compressor) { + self.compressor = compressor; + } + + pub fn reset_compression(&mut self) { + self.compressor = Compressor::default(); } pub fn offset(&self) -> u64 { self.offset } + pub fn last_written(&self) -> u64 { + self.byte_count + } + pub fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), io::Error> { - self.writer.write_all(bytes)?; - self.offset += bytes.len() as u64; + self.byte_count = self.compressor.write_to(&mut self.writer, bytes)?; + self.offset += self.byte_count; Ok(()) } pub fn write_u8(&mut self, n: u8) -> Result<(), io::Error> { - self.writer.write_all(&n.to_ne_bytes())?; - self.offset += 1; + self.byte_count = self + .compressor + .write_to(&mut self.writer, &n.to_ne_bytes())?; + self.offset += self.byte_count; Ok(()) } pub fn write_i8(&mut self, n: i8) -> Result<(), io::Error> { - self.writer.write_all(&n.to_ne_bytes())?; - self.offset += 1; + self.byte_count = self + .compressor + .write_to(&mut self.writer, &n.to_ne_bytes())?; + self.offset += self.byte_count; Ok(()) } pub fn write_u16(&mut self, n: u16) -> Result<(), io::Error> { - self.writer.write_all(&n.to_ne_bytes())?; - self.offset += 2; + self.byte_count = self + .compressor + .write_to(&mut self.writer, &n.to_ne_bytes())?; + self.offset += self.byte_count; Ok(()) } pub fn write_i16(&mut self, n: i16) -> Result<(), io::Error> { - self.writer.write_all(&n.to_ne_bytes())?; - self.offset += 2; + self.byte_count = self + .compressor + .write_to(&mut self.writer, &n.to_ne_bytes())?; + self.offset += self.byte_count; Ok(()) } pub fn write_u32(&mut self, n: u32) -> Result<(), io::Error> { - self.writer.write_all(&n.to_ne_bytes())?; - self.offset += 4; + self.byte_count = self + .compressor + .write_to(&mut self.writer, &n.to_ne_bytes())?; + self.offset += self.byte_count; Ok(()) } pub fn write_i32(&mut self, n: i32) -> Result<(), io::Error> { - self.writer.write_all(&n.to_ne_bytes())?; - self.offset += 4; + self.byte_count = self + .compressor + .write_to(&mut self.writer, &n.to_ne_bytes())?; + self.offset += self.byte_count; Ok(()) } pub fn write_u64(&mut self, n: u64) -> Result<(), io::Error> { - self.writer.write_all(&n.to_ne_bytes())?; - self.offset += 8; + self.byte_count = self + .compressor + .write_to(&mut self.writer, &n.to_ne_bytes())?; + self.offset += self.byte_count; Ok(()) } pub fn write_i64(&mut self, n: i64) -> Result<(), io::Error> { - self.writer.write_all(&n.to_ne_bytes())?; - self.offset += 8; + self.byte_count = self + .compressor + .write_to(&mut self.writer, &n.to_ne_bytes())?; + self.offset += self.byte_count; Ok(()) } pub fn write_f32(&mut self, n: f32) -> Result<(), io::Error> { - self.writer.write_all(&u32::to_ne_bytes(n.to_bits()))?; - self.offset += 4; + self.byte_count = self + .compressor + .write_to(&mut self.writer, &u32::to_ne_bytes(n.to_bits()))?; + self.offset += self.byte_count; Ok(()) } pub fn write_f64(&mut self, n: f64) -> Result<(), io::Error> { - self.writer.write_all(&u64::to_ne_bytes(n.to_bits()))?; - self.offset += 8; + self.byte_count = self + .compressor + .write_to(&mut self.writer, &u64::to_ne_bytes(n.to_bits()))?; + self.offset += self.byte_count; Ok(()) } diff --git a/tests/encode_images_with_compression.rs b/tests/encode_images_with_compression.rs index 80f513a..e4a0add 100644 --- a/tests/encode_images_with_compression.rs +++ b/tests/encode_images_with_compression.rs @@ -136,7 +136,7 @@ fn encode_decode_with_compression(compression: C) { #[test] fn encode_decode_without_compression() { - encode_decode_with_compression(Uncompressed); + encode_decode_with_compression(Uncompressed::default()); } #[test] From 1d8cbbc6a1d2889e51f69ee62f2f902291240728 Mon Sep 17 00:00:00 2001 From: Pretorer <39985339+Pretorer@users.noreply.github.com> Date: Mon, 10 Jan 2022 11:57:59 +0100 Subject: [PATCH 6/6] add Packbits compression --- README.md | 2 +- src/encoder/compression/mod.rs | 4 + src/encoder/compression/packbits.rs | 213 ++++++++++++++++++++++++ tests/encode_images_with_compression.rs | 5 + 4 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 src/encoder/compression/packbits.rs diff --git a/README.md b/README.md index 3612654..f494a50 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ This table lists photometric interpretations and sample formats which are suppor | None | ✓ | ✓ | | LZW | ✓ | ✓ | | Deflate | ✓ | ✓ | -| PackBits | ✓ | | +| PackBits | ✓ | ✓ | ## Not yet supported diff --git a/src/encoder/compression/mod.rs b/src/encoder/compression/mod.rs index 88dea24..04baef3 100644 --- a/src/encoder/compression/mod.rs +++ b/src/encoder/compression/mod.rs @@ -3,10 +3,12 @@ use std::io::{self, Write}; mod deflate; mod lzw; +mod packbits; mod uncompressed; pub use self::deflate::{Deflate, DeflateLevel}; pub use self::lzw::Lzw; +pub use self::packbits::Packbits; pub use self::uncompressed::Uncompressed; /// An algorithm used for compression @@ -30,6 +32,7 @@ pub enum Compressor { Uncompressed(Uncompressed), Lzw(Lzw), Deflate(Deflate), + Packbits(Packbits), } impl Default for Compressor { @@ -45,6 +48,7 @@ impl CompressionAlgorithm for Compressor { Compressor::Uncompressed(algorithm) => algorithm.write_to(writer, bytes), Compressor::Lzw(algorithm) => algorithm.write_to(writer, bytes), Compressor::Deflate(algorithm) => algorithm.write_to(writer, bytes), + Compressor::Packbits(algorithm) => algorithm.write_to(writer, bytes), } } } diff --git a/src/encoder/compression/packbits.rs b/src/encoder/compression/packbits.rs new file mode 100644 index 0000000..fc032d0 --- /dev/null +++ b/src/encoder/compression/packbits.rs @@ -0,0 +1,213 @@ +use crate::{encoder::compression::*, tags::CompressionMethod}; +use std::io::{BufWriter, Error, ErrorKind, Write}; + +/// Compressor that uses the Packbits[^note] algorithm to compress bytes. +/// +/// [^note]: PackBits is often ineffective on continuous tone images, +/// including many grayscale images. In such cases, it is better +/// to leave the image uncompressed. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct Packbits; + +impl Compression for Packbits { + const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::PackBits; + + fn get_algorithm(&self) -> Compressor { + Compressor::Packbits(*self) + } +} + +impl CompressionAlgorithm for Packbits { + fn write_to(&mut self, writer: &mut W, bytes: &[u8]) -> Result { + // Inspired by https://github.com/skirridsystems/packbits + + const MIN_REPT: u8 = 3; // Minimum run to compress between differ blocks + const MAX_BYTES: u8 = 128; // Maximum number of bytes that can be encoded in a header byte + + // Encoding for header byte based on number of bytes represented. + fn encode_diff(n: u8) -> u8 { + n - 1 + } + fn encode_rept(n: u8) -> u8 { + let var = 256 - (n - 1) as u16; + var as u8 + } + + fn write_u8(writer: &mut W, byte: u8) -> Result { + writer.write(&[byte]).map(|byte_count| byte_count as u64) + } + + let mut bufwriter = BufWriter::new(writer); + let mut bytes_written = 0u64; // The number of bytes written into the writer + let mut offset: Option = None; // The index of the first byte written into the writer + + let mut src_index: usize = 0; // Index of the current byte + let mut src_count = bytes.len(); //The number of bytes remaining to be compressed + + let mut in_run = false; // Indication whether counting of similar bytes is performed + let mut run_index = 0u8; // Distance into pending bytes that a run starts + + let mut bytes_pending = 0u8; // Bytes looked at but not yet output + let mut pending_index = 0usize; // Index of the first pending byte + + let mut curr_byte: u8; // Byte currently being considered + let mut last_byte: u8; // Previous byte + + // Need at least one byte to compress + if src_count == 0 { + return Err(Error::new(ErrorKind::WriteZero, "write zero")); + } + + // Prime compressor with first character. + last_byte = bytes[src_index]; + src_index += 1; + bytes_pending += 1; + + while src_count - 1 != 0 { + src_count -= 1; + curr_byte = bytes[src_index]; + src_index += 1; + bytes_pending += 1; + + if in_run { + if (curr_byte != last_byte) || (bytes_pending > MAX_BYTES) { + offset.get_or_insert(write_u8(&mut bufwriter, encode_rept(bytes_pending - 1))?); + write_u8(&mut bufwriter, last_byte)?; + bytes_written += 2; + + bytes_pending = 1; + pending_index = src_index - 1; + run_index = 0; + in_run = false; + } + } else { + if bytes_pending > MAX_BYTES { + // We have as much differing data as we can output in one chunk. + // Output MAX_BYTES leaving one byte. + offset.get_or_insert(write_u8(&mut bufwriter, encode_diff(MAX_BYTES))?); + bufwriter.write(&bytes[pending_index..pending_index + MAX_BYTES as usize])?; + bytes_written += 1 + MAX_BYTES as u64; + + pending_index += MAX_BYTES as usize; + bytes_pending -= MAX_BYTES; + run_index = bytes_pending - 1; // A run could start here + } else if curr_byte == last_byte { + if (bytes_pending - run_index >= MIN_REPT) || (run_index == 0) { + // This is a worthwhile run + if run_index != 0 { + // Flush differing data out of input buffer + offset.get_or_insert(write_u8(&mut bufwriter, encode_diff(run_index))?); + bufwriter + .write(&bytes[pending_index..pending_index + run_index as usize])?; + bytes_written += 1 + run_index as u64; + } + bytes_pending -= run_index; // Length of run + in_run = true; + } + } else { + run_index = bytes_pending - 1; // A run could start here + } + } + last_byte = curr_byte; + } + + // Output the remainder + if in_run { + bytes_written += 2; + offset.get_or_insert(write_u8(&mut bufwriter, encode_rept(bytes_pending))?); + write_u8(&mut bufwriter, last_byte)?; + } else { + bytes_written += 1 + bytes_pending as u64; + offset.get_or_insert(write_u8(&mut bufwriter, encode_diff(bytes_pending))?); + bufwriter.write(&bytes[pending_index..pending_index + bytes_pending as usize])?; + } + + Ok(bytes_written) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::encoder::compression::tests::TEST_DATA; + use std::io::Cursor; + + #[test] + fn test_packbits_single_byte() { + // compress single byte + const UNCOMPRESSED_DATA: [u8; 1] = [0x3F]; + const EXPECTED_COMPRESSED_DATA: [u8; 2] = [0x00, 0x3F]; + + let mut compressed_data = Vec::::new(); + let mut writer = Cursor::new(&mut compressed_data); + Packbits::default() + .write_to(&mut writer, &UNCOMPRESSED_DATA) + .unwrap(); + assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA); + } + + #[test] + fn test_packbits_rept() { + // compress buffer with repetitive sequence + const UNCOMPRESSED_DATA: &'static [u8] = + b"This strrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrring hangs."; + const EXPECTED_COMPRESSED_DATA: &'static [u8] = b"\x06This st\xD1r\x09ing hangs."; + + let mut compressed_data = Vec::::new(); + let mut writer = Cursor::new(&mut compressed_data); + Packbits::default() + .write_to(&mut writer, &UNCOMPRESSED_DATA) + .unwrap(); + assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA); + } + + #[test] + fn test_packbits_large_rept_nonrept() { + // compress buffer with large repetitive and non-repetitive sequence + let mut data = b"This st".to_vec(); + for _i in 0..158 { + data.push(b'r'); + } + data.extend_from_slice(b"ing hangs."); + for i in 0..158 { + data.push(i); + } + + const EXPECTED_COMPRESSED_DATA: [u8; 182] = [ + 0x06, 0x54, 0x68, 0x69, 0x73, 0x20, 0x73, 0x74, 0x81, 0x72, 0xE3, 0x72, 0x7F, 0x69, + 0x6E, 0x67, 0x20, 0x68, 0x61, 0x6E, 0x67, 0x73, 0x2E, 0x00, 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, + 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, + 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, + 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, + 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, + 0x75, 0x27, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, + 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, + ]; + + let mut compressed_data = Vec::::new(); + let mut writer = Cursor::new(&mut compressed_data); + Packbits::default() + .write_to(&mut writer, data.as_slice()) + .unwrap(); + assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA); + } + + #[test] + fn test_packbits() { + // compress teststring + const EXPECTED_COMPRESSED_DATA: &'static [u8] = + b"\x3CThis is a string for checking various compression algorithms."; + + let mut compressed_data = Vec::::new(); + let mut writer = Cursor::new(&mut compressed_data); + Packbits::default() + .write_to(&mut writer, &TEST_DATA) + .unwrap(); + assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA); + } +} diff --git a/tests/encode_images_with_compression.rs b/tests/encode_images_with_compression.rs index e4a0add..1cf178f 100644 --- a/tests/encode_images_with_compression.rs +++ b/tests/encode_images_with_compression.rs @@ -150,3 +150,8 @@ fn encode_decode_with_deflate() { encode_decode_with_compression(Deflate::with_level(DeflateLevel::Balanced)); encode_decode_with_compression(Deflate::with_level(DeflateLevel::Best)); } + +#[test] +fn encode_decode_with_packbits() { + encode_decode_with_compression(Packbits::default()); +}