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 => {
|
||||
for i in (samples * 2..buf.len()).step_by(2) {
|
||||
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());
|
||||
}
|
||||
}
|
||||
17..=32 => {
|
||||
for i in (samples * 4..buf.len()).step_by(4) {
|
||||
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());
|
||||
}
|
||||
}
|
||||
33..=64 => {
|
||||
for i in (samples * 8..buf.len()).step_by(8) {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
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.
|
||||
pub trait ColorType {
|
||||
/// The type of each sample of this colortype
|
||||
@@ -10,6 +36,8 @@ pub trait ColorType {
|
||||
const BITS_PER_SAMPLE: &'static [u16];
|
||||
/// The value of the tiff tag `SampleFormat`
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat];
|
||||
|
||||
fn horizontal_predict(row: &[Self::Inner], result: &mut Vec<Self::Inner>);
|
||||
}
|
||||
|
||||
pub struct Gray8;
|
||||
@@ -18,6 +46,8 @@ impl ColorType for Gray8 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[8];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct GrayI8;
|
||||
@@ -26,6 +56,8 @@ impl ColorType for GrayI8 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[8];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct Gray16;
|
||||
@@ -34,6 +66,8 @@ impl ColorType for Gray16 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[16];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct GrayI16;
|
||||
@@ -42,6 +76,8 @@ impl ColorType for GrayI16 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[16];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct Gray32;
|
||||
@@ -50,6 +86,8 @@ impl ColorType for Gray32 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[32];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct GrayI32;
|
||||
@@ -58,6 +96,8 @@ impl ColorType for GrayI32 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[32];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct Gray32Float;
|
||||
@@ -66,6 +106,10 @@ impl ColorType for Gray32Float {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[32];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP];
|
||||
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Gray64;
|
||||
@@ -74,6 +118,8 @@ impl ColorType for Gray64 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct GrayI64;
|
||||
@@ -82,6 +128,8 @@ impl ColorType for GrayI64 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct Gray64Float;
|
||||
@@ -90,6 +138,10 @@ impl ColorType for Gray64Float {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP];
|
||||
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RGB8;
|
||||
@@ -98,6 +150,8 @@ impl ColorType for RGB8 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct RGB16;
|
||||
@@ -106,6 +160,8 @@ impl ColorType for RGB16 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct RGB32;
|
||||
@@ -114,6 +170,8 @@ impl ColorType for RGB32 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct RGB32Float;
|
||||
@@ -122,6 +180,9 @@ impl ColorType for RGB32Float {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3];
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RGB64;
|
||||
@@ -130,6 +191,8 @@ impl ColorType for RGB64 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct RGB64Float;
|
||||
@@ -138,6 +201,9 @@ impl ColorType for RGB64Float {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3];
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RGBA8;
|
||||
@@ -146,6 +212,8 @@ impl ColorType for RGBA8 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct RGBA16;
|
||||
@@ -154,6 +222,8 @@ impl ColorType for RGBA16 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct RGBA32;
|
||||
@@ -162,6 +232,8 @@ impl ColorType for RGBA32 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct RGBA32Float;
|
||||
@@ -170,6 +242,9 @@ impl ColorType for RGBA32Float {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RGBA64;
|
||||
@@ -178,6 +253,8 @@ impl ColorType for RGBA64 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct RGBA64Float;
|
||||
@@ -186,6 +263,9 @@ impl ColorType for RGBA64Float {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CMYK8;
|
||||
@@ -194,6 +274,8 @@ impl ColorType for CMYK8 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct CMYK16;
|
||||
@@ -202,6 +284,8 @@ impl ColorType for CMYK16 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct CMYK32;
|
||||
@@ -210,6 +294,8 @@ impl ColorType for CMYK32 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct CMYK32Float;
|
||||
@@ -218,6 +304,10 @@ impl ColorType for CMYK32Float {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CMYK64;
|
||||
@@ -226,6 +316,8 @@ impl ColorType for CMYK64 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct CMYK64Float;
|
||||
@@ -234,6 +326,10 @@ impl ColorType for CMYK64Float {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct YCbCr8;
|
||||
@@ -242,4 +338,6 @@ impl ColorType for YCbCr8 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::YCbCr;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
+109
-57
@@ -10,8 +10,8 @@ use std::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::TiffResult,
|
||||
tags::{CompressionMethod, ResolutionUnit, Tag},
|
||||
error::{TiffResult, UsageError},
|
||||
tags::{CompressionMethod, ResolutionUnit, SampleFormat, Tag},
|
||||
TiffError, TiffFormatError,
|
||||
};
|
||||
|
||||
@@ -21,9 +21,55 @@ mod tiff_value;
|
||||
mod writer;
|
||||
|
||||
use self::colortype::*;
|
||||
use self::compression::Compression as Comp;
|
||||
use self::compression::*;
|
||||
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.
|
||||
///
|
||||
/// 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> {
|
||||
writer: TiffWriter<W>,
|
||||
kind: PhantomData<K>,
|
||||
predictor: Predictor,
|
||||
compression: Compression,
|
||||
}
|
||||
|
||||
/// Constructor functions to create standard Tiff files.
|
||||
@@ -83,6 +131,8 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
||||
let mut encoder = TiffEncoder {
|
||||
writer: TiffWriter::new(writer),
|
||||
kind: PhantomData,
|
||||
predictor: Predictor::None,
|
||||
compression: Compression::Uncompressed,
|
||||
};
|
||||
|
||||
K::write_header(&mut encoder.writer)?;
|
||||
@@ -90,6 +140,23 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
||||
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.
|
||||
pub fn new_directory(&mut self) -> TiffResult<DirectoryEncoder<W, K>> {
|
||||
DirectoryEncoder::new(&mut self.writer)
|
||||
@@ -100,20 +167,9 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> TiffResult<ImageEncoder<W, C, K, Uncompressed>> {
|
||||
) -> TiffResult<ImageEncoder<W, C, K>> {
|
||||
let encoder = DirectoryEncoder::new(&mut self.writer)?;
|
||||
ImageEncoder::new(encoder, width, height)
|
||||
}
|
||||
|
||||
/// Create an [`ImageEncoder`] to encode an image one slice at a time.
|
||||
pub fn new_image_with_compression<C: ColorType, D: Compression>(
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
compression: D,
|
||||
) -> TiffResult<ImageEncoder<W, C, K, D>> {
|
||||
let encoder = DirectoryEncoder::new(&mut self.writer)?;
|
||||
ImageEncoder::with_compression(encoder, width, height, compression)
|
||||
ImageEncoder::new(encoder, width, height, self.compression, self.predictor)
|
||||
}
|
||||
|
||||
/// 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,
|
||||
{
|
||||
let encoder = DirectoryEncoder::new(&mut self.writer)?;
|
||||
let image: ImageEncoder<W, C, K> = ImageEncoder::new(encoder, width, height)?;
|
||||
image.write_data(data)
|
||||
}
|
||||
|
||||
/// Convenience function to write an entire image from memory with a given compression.
|
||||
pub fn write_image_with_compression<C: ColorType, D: Compression>(
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
compression: D,
|
||||
data: &[C::Inner],
|
||||
) -> TiffResult<()>
|
||||
where
|
||||
[C::Inner]: TiffValue,
|
||||
{
|
||||
let encoder = DirectoryEncoder::new(&mut self.writer)?;
|
||||
let image: ImageEncoder<W, C, K, D> =
|
||||
ImageEncoder::with_compression(encoder, width, height, compression)?;
|
||||
let image: ImageEncoder<W, C, K> =
|
||||
ImageEncoder::new(encoder, width, height, self.compression, self.predictor)?;
|
||||
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
|
||||
pub struct ImageEncoder<
|
||||
'a,
|
||||
W: 'a + Write + Seek,
|
||||
C: ColorType,
|
||||
K: TiffKind,
|
||||
D: Compression = Uncompressed,
|
||||
> {
|
||||
pub struct ImageEncoder<'a, W: 'a + Write + Seek, C: ColorType, K: TiffKind> {
|
||||
encoder: DirectoryEncoder<'a, W, K>,
|
||||
strip_idx: u64,
|
||||
strip_count: u64,
|
||||
@@ -329,25 +363,30 @@ pub struct ImageEncoder<
|
||||
strip_offsets: Vec<K::OffsetType>,
|
||||
strip_byte_count: Vec<K::OffsetType>,
|
||||
dropped: bool,
|
||||
compression: D,
|
||||
compression: Compression,
|
||||
predictor: Predictor,
|
||||
_phantom: ::std::marker::PhantomData<C>,
|
||||
}
|
||||
|
||||
impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression>
|
||||
ImageEncoder<'a, W, T, K, D>
|
||||
{
|
||||
fn new(encoder: DirectoryEncoder<'a, W, K>, width: u32, height: u32) -> TiffResult<Self>
|
||||
where
|
||||
D: Default,
|
||||
{
|
||||
Self::with_compression(encoder, width, height, D::default())
|
||||
impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T, K> {
|
||||
fn sanity_check(compression: Compression, predictor: Predictor) -> TiffResult<()> {
|
||||
match (predictor, compression, T::SAMPLE_FORMAT[0]) {
|
||||
(Predictor::Horizontal, _, SampleFormat::IEEEFP | SampleFormat::Void) => {
|
||||
Err(TiffError::UsageError(UsageError::PredictorIncompatible))
|
||||
}
|
||||
(Predictor::FloatingPoint, _, _) => {
|
||||
Err(TiffError::UsageError(UsageError::PredictorUnavailable))
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn with_compression(
|
||||
fn new(
|
||||
mut encoder: DirectoryEncoder<'a, W, K>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
compression: D,
|
||||
compression: Compression,
|
||||
predictor: Predictor,
|
||||
) -> TiffResult<Self> {
|
||||
if width == 0 || height == 0 {
|
||||
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_bytes = row_samples * u64::from(<T::Inner>::BYTE_LEN);
|
||||
|
||||
// Limit the strip size to prevent potential memory and security issues.
|
||||
// Also keep the multiple strip handling 'oiled'
|
||||
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
|
||||
_ => (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::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)?;
|
||||
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(),
|
||||
dropped: false,
|
||||
compression,
|
||||
predictor,
|
||||
_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.
|
||||
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;
|
||||
|
||||
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
|
||||
for ImageEncoder<'a, W, C, K, D>
|
||||
{
|
||||
impl<'a, W: Write + Seek, C: ColorType, K: TiffKind> Drop for ImageEncoder<'a, W, C, K> {
|
||||
fn drop(&mut self) {
|
||||
if !self.dropped {
|
||||
let _ = self.finish_internal();
|
||||
|
||||
@@ -236,6 +236,9 @@ impl fmt::Display for TiffUnsupportedError {
|
||||
pub enum UsageError {
|
||||
InvalidChunkType(ChunkType, ChunkType),
|
||||
InvalidChunkIndex(u32),
|
||||
PredictorCompressionMismatch,
|
||||
PredictorIncompatible,
|
||||
PredictorUnavailable,
|
||||
}
|
||||
|
||||
impl fmt::Display for UsageError {
|
||||
@@ -250,6 +253,15 @@ impl fmt::Display for UsageError {
|
||||
)
|
||||
}
|
||||
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! {
|
||||
pub enum Predictor(u16) {
|
||||
/// No changes were made to the data
|
||||
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,
|
||||
/// Not currently supported
|
||||
FloatingPoint = 3,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use tiff::{
|
||||
encoder::{
|
||||
colortype::{self, ColorType},
|
||||
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 generate_pixel(x: u32, y: u32) -> [<Self::Color as ColorType>::Inner; NUM_CHANNELS];
|
||||
|
||||
fn compress<C: Compression, W: Write + Seek>(
|
||||
&self,
|
||||
encoder: &mut TiffEncoder<W>,
|
||||
compression: C,
|
||||
) where
|
||||
fn compress<W: Write + Seek>(&self, encoder: &mut TiffEncoder<W>)
|
||||
where
|
||||
[<Self::Color as ColorType>::Inner]: TiffValue,
|
||||
{
|
||||
let image = encoder
|
||||
.new_image_with_compression::<Self::Color, C>(Self::WIDTH, Self::HEIGHT, compression)
|
||||
.new_image::<Self::Color>(Self::WIDTH, Self::HEIGHT)
|
||||
.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 image_rgb = TestImageColor::generate();
|
||||
@@ -103,9 +100,11 @@ fn encode_decode_with_compression<C: Compression + Clone>(compression: C) {
|
||||
// Encode tiff with compression
|
||||
{
|
||||
// Create a multipage image with 2 images
|
||||
let mut encoder = TiffEncoder::new(&mut data).unwrap();
|
||||
image_rgb.compress(&mut encoder, compression.clone());
|
||||
image_grayscale.compress(&mut encoder, compression);
|
||||
let mut encoder = TiffEncoder::new(&mut data)
|
||||
.unwrap()
|
||||
.with_compression(compression);
|
||||
image_rgb.compress(&mut encoder);
|
||||
image_grayscale.compress(&mut encoder);
|
||||
}
|
||||
|
||||
// Decode tiff
|
||||
@@ -136,22 +135,22 @@ fn encode_decode_with_compression<C: Compression + Clone>(compression: C) {
|
||||
|
||||
#[test]
|
||||
fn encode_decode_without_compression() {
|
||||
encode_decode_with_compression(Uncompressed::default());
|
||||
encode_decode_with_compression(Compression::Uncompressed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_decode_with_lzw() {
|
||||
encode_decode_with_compression(Lzw::default());
|
||||
encode_decode_with_compression(Compression::Lzw);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_decode_with_deflate() {
|
||||
encode_decode_with_compression(Deflate::with_level(DeflateLevel::Fast));
|
||||
encode_decode_with_compression(Deflate::with_level(DeflateLevel::Balanced));
|
||||
encode_decode_with_compression(Deflate::with_level(DeflateLevel::Best));
|
||||
encode_decode_with_compression(Compression::Deflate(DeflateLevel::Fast));
|
||||
encode_decode_with_compression(Compression::Deflate(DeflateLevel::Balanced));
|
||||
encode_decode_with_compression(Compression::Deflate(DeflateLevel::Best));
|
||||
}
|
||||
|
||||
#[test]
|
||||
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