Add horizontal predictor support (#240)

This commit is contained in:
Jean-Baptiste Skutnik authored and GitHub committed 2024-09-23 19:45:45 +01:00
1 parent de6d9cf131
commit e28ad564d9
7 files changed
+475 -77

No files matched your search

+3 -3
View File
@@ -265,21 +265,21 @@ fn rev_hpredict_nsamp(buf: &mut [u8], bit_depth: u8, samples: usize) {
9..=16 => { 9..=16 => {
for i in (samples * 2..buf.len()).step_by(2) { for i in (samples * 2..buf.len()).step_by(2) {
let v = u16::from_ne_bytes(buf[i..][..2].try_into().unwrap()); let v = u16::from_ne_bytes(buf[i..][..2].try_into().unwrap());
let p = u16::from_ne_bytes(buf[i - samples..][..2].try_into().unwrap()); let p = u16::from_ne_bytes(buf[i - 2 * samples..][..2].try_into().unwrap());
buf[i..][..2].copy_from_slice(&(v.wrapping_add(p)).to_ne_bytes()); buf[i..][..2].copy_from_slice(&(v.wrapping_add(p)).to_ne_bytes());
} }
} }
17..=32 => { 17..=32 => {
for i in (samples * 4..buf.len()).step_by(4) { for i in (samples * 4..buf.len()).step_by(4) {
let v = u32::from_ne_bytes(buf[i..][..4].try_into().unwrap()); let v = u32::from_ne_bytes(buf[i..][..4].try_into().unwrap());
let p = u32::from_ne_bytes(buf[i - samples..][..4].try_into().unwrap()); let p = u32::from_ne_bytes(buf[i - 4 * samples..][..4].try_into().unwrap());
buf[i..][..4].copy_from_slice(&(v.wrapping_add(p)).to_ne_bytes()); buf[i..][..4].copy_from_slice(&(v.wrapping_add(p)).to_ne_bytes());
} }
} }
33..=64 => { 33..=64 => {
for i in (samples * 8..buf.len()).step_by(8) { for i in (samples * 8..buf.len()).step_by(8) {
let v = u64::from_ne_bytes(buf[i..][..8].try_into().unwrap()); let v = u64::from_ne_bytes(buf[i..][..8].try_into().unwrap());
let p = u64::from_ne_bytes(buf[i - samples..][..8].try_into().unwrap()); let p = u64::from_ne_bytes(buf[i - 8 * samples..][..8].try_into().unwrap());
buf[i..][..8].copy_from_slice(&(v.wrapping_add(p)).to_ne_bytes()); buf[i..][..8].copy_from_slice(&(v.wrapping_add(p)).to_ne_bytes());
} }
} }
+98
View File
@@ -1,5 +1,31 @@
use crate::tags::{PhotometricInterpretation, SampleFormat}; use crate::tags::{PhotometricInterpretation, SampleFormat};
macro_rules! integer_horizontal_predict {
() => {
fn horizontal_predict(row: &[Self::Inner], result: &mut Vec<Self::Inner>) {
let sample_size = Self::SAMPLE_FORMAT.len();
if row.len() < sample_size {
debug_assert!(false);
return;
}
let (start, rest) = row.split_at(sample_size);
result.extend_from_slice(start);
if result.capacity() - result.len() < rest.len() {
return;
}
result.extend(
row.into_iter()
.zip(rest)
.map(|(prev, current)| current.wrapping_sub(*prev)),
);
}
};
}
/// Trait for different colortypes that can be encoded. /// Trait for different colortypes that can be encoded.
pub trait ColorType { pub trait ColorType {
/// The type of each sample of this colortype /// The type of each sample of this colortype
@@ -10,6 +36,8 @@ pub trait ColorType {
const BITS_PER_SAMPLE: &'static [u16]; const BITS_PER_SAMPLE: &'static [u16];
/// The value of the tiff tag `SampleFormat` /// The value of the tiff tag `SampleFormat`
const SAMPLE_FORMAT: &'static [SampleFormat]; const SAMPLE_FORMAT: &'static [SampleFormat];
fn horizontal_predict(row: &[Self::Inner], result: &mut Vec<Self::Inner>);
} }
pub struct Gray8; pub struct Gray8;
@@ -18,6 +46,8 @@ impl ColorType for Gray8 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[8]; const BITS_PER_SAMPLE: &'static [u16] = &[8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
integer_horizontal_predict!();
} }
pub struct GrayI8; pub struct GrayI8;
@@ -26,6 +56,8 @@ impl ColorType for GrayI8 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[8]; const BITS_PER_SAMPLE: &'static [u16] = &[8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
integer_horizontal_predict!();
} }
pub struct Gray16; pub struct Gray16;
@@ -34,6 +66,8 @@ impl ColorType for Gray16 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[16]; const BITS_PER_SAMPLE: &'static [u16] = &[16];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
integer_horizontal_predict!();
} }
pub struct GrayI16; pub struct GrayI16;
@@ -42,6 +76,8 @@ impl ColorType for GrayI16 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[16]; const BITS_PER_SAMPLE: &'static [u16] = &[16];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
integer_horizontal_predict!();
} }
pub struct Gray32; pub struct Gray32;
@@ -50,6 +86,8 @@ impl ColorType for Gray32 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[32]; const BITS_PER_SAMPLE: &'static [u16] = &[32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
integer_horizontal_predict!();
} }
pub struct GrayI32; pub struct GrayI32;
@@ -58,6 +96,8 @@ impl ColorType for GrayI32 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[32]; const BITS_PER_SAMPLE: &'static [u16] = &[32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
integer_horizontal_predict!();
} }
pub struct Gray32Float; pub struct Gray32Float;
@@ -66,6 +106,10 @@ impl ColorType for Gray32Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[32]; const BITS_PER_SAMPLE: &'static [u16] = &[32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
} }
pub struct Gray64; pub struct Gray64;
@@ -74,6 +118,8 @@ impl ColorType for Gray64 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[64]; const BITS_PER_SAMPLE: &'static [u16] = &[64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
integer_horizontal_predict!();
} }
pub struct GrayI64; pub struct GrayI64;
@@ -82,6 +128,8 @@ impl ColorType for GrayI64 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[64]; const BITS_PER_SAMPLE: &'static [u16] = &[64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
integer_horizontal_predict!();
} }
pub struct Gray64Float; pub struct Gray64Float;
@@ -90,6 +138,10 @@ impl ColorType for Gray64Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[64]; const BITS_PER_SAMPLE: &'static [u16] = &[64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
} }
pub struct RGB8; pub struct RGB8;
@@ -98,6 +150,8 @@ impl ColorType for RGB8 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8]; const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
integer_horizontal_predict!();
} }
pub struct RGB16; pub struct RGB16;
@@ -106,6 +160,8 @@ impl ColorType for RGB16 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16]; const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
integer_horizontal_predict!();
} }
pub struct RGB32; pub struct RGB32;
@@ -114,6 +170,8 @@ impl ColorType for RGB32 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32]; const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
integer_horizontal_predict!();
} }
pub struct RGB32Float; pub struct RGB32Float;
@@ -122,6 +180,9 @@ impl ColorType for RGB32Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32]; const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
} }
pub struct RGB64; pub struct RGB64;
@@ -130,6 +191,8 @@ impl ColorType for RGB64 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64]; const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
integer_horizontal_predict!();
} }
pub struct RGB64Float; pub struct RGB64Float;
@@ -138,6 +201,9 @@ impl ColorType for RGB64Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64]; const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
} }
pub struct RGBA8; pub struct RGBA8;
@@ -146,6 +212,8 @@ impl ColorType for RGBA8 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8]; const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
} }
pub struct RGBA16; pub struct RGBA16;
@@ -154,6 +222,8 @@ impl ColorType for RGBA16 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16]; const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
} }
pub struct RGBA32; pub struct RGBA32;
@@ -162,6 +232,8 @@ impl ColorType for RGBA32 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32]; const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
} }
pub struct RGBA32Float; pub struct RGBA32Float;
@@ -170,6 +242,9 @@ impl ColorType for RGBA32Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32]; const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
} }
pub struct RGBA64; pub struct RGBA64;
@@ -178,6 +253,8 @@ impl ColorType for RGBA64 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64]; const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
} }
pub struct RGBA64Float; pub struct RGBA64Float;
@@ -186,6 +263,9 @@ impl ColorType for RGBA64Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64]; const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
} }
pub struct CMYK8; pub struct CMYK8;
@@ -194,6 +274,8 @@ impl ColorType for CMYK8 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8]; const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
} }
pub struct CMYK16; pub struct CMYK16;
@@ -202,6 +284,8 @@ impl ColorType for CMYK16 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16]; const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
} }
pub struct CMYK32; pub struct CMYK32;
@@ -210,6 +294,8 @@ impl ColorType for CMYK32 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32]; const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
} }
pub struct CMYK32Float; pub struct CMYK32Float;
@@ -218,6 +304,10 @@ impl ColorType for CMYK32Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32]; const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
} }
pub struct CMYK64; pub struct CMYK64;
@@ -226,6 +316,8 @@ impl ColorType for CMYK64 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64]; const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
} }
pub struct CMYK64Float; pub struct CMYK64Float;
@@ -234,6 +326,10 @@ impl ColorType for CMYK64Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64]; const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
} }
pub struct YCbCr8; pub struct YCbCr8;
@@ -242,4 +338,6 @@ impl ColorType for YCbCr8 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::YCbCr; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::YCbCr;
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8]; const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
integer_horizontal_predict!();
} }
+109 -57
View File
@@ -10,8 +10,8 @@ use std::{
}; };
use crate::{ use crate::{
error::TiffResult, error::{TiffResult, UsageError},
tags::{CompressionMethod, ResolutionUnit, Tag}, tags::{CompressionMethod, ResolutionUnit, SampleFormat, Tag},
TiffError, TiffFormatError, TiffError, TiffFormatError,
}; };
@@ -21,9 +21,55 @@ mod tiff_value;
mod writer; mod writer;
use self::colortype::*; use self::colortype::*;
use self::compression::Compression as Comp;
use self::compression::*; use self::compression::*;
use self::writer::*; use self::writer::*;
/// Type of prediction to prepare the image with.
///
/// Image data can be very unpredictable, and thus very hard to compress. Predictors are simple
/// passes ran over the image data to prepare it for compression. This is mostly used for LZW
/// compression, where using [Predictor::Horizontal] we see a 35% improvement in compression
/// ratio over the unpredicted compression !
///
/// [Predictor::FloatingPoint] is currently not supported.
pub type Predictor = crate::tags::Predictor;
pub type DeflateLevel = compression::DeflateLevel;
#[derive(Clone, Copy, PartialEq)]
pub enum Compression {
Uncompressed,
Lzw,
Deflate(DeflateLevel),
Packbits,
}
impl Default for Compression {
fn default() -> Self {
Self::Uncompressed
}
}
impl Compression {
fn tag(&self) -> CompressionMethod {
match self {
Compression::Uncompressed => CompressionMethod::None,
Compression::Lzw => CompressionMethod::LZW,
Compression::Deflate(_) => CompressionMethod::Deflate,
Compression::Packbits => CompressionMethod::PackBits,
}
}
fn get_algorithm(&self) -> Compressor {
match self {
Compression::Uncompressed => compression::Uncompressed {}.get_algorithm(),
Compression::Lzw => compression::Lzw {}.get_algorithm(),
Compression::Deflate(level) => compression::Deflate::with_level(*level).get_algorithm(),
Compression::Packbits => compression::Packbits {}.get_algorithm(),
}
}
}
/// Encoder for Tiff and BigTiff files. /// Encoder for Tiff and BigTiff files.
/// ///
/// With this type you can get a `DirectoryEncoder` or a `ImageEncoder` /// With this type you can get a `DirectoryEncoder` or a `ImageEncoder`
@@ -52,6 +98,8 @@ use self::writer::*;
pub struct TiffEncoder<W, K: TiffKind = TiffKindStandard> { pub struct TiffEncoder<W, K: TiffKind = TiffKindStandard> {
writer: TiffWriter<W>, writer: TiffWriter<W>,
kind: PhantomData<K>, kind: PhantomData<K>,
predictor: Predictor,
compression: Compression,
} }
/// Constructor functions to create standard Tiff files. /// Constructor functions to create standard Tiff files.
@@ -83,6 +131,8 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
let mut encoder = TiffEncoder { let mut encoder = TiffEncoder {
writer: TiffWriter::new(writer), writer: TiffWriter::new(writer),
kind: PhantomData, kind: PhantomData,
predictor: Predictor::None,
compression: Compression::Uncompressed,
}; };
K::write_header(&mut encoder.writer)?; K::write_header(&mut encoder.writer)?;
@@ -90,6 +140,23 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
Ok(encoder) Ok(encoder)
} }
/// Set the predictor to use
///
/// A predictor is used to simplify the file before writing it. This is very
/// useful when writing a file compressed using LZW as it can improve efficiency
pub fn with_predictor(mut self, predictor: Predictor) -> Self {
self.predictor = predictor;
self
}
/// Set the compression method to use
pub fn with_compression(mut self, compression: Compression) -> Self {
self.compression = compression;
self
}
/// Create a [`DirectoryEncoder`] to encode an ifd directory. /// Create a [`DirectoryEncoder`] to encode an ifd directory.
pub fn new_directory(&mut self) -> TiffResult<DirectoryEncoder<W, K>> { pub fn new_directory(&mut self) -> TiffResult<DirectoryEncoder<W, K>> {
DirectoryEncoder::new(&mut self.writer) DirectoryEncoder::new(&mut self.writer)
@@ -100,20 +167,9 @@ 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, Uncompressed>> { ) -> TiffResult<ImageEncoder<W, C, K>> {
let encoder = DirectoryEncoder::new(&mut self.writer)?; let encoder = DirectoryEncoder::new(&mut self.writer)?;
ImageEncoder::new(encoder, width, height) ImageEncoder::new(encoder, width, height, self.compression, self.predictor)
}
/// 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.
@@ -127,24 +183,8 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
[C::Inner]: TiffValue, [C::Inner]: TiffValue,
{ {
let encoder = DirectoryEncoder::new(&mut self.writer)?; let encoder = DirectoryEncoder::new(&mut self.writer)?;
let image: ImageEncoder<W, C, K> = ImageEncoder::new(encoder, width, height)?; let image: ImageEncoder<W, C, K> =
image.write_data(data) ImageEncoder::new(encoder, width, height, self.compression, self.predictor)?;
}
/// 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) image.write_data(data)
} }
} }
@@ -312,13 +352,7 @@ 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< pub struct ImageEncoder<'a, W: 'a + Write + Seek, C: ColorType, K: TiffKind> {
'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,
@@ -329,25 +363,30 @@ pub struct ImageEncoder<
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, compression: Compression,
predictor: Predictor,
_phantom: ::std::marker::PhantomData<C>, _phantom: ::std::marker::PhantomData<C>,
} }
impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression> impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T, K> {
ImageEncoder<'a, W, T, K, D> fn sanity_check(compression: Compression, predictor: Predictor) -> TiffResult<()> {
{ match (predictor, compression, T::SAMPLE_FORMAT[0]) {
fn new(encoder: DirectoryEncoder<'a, W, K>, width: u32, height: u32) -> TiffResult<Self> (Predictor::Horizontal, _, SampleFormat::IEEEFP | SampleFormat::Void) => {
where Err(TiffError::UsageError(UsageError::PredictorIncompatible))
D: Default, }
{ (Predictor::FloatingPoint, _, _) => {
Self::with_compression(encoder, width, height, D::default()) Err(TiffError::UsageError(UsageError::PredictorUnavailable))
}
_ => Ok(()),
}
} }
fn with_compression( fn new(
mut encoder: DirectoryEncoder<'a, W, K>, mut encoder: DirectoryEncoder<'a, W, K>,
width: u32, width: u32,
height: u32, height: u32,
compression: D, compression: Compression,
predictor: Predictor,
) -> TiffResult<Self> { ) -> TiffResult<Self> {
if width == 0 || height == 0 { if width == 0 || height == 0 {
return Err(TiffError::FormatError(TiffFormatError::InvalidDimensions( return Err(TiffError::FormatError(TiffFormatError::InvalidDimensions(
@@ -355,13 +394,15 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression>
))); )));
} }
Self::sanity_check(compression, predictor)?;
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);
// Limit the strip size to prevent potential memory and security issues. // Limit the strip size to prevent potential memory and security issues.
// Also keep the multiple strip handling 'oiled' // Also keep the multiple strip handling 'oiled'
let rows_per_strip = { let rows_per_strip = {
match D::COMPRESSION_METHOD { match compression.tag() {
CompressionMethod::PackBits => 1, // Each row must be packed separately. Do not compress across row boundaries CompressionMethod::PackBits => 1, // Each row must be packed separately. Do not compress across row boundaries
_ => (1_000_000 + row_bytes - 1) / row_bytes, _ => (1_000_000 + row_bytes - 1) / row_bytes,
} }
@@ -371,7 +412,8 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression>
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, D::COMPRESSION_METHOD.to_u16())?; encoder.write_tag(Tag::Compression, compression.tag().to_u16())?;
encoder.write_tag(Tag::Predictor, predictor.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();
@@ -400,6 +442,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression>
strip_byte_count: Vec::new(), strip_byte_count: Vec::new(),
dropped: false, dropped: false,
compression, compression,
predictor,
_phantom: ::std::marker::PhantomData, _phantom: ::std::marker::PhantomData,
}) })
} }
@@ -432,7 +475,18 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression>
} }
// Write the (possible compressed) data to the encoder. // Write the (possible compressed) data to the encoder.
let offset = self.encoder.write_data(value)?; let offset = match self.predictor {
Predictor::None => self.encoder.write_data(value)?,
Predictor::Horizontal => {
let mut row_result = Vec::with_capacity(value.len());
for row in value.chunks_exact(self.row_samples as usize) {
T::horizontal_predict(row, &mut row_result);
}
self.encoder.write_data(row_result.as_slice())?
}
_ => unimplemented!(),
};
let byte_count = self.encoder.last_written() as usize; 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)?);
@@ -552,9 +606,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression>
} }
} }
impl<'a, W: Write + Seek, C: ColorType, K: TiffKind, D: Compression> Drop impl<'a, W: Write + Seek, C: ColorType, K: TiffKind> Drop for ImageEncoder<'a, W, C, K> {
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();
+12
View File
@@ -236,6 +236,9 @@ impl fmt::Display for TiffUnsupportedError {
pub enum UsageError { pub enum UsageError {
InvalidChunkType(ChunkType, ChunkType), InvalidChunkType(ChunkType, ChunkType),
InvalidChunkIndex(u32), InvalidChunkIndex(u32),
PredictorCompressionMismatch,
PredictorIncompatible,
PredictorUnavailable,
} }
impl fmt::Display for UsageError { impl fmt::Display for UsageError {
@@ -250,6 +253,15 @@ impl fmt::Display for UsageError {
) )
} }
InvalidChunkIndex(index) => write!(fmt, "Image chunk index ({}) requested.", index), InvalidChunkIndex(index) => write!(fmt, "Image chunk index ({}) requested.", index),
PredictorCompressionMismatch => write!(
fmt,
"The requested predictor is not compatible with the requested compression"
),
PredictorIncompatible => write!(
fmt,
"The requested predictor is not compatible with the image's format"
),
PredictorUnavailable => write!(fmt, "The requested predictor is not available"),
} }
} }
} }
+6
View File
@@ -209,8 +209,14 @@ pub enum PlanarConfiguration(u16) {
tags! { tags! {
pub enum Predictor(u16) { pub enum Predictor(u16) {
/// No changes were made to the data
None = 1, None = 1,
/// The images' rows were processed to contain the difference of each pixel from the previous one.
///
/// This means that instead of having in order `[r1, g1. b1, r2, g2 ...]` you will find
/// `[r1, g1, b1, r2-r1, g2-g1, b2-b1, r3-r2, g3-g2, ...]`
Horizontal = 2, Horizontal = 2,
/// Not currently supported
FloatingPoint = 3, FloatingPoint = 3,
} }
} }
+16 -17
View File
@@ -6,7 +6,7 @@ use tiff::{
encoder::{ encoder::{
colortype::{self, ColorType}, colortype::{self, ColorType},
compression::*, compression::*,
TiffEncoder, TiffValue, Compression, TiffEncoder, TiffValue,
}, },
}; };
@@ -18,15 +18,12 @@ trait TestImage<const NUM_CHANNELS: usize>: From<Vec<<Self::Color as ColorType>:
fn reference_data(&self) -> &[<Self::Color as ColorType>::Inner]; fn reference_data(&self) -> &[<Self::Color as ColorType>::Inner];
fn generate_pixel(x: u32, y: u32) -> [<Self::Color as ColorType>::Inner; NUM_CHANNELS]; fn generate_pixel(x: u32, y: u32) -> [<Self::Color as ColorType>::Inner; NUM_CHANNELS];
fn compress<C: Compression, W: Write + Seek>( fn compress<W: Write + Seek>(&self, encoder: &mut TiffEncoder<W>)
&self, where
encoder: &mut TiffEncoder<W>,
compression: C,
) where
[<Self::Color as ColorType>::Inner]: TiffValue, [<Self::Color as ColorType>::Inner]: TiffValue,
{ {
let image = encoder let image = encoder
.new_image_with_compression::<Self::Color, C>(Self::WIDTH, Self::HEIGHT, compression) .new_image::<Self::Color>(Self::WIDTH, Self::HEIGHT)
.unwrap(); .unwrap();
image.write_data(self.reference_data()).unwrap(); image.write_data(self.reference_data()).unwrap();
} }
@@ -94,7 +91,7 @@ impl TestImage<1> for TestImageGrayscale {
} }
} }
fn encode_decode_with_compression<C: Compression + Clone>(compression: C) { fn encode_decode_with_compression(compression: Compression) {
let mut data = Cursor::new(Vec::new()); let mut data = Cursor::new(Vec::new());
let image_rgb = TestImageColor::generate(); let image_rgb = TestImageColor::generate();
@@ -103,9 +100,11 @@ fn encode_decode_with_compression<C: Compression + Clone>(compression: C) {
// Encode tiff with compression // Encode tiff with compression
{ {
// Create a multipage image with 2 images // Create a multipage image with 2 images
let mut encoder = TiffEncoder::new(&mut data).unwrap(); let mut encoder = TiffEncoder::new(&mut data)
image_rgb.compress(&mut encoder, compression.clone()); .unwrap()
image_grayscale.compress(&mut encoder, compression); .with_compression(compression);
image_rgb.compress(&mut encoder);
image_grayscale.compress(&mut encoder);
} }
// Decode tiff // Decode tiff
@@ -136,22 +135,22 @@ fn encode_decode_with_compression<C: Compression + Clone>(compression: C) {
#[test] #[test]
fn encode_decode_without_compression() { fn encode_decode_without_compression() {
encode_decode_with_compression(Uncompressed::default()); encode_decode_with_compression(Compression::Uncompressed);
} }
#[test] #[test]
fn encode_decode_with_lzw() { fn encode_decode_with_lzw() {
encode_decode_with_compression(Lzw::default()); encode_decode_with_compression(Compression::Lzw);
} }
#[test] #[test]
fn encode_decode_with_deflate() { fn encode_decode_with_deflate() {
encode_decode_with_compression(Deflate::with_level(DeflateLevel::Fast)); encode_decode_with_compression(Compression::Deflate(DeflateLevel::Fast));
encode_decode_with_compression(Deflate::with_level(DeflateLevel::Balanced)); encode_decode_with_compression(Compression::Deflate(DeflateLevel::Balanced));
encode_decode_with_compression(Deflate::with_level(DeflateLevel::Best)); encode_decode_with_compression(Compression::Deflate(DeflateLevel::Best));
} }
#[test] #[test]
fn encode_decode_with_packbits() { fn encode_decode_with_packbits() {
encode_decode_with_compression(Packbits::default()); encode_decode_with_compression(Compression::Packbits);
} }
+231
View File
@@ -0,0 +1,231 @@
extern crate tiff;
use tiff::decoder::{Decoder, DecodingResult};
use tiff::encoder::{colortype, Compression, Predictor, TiffEncoder};
use tiff::ColorType;
use std::fs::File;
use std::io::{Cursor, Seek, SeekFrom};
use std::path::PathBuf;
const TEST_IMAGE_DIR: &str = "./tests/images/";
macro_rules! test_predict {
($name:ident, $buffer:ident, $buffer_ty:ty) => {
fn $name<C: colortype::ColorType<Inner = $buffer_ty>>(
file: &str,
expected_type: ColorType,
) {
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
let file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(file).expect("Cannot create decoder!");
assert_eq!(decoder.colortype().unwrap(), expected_type);
let image_data = match decoder.read_image().unwrap() {
DecodingResult::$buffer(res) => res,
_ => panic!("Wrong data type"),
};
let mut predicted = Vec::with_capacity(image_data.len());
C::horizontal_predict(&image_data, &mut predicted);
let sample_size = C::SAMPLE_FORMAT.len();
(0..sample_size).for_each(|i| {
assert_eq!(predicted[i], image_data[i]);
});
(sample_size..image_data.len()).for_each(|i| {
predicted[i] = predicted[i].wrapping_add(predicted[i - sample_size]);
assert_eq!(predicted[i], image_data[i]);
});
}
};
}
test_predict!(test_u8_predict, U8, u8);
test_predict!(test_i8_predict, I8, i8);
test_predict!(test_u16_predict, U16, u16);
test_predict!(test_i16_predict, I16, i16);
test_predict!(test_u32_predict, U32, u32);
test_predict!(test_u64_predict, U64, u64);
#[test]
fn test_gray_u8_predict() {
test_u8_predict::<colortype::Gray8>("minisblack-1c-8b.tiff", ColorType::Gray(8));
}
#[test]
fn test_gray_i8_predict() {
test_i8_predict::<colortype::GrayI8>("minisblack-1c-i8b.tiff", ColorType::Gray(8));
}
#[test]
fn test_rgb_u8_predict() {
test_u8_predict::<colortype::RGB8>("rgb-3c-8b.tiff", ColorType::RGB(8));
}
#[test]
fn test_cmyk_u8_predict() {
test_u8_predict::<colortype::CMYK8>("cmyk-3c-8b.tiff", ColorType::CMYK(8));
}
#[test]
fn test_gray_u16_predict() {
test_u16_predict::<colortype::Gray16>("minisblack-1c-16b.tiff", ColorType::Gray(16));
}
#[test]
fn test_gray_i16_predict() {
test_i16_predict::<colortype::GrayI16>("minisblack-1c-i16b.tiff", ColorType::Gray(16));
}
#[test]
fn test_rgb_u16_predict() {
test_u16_predict::<colortype::RGB16>("rgb-3c-16b.tiff", ColorType::RGB(16));
}
#[test]
fn test_cmyk_u16_predict() {
test_u16_predict::<colortype::CMYK16>("cmyk-3c-16b.tiff", ColorType::CMYK(16));
}
#[test]
fn test_gray_u32_predict() {
test_u32_predict::<colortype::Gray32>("gradient-1c-32b.tiff", ColorType::Gray(32));
}
#[test]
fn test_rgb_u32_predict() {
test_u32_predict::<colortype::RGB32>("gradient-3c-32b.tiff", ColorType::RGB(32));
}
#[test]
fn test_gray_u64_predict() {
test_u64_predict::<colortype::Gray64>("gradient-1c-64b.tiff", ColorType::Gray(64));
}
#[test]
fn test_rgb_u64_predict() {
test_u64_predict::<colortype::RGB64>("gradient-3c-64b.tiff", ColorType::RGB(64));
}
#[test]
fn test_ycbcr_u8_predict() {
test_u8_predict::<colortype::YCbCr8>("tiled-jpeg-ycbcr.tif", ColorType::YCbCr(8));
}
macro_rules! test_predict_roundtrip {
($name:ident, $buffer:ident, $buffer_ty:ty) => {
fn $name<C: colortype::ColorType<Inner = $buffer_ty>>(
file: &str,
expected_type: ColorType,
) {
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(decoder.colortype().unwrap(), expected_type);
let image_data = match decoder.read_image().unwrap() {
DecodingResult::$buffer(res) => res,
_ => panic!("Wrong data type"),
};
let mut file = Cursor::new(Vec::new());
{
let mut tiff = TiffEncoder::new(&mut file)
.unwrap()
.with_predictor(Predictor::Horizontal);
let (width, height) = decoder.dimensions().unwrap();
tiff.write_image::<C>(width, height, &image_data).unwrap();
}
file.seek(SeekFrom::Start(0)).unwrap();
{
let mut decoder = Decoder::new(&mut file).unwrap();
if let DecodingResult::$buffer(img_res) =
decoder.read_image().expect("Decoding image failed")
{
assert_eq!(image_data, img_res);
} else {
panic!("Wrong data type");
}
}
}
};
}
test_predict_roundtrip!(test_u8_predict_roundtrip, U8, u8);
test_predict_roundtrip!(test_i8_predict_roundtrip, I8, i8);
test_predict_roundtrip!(test_u16_predict_roundtrip, U16, u16);
test_predict_roundtrip!(test_i16_predict_roundtrip, I16, i16);
test_predict_roundtrip!(test_u32_predict_roundtrip, U32, u32);
test_predict_roundtrip!(test_u64_predict_roundtrip, U64, u64);
#[test]
fn test_gray_u8_predict_roundtrip() {
test_u8_predict_roundtrip::<colortype::Gray8>("minisblack-1c-8b.tiff", ColorType::Gray(8));
}
#[test]
fn test_gray_i8_predict_roundtrip() {
test_i8_predict_roundtrip::<colortype::GrayI8>("minisblack-1c-i8b.tiff", ColorType::Gray(8));
}
#[test]
fn test_rgb_u8_predict_roundtrip() {
test_u8_predict_roundtrip::<colortype::RGB8>("rgb-3c-8b.tiff", ColorType::RGB(8));
}
#[test]
fn test_cmyk_u8_predict_roundtrip() {
test_u8_predict_roundtrip::<colortype::CMYK8>("cmyk-3c-8b.tiff", ColorType::CMYK(8));
}
#[test]
fn test_gray_u16_predict_roundtrip() {
test_u16_predict_roundtrip::<colortype::Gray16>("minisblack-1c-16b.tiff", ColorType::Gray(16));
}
#[test]
fn test_gray_i16_predict_roundtrip() {
test_i16_predict_roundtrip::<colortype::GrayI16>(
"minisblack-1c-i16b.tiff",
ColorType::Gray(16),
);
}
#[test]
fn test_rgb_u16_predict_roundtrip() {
test_u16_predict_roundtrip::<colortype::RGB16>("rgb-3c-16b.tiff", ColorType::RGB(16));
}
#[test]
fn test_cmyk_u16_predict_roundtrip() {
test_u16_predict_roundtrip::<colortype::CMYK16>("cmyk-3c-16b.tiff", ColorType::CMYK(16));
}
#[test]
fn test_gray_u32_predict_roundtrip() {
test_u32_predict_roundtrip::<colortype::Gray32>("gradient-1c-32b.tiff", ColorType::Gray(32));
}
#[test]
fn test_rgb_u32_predict_roundtrip() {
test_u32_predict_roundtrip::<colortype::RGB32>("gradient-3c-32b.tiff", ColorType::RGB(32));
}
#[test]
fn test_gray_u64_predict_roundtrip() {
test_u64_predict_roundtrip::<colortype::Gray64>("gradient-1c-64b.tiff", ColorType::Gray(64));
}
#[test]
fn test_rgb_u64_predict_roundtrip() {
test_u64_predict_roundtrip::<colortype::RGB64>("gradient-3c-64b.tiff", ColorType::RGB(64));
}
#[test]
fn test_ycbcr_u8_predict_roundtrip() {
test_u8_predict_roundtrip::<colortype::YCbCr8>("tiled-jpeg-ycbcr.tif", ColorType::YCbCr(8));
}