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]