Merge pull request #151 from mindQ-GmbH-Co-KG/compression-release
Add TIFF compression during encoding and corresponding tests
This commit is contained in:
12 files changed
+913
-156
No files matched your search
@@ -16,25 +16,25 @@ TIFF decoding and encoding library in pure Rust
|
|||||||
### Formats
|
### 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.
|
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 |
|
| `PhotometricInterpretation` | UINT Format | IEEEFP Format |
|
||||||
|-|-|-|
|
| --------------------------- | --------------------------------------- | ------------------------- |
|
||||||
| `WhiteIsZero` | Gray(8\|16\|32\|64) | Gray(32\|64) |
|
| `WhiteIsZero` | Gray(8\|16\|32\|64) | Gray(32\|64) |
|
||||||
| `BlackIsZero` | 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) |
|
| `RGB` | RGB(8\|16\|32\|64), RGBA(8\|16\|32\|64) | RGB(32\|64), RGBA(32\|64) |
|
||||||
| `RGBPalette` | | |
|
| `RGBPalette` | | |
|
||||||
| `Mask` | | |
|
| `Mask` | | |
|
||||||
| `CMYK` | CMYK(8\|16\|32\|64) | CMYK(32\|64) |
|
| `CMYK` | CMYK(8\|16\|32\|64) | CMYK(32\|64) |
|
||||||
| `YCbCr` | | |
|
| `YCbCr` | | |
|
||||||
| `CIELab` | | |
|
| `CIELab` | | |
|
||||||
|
|
||||||
### Compressions
|
### Compressions
|
||||||
|
|
||||||
| | Decoding | Encoding |
|
| | Decoding | Encoding |
|
||||||
|-|-|-|
|
| -------- | -------- | -------- |
|
||||||
| None | ✓ | ✓ |
|
| None | ✓ | ✓ |
|
||||||
| LZW | ✓ | |
|
| LZW | ✓ | ✓ |
|
||||||
| Deflate | ✓ | |
|
| Deflate | ✓ | ✓ |
|
||||||
| PackBits | ✓ | |
|
| PackBits | ✓ | ✓ |
|
||||||
|
|
||||||
|
|
||||||
## Not yet supported
|
## Not yet supported
|
||||||
|
|||||||
+1
-1
@@ -33,7 +33,7 @@ fn main() {
|
|||||||
def.data,
|
def.data,
|
||||||
|b, input| b.iter(|| read_image(input)),
|
|b, input| b.iter(|| read_image(input)),
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
let mut c = Criterion::default().configure_from_args();
|
let mut c = Criterion::default().configure_from_args();
|
||||||
let mut group = c.benchmark_group("tiff-lzw");
|
let mut group = c.benchmark_group("tiff-lzw");
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
use crate::{encoder::compression::*, tags::CompressionMethod};
|
||||||
|
use flate2::{write::ZlibEncoder, Compression as FlateCompression};
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
/// The Deflate algorithm used to compress image data in TIFF files.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Deflate {
|
||||||
|
level: FlateCompression,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
||||||
|
/// Create a new deflate compressor with a specific level of compression.
|
||||||
|
pub fn with_level(level: DeflateLevel) -> Self {
|
||||||
|
Self {
|
||||||
|
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 get_algorithm(&self) -> Compressor {
|
||||||
|
Compressor::Deflate(self.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompressionAlgorithm for Deflate {
|
||||||
|
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
|
||||||
|
let mut encoder = ZlibEncoder::new(writer, self.level);
|
||||||
|
encoder.write(&bytes)?;
|
||||||
|
encoder.try_finish()?;
|
||||||
|
Ok(encoder.total_out())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::encoder::compression::tests::TEST_DATA;
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
#[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 mut compressed_data = Vec::<u8>::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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
use crate::{encoder::compression::*, tags::CompressionMethod};
|
||||||
|
use std::io::Write;
|
||||||
|
use weezl::encode::Encoder as LZWEncoder;
|
||||||
|
|
||||||
|
/// The LZW algorithm used to compress image data in TIFF files.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
|
pub struct Lzw;
|
||||||
|
|
||||||
|
impl Compression for Lzw {
|
||||||
|
const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::LZW;
|
||||||
|
|
||||||
|
fn get_algorithm(&self) -> Compressor {
|
||||||
|
Compressor::Lzw(*self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompressionAlgorithm for Lzw {
|
||||||
|
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
|
||||||
|
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 super::*;
|
||||||
|
use crate::encoder::compression::tests::TEST_DATA;
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
#[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 mut compressed_data = Vec::<u8>::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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
use crate::tags::CompressionMethod;
|
||||||
|
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
|
||||||
|
pub trait CompressionAlgorithm {
|
||||||
|
/// The algorithm writes data directly into the writer.
|
||||||
|
/// It returns the total number of bytes written.
|
||||||
|
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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;
|
||||||
|
|
||||||
|
/// 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),
|
||||||
|
Packbits(Packbits),
|
||||||
|
}
|
||||||
|
|
||||||
|
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<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
|
||||||
|
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),
|
||||||
|
Compressor::Packbits(algorithm) => algorithm.write_to(writer, bytes),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
pub const TEST_DATA: &'static [u8] =
|
||||||
|
b"This is a string for checking various compression algorithms.";
|
||||||
|
}
|
||||||
@@ -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<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
|
||||||
|
// 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<W: Write>(writer: &mut W, byte: u8) -> Result<u64, Error> {
|
||||||
|
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<u64> = 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::<u8>::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::<u8>::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::<u8>::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::<u8>::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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
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)]
|
||||||
|
pub struct Uncompressed;
|
||||||
|
|
||||||
|
impl Compression for Uncompressed {
|
||||||
|
const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::None;
|
||||||
|
|
||||||
|
fn get_algorithm(&self) -> Compressor {
|
||||||
|
Compressor::Uncompressed(*self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompressionAlgorithm for Uncompressed {
|
||||||
|
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
|
||||||
|
writer.write(bytes).map(|byte_count| byte_count as u64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::encoder::compression::tests::TEST_DATA;
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_no_compression() {
|
||||||
|
let mut compressed_data = Vec::<u8>::new();
|
||||||
|
let mut writer = Cursor::new(&mut compressed_data);
|
||||||
|
Uncompressed::default()
|
||||||
|
.write_to(&mut writer, TEST_DATA)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(TEST_DATA, compressed_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
+77
-9
@@ -12,14 +12,16 @@ use std::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::TiffResult,
|
error::TiffResult,
|
||||||
tags::{self, ResolutionUnit, Tag},
|
tags::{ResolutionUnit, Tag},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod colortype;
|
pub mod colortype;
|
||||||
|
pub mod compression;
|
||||||
mod tiff_value;
|
mod tiff_value;
|
||||||
mod writer;
|
mod writer;
|
||||||
|
|
||||||
use self::colortype::*;
|
use self::colortype::*;
|
||||||
|
use self::compression::*;
|
||||||
use self::writer::*;
|
use self::writer::*;
|
||||||
|
|
||||||
/// Encoder for Tiff and BigTiff files.
|
/// Encoder for Tiff and BigTiff files.
|
||||||
@@ -98,11 +100,22 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
) -> TiffResult<ImageEncoder<W, C, K>> {
|
) -> TiffResult<ImageEncoder<W, C, K, Uncompressed>> {
|
||||||
let encoder = DirectoryEncoder::new(&mut self.writer)?;
|
let encoder = DirectoryEncoder::new(&mut self.writer)?;
|
||||||
ImageEncoder::new(encoder, width, height)
|
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.
|
/// Convenience function to write an entire image from memory.
|
||||||
pub fn write_image<C: ColorType>(
|
pub fn write_image<C: ColorType>(
|
||||||
&mut self,
|
&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)?;
|
let image: ImageEncoder<W, C, K> = ImageEncoder::new(encoder, width, height)?;
|
||||||
image.write_data(data)
|
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.
|
/// Low level interface to encode ifd directories.
|
||||||
@@ -216,6 +246,11 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
|
|||||||
Ok(offset)
|
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<()> {
|
fn finish_internal(&mut self) -> TiffResult<()> {
|
||||||
let ifd_pointer = self.write_directory()?;
|
let ifd_pointer = self.write_directory()?;
|
||||||
let curr_pos = self.writer.offset();
|
let curr_pos = self.writer.offset();
|
||||||
@@ -277,7 +312,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
|
/// 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>,
|
encoder: DirectoryEncoder<'a, W, K>,
|
||||||
strip_idx: u64,
|
strip_idx: u64,
|
||||||
strip_count: u64,
|
strip_count: u64,
|
||||||
@@ -288,11 +329,26 @@ pub struct ImageEncoder<'a, W: 'a + Write + Seek, C: ColorType, K: TiffKind> {
|
|||||||
strip_offsets: Vec<K::OffsetType>,
|
strip_offsets: Vec<K::OffsetType>,
|
||||||
strip_byte_count: Vec<K::OffsetType>,
|
strip_byte_count: Vec<K::OffsetType>,
|
||||||
dropped: bool,
|
dropped: bool,
|
||||||
|
compression: D,
|
||||||
_phantom: ::std::marker::PhantomData<C>,
|
_phantom: ::std::marker::PhantomData<C>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T, K> {
|
impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression>
|
||||||
fn new(mut encoder: DirectoryEncoder<'a, W, K>, width: u32, height: u32) -> TiffResult<Self> {
|
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_samples = u64::from(width) * u64::try_from(<T>::BITS_PER_SAMPLE.len())?;
|
||||||
let row_bytes = row_samples * u64::from(<T::Inner>::BYTE_LEN);
|
let row_bytes = row_samples * u64::from(<T::Inner>::BYTE_LEN);
|
||||||
|
|
||||||
@@ -304,7 +360,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::ImageWidth, width)?;
|
||||||
encoder.write_tag(Tag::ImageLength, height)?;
|
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)?;
|
encoder.write_tag(Tag::BitsPerSample, <T>::BITS_PER_SAMPLE)?;
|
||||||
let sample_format: Vec<_> = <T>::SAMPLE_FORMAT.iter().map(|s| s.to_u16()).collect();
|
let sample_format: Vec<_> = <T>::SAMPLE_FORMAT.iter().map(|s| s.to_u16()).collect();
|
||||||
@@ -332,6 +388,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
|
|||||||
strip_offsets: Vec::new(),
|
strip_offsets: Vec::new(),
|
||||||
strip_byte_count: Vec::new(),
|
strip_byte_count: Vec::new(),
|
||||||
dropped: false,
|
dropped: false,
|
||||||
|
compression: compression,
|
||||||
_phantom: ::std::marker::PhantomData,
|
_phantom: ::std::marker::PhantomData,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -354,7 +411,6 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
|
|||||||
where
|
where
|
||||||
[T::Inner]: TiffValue,
|
[T::Inner]: TiffValue,
|
||||||
{
|
{
|
||||||
// TODO: Compression
|
|
||||||
let samples = self.next_strip_sample_count();
|
let samples = self.next_strip_sample_count();
|
||||||
if u64::try_from(value.len())? != samples {
|
if u64::try_from(value.len())? != samples {
|
||||||
return Err(io::Error::new(
|
return Err(io::Error::new(
|
||||||
@@ -364,9 +420,12 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
|
|||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write the (possible compressed) data to the encoder.
|
||||||
let offset = self.encoder.write_data(value)?;
|
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_offsets.push(K::convert_offset(offset)?);
|
||||||
self.strip_byte_count.push(value.bytes().try_into()?);
|
self.strip_byte_count.push(byte_count.try_into()?);
|
||||||
|
|
||||||
self.strip_idx += 1;
|
self.strip_idx += 1;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -392,12 +451,19 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
|
|||||||
)
|
)
|
||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.encoder
|
||||||
|
.writer
|
||||||
|
.set_compression(self.compression.get_algorithm());
|
||||||
|
|
||||||
let mut idx = 0;
|
let mut idx = 0;
|
||||||
while self.next_strip_sample_count() > 0 {
|
while self.next_strip_sample_count() > 0 {
|
||||||
let sample_count = usize::try_from(self.next_strip_sample_count())?;
|
let sample_count = usize::try_from(self.next_strip_sample_count())?;
|
||||||
self.write_strip(&data[idx..idx + sample_count])?;
|
self.write_strip(&data[idx..idx + sample_count])?;
|
||||||
idx += sample_count;
|
idx += sample_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.encoder.writer.reset_compression();
|
||||||
self.finish()?;
|
self.finish()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -475,7 +541,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) {
|
fn drop(&mut self) {
|
||||||
if !self.dropped {
|
if !self.dropped {
|
||||||
let _ = self.finish_internal();
|
let _ = self.finish_internal();
|
||||||
|
|||||||
+147
-107
@@ -1,4 +1,4 @@
|
|||||||
use std::io::Write;
|
use std::{borrow::Cow, io::Write, slice::from_ref};
|
||||||
|
|
||||||
use crate::{bytecast, tags::Type, TiffError, TiffFormatError, TiffResult};
|
use crate::{bytecast, tags::Type, TiffError, TiffFormatError, TiffResult};
|
||||||
|
|
||||||
@@ -12,7 +12,18 @@ pub trait TiffValue {
|
|||||||
fn bytes(&self) -> usize {
|
fn bytes(&self) -> usize {
|
||||||
self.count() * usize::from(Self::BYTE_LEN)
|
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] {
|
impl TiffValue for [u8] {
|
||||||
@@ -23,9 +34,8 @@ impl TiffValue for [u8] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
writer.write_bytes(self)?;
|
Cow::Borrowed(self)
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,10 +47,8 @@ impl TiffValue for [i8] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
let slice = bytecast::i8_as_ne_bytes(self);
|
Cow::Borrowed(bytecast::i8_as_ne_bytes(self))
|
||||||
writer.write_bytes(slice)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,10 +60,8 @@ impl TiffValue for [u16] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
let slice = bytecast::u16_as_ne_bytes(self);
|
Cow::Borrowed(bytecast::u16_as_ne_bytes(self))
|
||||||
writer.write_bytes(slice)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,10 +73,8 @@ impl TiffValue for [i16] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
let slice = bytecast::i16_as_ne_bytes(self);
|
Cow::Borrowed(bytecast::i16_as_ne_bytes(self))
|
||||||
writer.write_bytes(slice)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,10 +86,8 @@ impl TiffValue for [u32] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
let slice = bytecast::u32_as_ne_bytes(self);
|
Cow::Borrowed(bytecast::u32_as_ne_bytes(self))
|
||||||
writer.write_bytes(slice)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,10 +99,8 @@ impl TiffValue for [i32] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
let slice = bytecast::i32_as_ne_bytes(self);
|
Cow::Borrowed(bytecast::i32_as_ne_bytes(self))
|
||||||
writer.write_bytes(slice)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,10 +112,8 @@ impl TiffValue for [u64] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
let slice = bytecast::u64_as_ne_bytes(self);
|
Cow::Borrowed(bytecast::u64_as_ne_bytes(self))
|
||||||
writer.write_bytes(slice)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,10 +125,8 @@ impl TiffValue for [i64] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
let slice = bytecast::i64_as_ne_bytes(self);
|
Cow::Borrowed(bytecast::i64_as_ne_bytes(self))
|
||||||
writer.write_bytes(slice)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,11 +138,9 @@ impl TiffValue for [f32] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
// We write using nativeedian so this sould be safe
|
// We write using native endian so this should be safe
|
||||||
let slice = bytecast::f32_as_ne_bytes(self);
|
Cow::Borrowed(bytecast::f32_as_ne_bytes(self))
|
||||||
writer.write_bytes(slice)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,75 +152,9 @@ impl TiffValue for [f64] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
// We write using nativeedian so this sould be safe
|
// We write using native endian so this should be safe
|
||||||
let slice = bytecast::f64_as_ne_bytes(self);
|
Cow::Borrowed(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(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,6 +170,10 @@ impl TiffValue for u8 {
|
|||||||
writer.write_u8(*self)?;
|
writer.write_u8(*self)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(from_ref(self))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TiffValue for i8 {
|
impl TiffValue for i8 {
|
||||||
@@ -256,6 +188,10 @@ impl TiffValue for i8 {
|
|||||||
writer.write_i8(*self)?;
|
writer.write_i8(*self)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(bytecast::i8_as_ne_bytes(from_ref(self)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TiffValue for u16 {
|
impl TiffValue for u16 {
|
||||||
@@ -270,6 +206,10 @@ impl TiffValue for u16 {
|
|||||||
writer.write_u16(*self)?;
|
writer.write_u16(*self)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(bytecast::u16_as_ne_bytes(from_ref(self)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TiffValue for i16 {
|
impl TiffValue for i16 {
|
||||||
@@ -284,6 +224,10 @@ impl TiffValue for i16 {
|
|||||||
writer.write_i16(*self)?;
|
writer.write_i16(*self)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(bytecast::i16_as_ne_bytes(from_ref(self)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TiffValue for u32 {
|
impl TiffValue for u32 {
|
||||||
@@ -298,6 +242,10 @@ impl TiffValue for u32 {
|
|||||||
writer.write_u32(*self)?;
|
writer.write_u32(*self)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(self)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TiffValue for i32 {
|
impl TiffValue for i32 {
|
||||||
@@ -312,6 +260,10 @@ impl TiffValue for i32 {
|
|||||||
writer.write_i32(*self)?;
|
writer.write_i32(*self)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(bytecast::i32_as_ne_bytes(from_ref(self)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TiffValue for u64 {
|
impl TiffValue for u64 {
|
||||||
@@ -326,6 +278,10 @@ impl TiffValue for u64 {
|
|||||||
writer.write_u64(*self)?;
|
writer.write_u64(*self)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(self)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TiffValue for i64 {
|
impl TiffValue for i64 {
|
||||||
@@ -340,6 +296,10 @@ impl TiffValue for i64 {
|
|||||||
writer.write_i64(*self)?;
|
writer.write_i64(*self)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(bytecast::i64_as_ne_bytes(from_ref(self)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TiffValue for f32 {
|
impl TiffValue for f32 {
|
||||||
@@ -354,6 +314,10 @@ impl TiffValue for f32 {
|
|||||||
writer.write_f32(*self)?;
|
writer.write_f32(*self)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(bytecast::f32_as_ne_bytes(from_ref(self)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TiffValue for f64 {
|
impl TiffValue for f64 {
|
||||||
@@ -368,6 +332,10 @@ impl TiffValue for f64 {
|
|||||||
writer.write_f64(*self)?;
|
writer.write_f64(*self)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(bytecast::f64_as_ne_bytes(from_ref(self)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TiffValue for Ifd {
|
impl TiffValue for Ifd {
|
||||||
@@ -382,6 +350,10 @@ impl TiffValue for Ifd {
|
|||||||
writer.write_u32(self.0)?;
|
writer.write_u32(self.0)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(&self.0)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TiffValue for Ifd8 {
|
impl TiffValue for Ifd8 {
|
||||||
@@ -396,6 +368,10 @@ impl TiffValue for Ifd8 {
|
|||||||
writer.write_u64(self.0)?;
|
writer.write_u64(self.0)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(&self.0)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TiffValue for Rational {
|
impl TiffValue for Rational {
|
||||||
@@ -411,6 +387,14 @@ impl TiffValue for Rational {
|
|||||||
writer.write_u32(self.d)?;
|
writer.write_u32(self.d)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Owned({
|
||||||
|
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()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TiffValue for SRational {
|
impl TiffValue for SRational {
|
||||||
@@ -426,6 +410,14 @@ impl TiffValue for SRational {
|
|||||||
writer.write_i32(self.d)?;
|
writer.write_i32(self.d)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Owned({
|
||||||
|
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()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TiffValue for str {
|
impl TiffValue for str {
|
||||||
@@ -445,6 +437,17 @@ impl TiffValue for str {
|
|||||||
Err(TiffError::FormatError(TiffFormatError::InvalidTag))
|
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 {
|
impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T {
|
||||||
@@ -458,8 +461,45 @@ impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T {
|
|||||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||||
(*self).write(writer)
|
(*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`
|
/// Type to represent tiff values of type `IFD`
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Ifd(pub u32);
|
pub struct Ifd(pub u32);
|
||||||
|
|||||||
+63
-23
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::encoder::compression::*;
|
||||||
use crate::error::TiffResult;
|
use crate::error::TiffResult;
|
||||||
use std::io::{self, Seek, SeekFrom, Write};
|
use std::io::{self, Seek, SeekFrom, Write};
|
||||||
|
|
||||||
@@ -42,87 +43,126 @@ pub fn write_bigtiff_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<
|
|||||||
pub struct TiffWriter<W> {
|
pub struct TiffWriter<W> {
|
||||||
writer: W,
|
writer: W,
|
||||||
offset: u64,
|
offset: u64,
|
||||||
|
byte_count: u64,
|
||||||
|
compressor: Compressor,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: Write> TiffWriter<W> {
|
impl<W: Write> TiffWriter<W> {
|
||||||
pub fn new(writer: W) -> Self {
|
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 {
|
pub fn offset(&self) -> u64 {
|
||||||
self.offset
|
self.offset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn last_written(&self) -> u64 {
|
||||||
|
self.byte_count
|
||||||
|
}
|
||||||
|
|
||||||
pub fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), io::Error> {
|
pub fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), io::Error> {
|
||||||
self.writer.write_all(bytes)?;
|
self.byte_count = self.compressor.write_to(&mut self.writer, bytes)?;
|
||||||
self.offset += bytes.len() as u64;
|
self.offset += self.byte_count;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_u8(&mut self, n: u8) -> Result<(), io::Error> {
|
pub fn write_u8(&mut self, n: u8) -> Result<(), io::Error> {
|
||||||
self.writer.write_all(&n.to_ne_bytes())?;
|
self.byte_count = self
|
||||||
self.offset += 1;
|
.compressor
|
||||||
|
.write_to(&mut self.writer, &n.to_ne_bytes())?;
|
||||||
|
self.offset += self.byte_count;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_i8(&mut self, n: i8) -> Result<(), io::Error> {
|
pub fn write_i8(&mut self, n: i8) -> Result<(), io::Error> {
|
||||||
self.writer.write_all(&n.to_ne_bytes())?;
|
self.byte_count = self
|
||||||
self.offset += 1;
|
.compressor
|
||||||
|
.write_to(&mut self.writer, &n.to_ne_bytes())?;
|
||||||
|
self.offset += self.byte_count;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_u16(&mut self, n: u16) -> Result<(), io::Error> {
|
pub fn write_u16(&mut self, n: u16) -> Result<(), io::Error> {
|
||||||
self.writer.write_all(&n.to_ne_bytes())?;
|
self.byte_count = self
|
||||||
self.offset += 2;
|
.compressor
|
||||||
|
.write_to(&mut self.writer, &n.to_ne_bytes())?;
|
||||||
|
self.offset += self.byte_count;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_i16(&mut self, n: i16) -> Result<(), io::Error> {
|
pub fn write_i16(&mut self, n: i16) -> Result<(), io::Error> {
|
||||||
self.writer.write_all(&n.to_ne_bytes())?;
|
self.byte_count = self
|
||||||
self.offset += 2;
|
.compressor
|
||||||
|
.write_to(&mut self.writer, &n.to_ne_bytes())?;
|
||||||
|
self.offset += self.byte_count;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_u32(&mut self, n: u32) -> Result<(), io::Error> {
|
pub fn write_u32(&mut self, n: u32) -> Result<(), io::Error> {
|
||||||
self.writer.write_all(&n.to_ne_bytes())?;
|
self.byte_count = self
|
||||||
self.offset += 4;
|
.compressor
|
||||||
|
.write_to(&mut self.writer, &n.to_ne_bytes())?;
|
||||||
|
self.offset += self.byte_count;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_i32(&mut self, n: i32) -> Result<(), io::Error> {
|
pub fn write_i32(&mut self, n: i32) -> Result<(), io::Error> {
|
||||||
self.writer.write_all(&n.to_ne_bytes())?;
|
self.byte_count = self
|
||||||
self.offset += 4;
|
.compressor
|
||||||
|
.write_to(&mut self.writer, &n.to_ne_bytes())?;
|
||||||
|
self.offset += self.byte_count;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_u64(&mut self, n: u64) -> Result<(), io::Error> {
|
pub fn write_u64(&mut self, n: u64) -> Result<(), io::Error> {
|
||||||
self.writer.write_all(&n.to_ne_bytes())?;
|
self.byte_count = self
|
||||||
self.offset += 8;
|
.compressor
|
||||||
|
.write_to(&mut self.writer, &n.to_ne_bytes())?;
|
||||||
|
self.offset += self.byte_count;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_i64(&mut self, n: i64) -> Result<(), io::Error> {
|
pub fn write_i64(&mut self, n: i64) -> Result<(), io::Error> {
|
||||||
self.writer.write_all(&n.to_ne_bytes())?;
|
self.byte_count = self
|
||||||
self.offset += 8;
|
.compressor
|
||||||
|
.write_to(&mut self.writer, &n.to_ne_bytes())?;
|
||||||
|
self.offset += self.byte_count;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_f32(&mut self, n: f32) -> Result<(), io::Error> {
|
pub fn write_f32(&mut self, n: f32) -> Result<(), io::Error> {
|
||||||
self.writer.write_all(&u32::to_ne_bytes(n.to_bits()))?;
|
self.byte_count = self
|
||||||
self.offset += 4;
|
.compressor
|
||||||
|
.write_to(&mut self.writer, &u32::to_ne_bytes(n.to_bits()))?;
|
||||||
|
self.offset += self.byte_count;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_f64(&mut self, n: f64) -> Result<(), io::Error> {
|
pub fn write_f64(&mut self, n: f64) -> Result<(), io::Error> {
|
||||||
self.writer.write_all(&u64::to_ne_bytes(n.to_bits()))?;
|
self.byte_count = self
|
||||||
self.offset += 8;
|
.compressor
|
||||||
|
.write_to(&mut self.writer, &u64::to_ne_bytes(n.to_bits()))?;
|
||||||
|
self.offset += self.byte_count;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ use crate::tags::{
|
|||||||
};
|
};
|
||||||
use crate::ColorType;
|
use crate::ColorType;
|
||||||
|
|
||||||
|
use crate::weezl::LzwError;
|
||||||
|
|
||||||
/// Tiff error kinds.
|
/// Tiff error kinds.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum TiffError {
|
pub enum TiffError {
|
||||||
@@ -288,5 +290,15 @@ impl From<std::num::TryFromIntError> for TiffError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<LzwError> for TiffError {
|
||||||
|
fn from(err: LzwError) -> TiffError {
|
||||||
|
match err {
|
||||||
|
LzwError::InvalidCode => TiffError::FormatError(TiffFormatError::Format(String::from(
|
||||||
|
"LZW compressed data corrupted",
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Result of an image decoding/encoding process
|
/// Result of an image decoding/encoding process
|
||||||
pub type TiffResult<T> = Result<T, TiffError>;
|
pub type TiffResult<T> = Result<T, TiffError>;
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
extern crate tiff;
|
||||||
|
|
||||||
|
use std::io::{Cursor, Seek, Write};
|
||||||
|
use tiff::{
|
||||||
|
decoder::{Decoder, DecodingResult},
|
||||||
|
encoder::{
|
||||||
|
colortype::{self, ColorType},
|
||||||
|
compression::*,
|
||||||
|
TiffEncoder, TiffValue,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
trait TestImage<const NUM_CHANNELS: usize>: From<Vec<<Self::Color as ColorType>::Inner>> {
|
||||||
|
const WIDTH: u32;
|
||||||
|
const HEIGHT: u32;
|
||||||
|
type Color: ColorType;
|
||||||
|
|
||||||
|
fn reference_data(&self) -> &[<Self::Color as ColorType>::Inner];
|
||||||
|
fn generate_pixel(x: u32, y: u32) -> [<Self::Color as ColorType>::Inner; NUM_CHANNELS];
|
||||||
|
|
||||||
|
fn compress<C: Compression, W: Write + Seek>(
|
||||||
|
&self,
|
||||||
|
encoder: &mut TiffEncoder<W>,
|
||||||
|
compression: C,
|
||||||
|
) where
|
||||||
|
[<Self::Color as ColorType>::Inner]: TiffValue,
|
||||||
|
{
|
||||||
|
let image = encoder
|
||||||
|
.new_image_with_compression::<Self::Color, C>(Self::WIDTH, Self::HEIGHT, compression)
|
||||||
|
.unwrap();
|
||||||
|
image.write_data(self.reference_data()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
data.extend(IntoIterator::into_iter(Self::generate_pixel(x, y)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::from(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TestImageColor(Vec<u16>);
|
||||||
|
|
||||||
|
impl From<Vec<u16>> for TestImageColor {
|
||||||
|
fn from(value: Vec<u16>) -> 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) -> [<Self::Color as ColorType>::Inner; 3] {
|
||||||
|
let val = (x + y) % <Self::Color as ColorType>::Inner::MAX as u32;
|
||||||
|
[val as <Self::Color as ColorType>::Inner; 3]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TestImageGrayscale(Vec<u8>);
|
||||||
|
|
||||||
|
impl From<Vec<u8>> for TestImageGrayscale {
|
||||||
|
fn from(value: Vec<u8>) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestImage<1> for TestImageGrayscale {
|
||||||
|
const WIDTH: u32 = 21;
|
||||||
|
const HEIGHT: u32 = 10;
|
||||||
|
type Color = colortype::Gray8;
|
||||||
|
|
||||||
|
fn reference_data(&self) -> &[u8] {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_pixel(x: u32, y: u32) -> [<Self::Color as ColorType>::Inner; 1] {
|
||||||
|
let val = (x + y) % <Self::Color as ColorType>::Inner::MAX as u32;
|
||||||
|
[val as <Self::Color as ColorType>::Inner]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_decode_with_compression<C: Compression + Clone>(compression: C) {
|
||||||
|
let mut data = Cursor::new(Vec::new());
|
||||||
|
|
||||||
|
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();
|
||||||
|
image_rgb.compress(&mut encoder, compression.clone());
|
||||||
|
image_grayscale.compress(&mut encoder, compression);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode tiff
|
||||||
|
data.set_position(0);
|
||||||
|
{
|
||||||
|
let mut decoder = Decoder::new(data).unwrap();
|
||||||
|
|
||||||
|
// Check the RGB image
|
||||||
|
assert_eq!(
|
||||||
|
match decoder.read_image() {
|
||||||
|
Ok(DecodingResult::U16(image_data)) => image_data,
|
||||||
|
unexpected => panic!("Descoding RGB failed: {:?}", unexpected),
|
||||||
|
},
|
||||||
|
image_rgb.reference_data()
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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_grayscale.reference_data()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn encode_decode_without_compression() {
|
||||||
|
encode_decode_with_compression(Uncompressed::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn encode_decode_with_packbits() {
|
||||||
|
encode_decode_with_compression(Packbits::default());
|
||||||
|
}
|
||||||
Reference in new issue
Block a user