Add TIFF compression and corresponding tests

This commit is contained in:
Christopher Gundler committed 2021-12-21 16:47:11 +01:00
1 parent d7d63e648b
commit 99b2aa9b3a
10 files changed
+664 -123

No files matched your search

+4 -4
View File
@@ -17,7 +17,7 @@ TIFF decoding and encoding library in pure Rust
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) |
@@ -30,10 +30,10 @@ This table lists photometric interpretations and sample formats which are suppor
### Compressions
| | Decoding | Encoding |
|-|-|-|
| -------- | -------- | -------- |
| None | ✓ | ✓ |
| LZW | ✓ | |
| Deflate | ✓ | |
| LZW | ✓ | ✓ |
| Deflate | ✓ | ✓ |
| PackBits | ✓ | |
+1 -1
View File
@@ -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");
+108
View File
@@ -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);
}
}
+79
View File
@@ -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);
}
}
+58
View File
@@ -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
}
}
+44
View File
@@ -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);
}
}
+67 -11
View File
@@ -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<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
&mut self,
width: u32,
height: u32,
) -> TiffResult<ImageEncoder<W, C, K>> {
) -> TiffResult<ImageEncoder<W, C, K, Uncompressed>> {
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<C: ColorType, D: Compression>(
&mut self,
width: u32,
height: u32,
compression: D,
) -> TiffResult<ImageEncoder<W, C, K, D>> {
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<C: ColorType>(
&mut self,
@@ -117,6 +130,23 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
let image: ImageEncoder<W, C, K> = 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<C: ColorType, D: 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<W, C, K, D> =
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<K::OffsetType>,
strip_byte_count: Vec<K::OffsetType>,
dropped: bool,
compression: D,
_phantom: ::std::marker::PhantomData<C>,
}
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<Self> {
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<Self>
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<Self> {
let row_samples = u64::from(width) * u64::try_from(<T>::BITS_PER_SAMPLE.len())?;
let row_bytes = row_samples * u64::from(<T::Inner>::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, <T>::BITS_PER_SAMPLE)?;
let sample_format: Vec<_> = <T>::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::<T, K, W>(&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();
+153 -107
View File
@@ -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<W: Write>(&self, writer: &mut TiffWriter<W>) -> 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<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.write_bytes(&self.data())?;
Ok(())
}
}
impl TiffValue for [u8] {
@@ -23,9 +34,8 @@ impl TiffValue for [u8] {
self.len()
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> 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<W: Write>(&self, writer: &mut TiffWriter<W>) -> 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<W: Write>(&self, writer: &mut TiffWriter<W>) -> 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<W: Write>(&self, writer: &mut TiffWriter<W>) -> 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<W: Write>(&self, writer: &mut TiffWriter<W>) -> 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<W: Write>(&self, writer: &mut TiffWriter<W>) -> 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<W: Write>(&self, writer: &mut TiffWriter<W>) -> 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<W: Write>(&self, writer: &mut TiffWriter<W>) -> 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<W: Write>(&self, writer: &mut TiffWriter<W>) -> 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<W: Write>(&self, writer: &mut TiffWriter<W>) -> 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<W: Write>(&self, writer: &mut TiffWriter<W>) -> 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<W: Write>(&self, writer: &mut TiffWriter<W>) -> 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<W: Write>(&self, writer: &mut TiffWriter<W>) -> 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<W: Write>(&self, writer: &mut TiffWriter<W>) -> 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<W: Write>(&self, writer: &mut TiffWriter<W>) -> 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<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
for x in self {
x.write(writer)?;
}
Ok(())
}
fn data(&self) -> Cow<[u8]> {
let mut buf: Vec<u8> = 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);
+13
View File
@@ -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<std::num::TryFromIntError> for TiffError {
}
}
impl From<LzwError> for TiffError {
fn from(_err: LzwError) -> TiffError {
TiffError::CompressionError
}
}
/// Result of an image decoding/encoding process
pub type TiffResult<T> = Result<T, TiffError>;
+137
View File
@@ -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<Self::PixelType>;
}
struct TestImageColor;
impl TestImage for TestImageColor {
const WIDTH: u32 = 1;
const HEIGHT: u32 = 7;
type PixelType = u16;
fn generate() -> Vec<Self::PixelType> {
let mut data: Vec<u16> = 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<Self::PixelType> {
let mut data: Vec<u8> = 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<C: Compression + Clone>(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::<colortype::RGB16, C>(
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::<colortype::Gray8, C>(
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));
}