Add TIFF compression and corresponding tests
This commit is contained in:
1 parent
d7d63e648b
commit
99b2aa9b3a
10 files changed
+676
-135
No files matched your search
@@ -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<u8>,
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
@@ -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<u8>,
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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<C: Compression>(data: &[u8], compressor: C) -> Vec<u8> {
|
||||
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::<Gray8, _>(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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user