Add horizontal predictor support (#240)
This commit is contained in:
1 parent
de6d9cf131
commit
e28ad564d9
7 files changed
+475
-77
No files matched your search
+3
-3
@@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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();
|
||||||
|
|||||||
@@ -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"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
Reference in new issue
Block a user