diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 60fb8424..09d530bc 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -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()); } } diff --git a/src/encoder/colortype.rs b/src/encoder/colortype.rs index 1946dafa..462cf626 100644 --- a/src/encoder/colortype.rs +++ b/src/encoder/colortype.rs @@ -1,5 +1,31 @@ use crate::tags::{PhotometricInterpretation, SampleFormat}; +macro_rules! integer_horizontal_predict { + () => { + fn horizontal_predict(row: &[Self::Inner], result: &mut Vec) { + 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); } 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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!(); } diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index 152a77a6..c510474b 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -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 { writer: TiffWriter, kind: PhantomData, + predictor: Predictor, + compression: Compression, } /// Constructor functions to create standard Tiff files. @@ -83,6 +131,8 @@ impl TiffEncoder { 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 TiffEncoder { 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::new(&mut self.writer) @@ -100,20 +167,9 @@ impl TiffEncoder { &mut self, width: u32, height: u32, - ) -> TiffResult> { - 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( - &mut self, - width: u32, - height: u32, - compression: D, - ) -> TiffResult> { + ) -> TiffResult> { 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 TiffEncoder { [C::Inner]: TiffValue, { let encoder = DirectoryEncoder::new(&mut self.writer)?; - let image: ImageEncoder = 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( - &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 = - ImageEncoder::with_compression(encoder, width, height, compression)?; + let image: ImageEncoder = + 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, strip_byte_count: Vec, dropped: bool, - compression: D, + compression: Compression, + predictor: Predictor, _phantom: ::std::marker::PhantomData, } -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 - 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 { 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(::BITS_PER_SAMPLE.len())?; let row_bytes = row_samples * u64::from(::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, ::BITS_PER_SAMPLE)?; let sample_format: Vec<_> = ::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(); diff --git a/src/error.rs b/src/error.rs index 7dc781e0..955da670 100644 --- a/src/error.rs +++ b/src/error.rs @@ -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"), } } } diff --git a/src/tags.rs b/src/tags.rs index 3b86dc1f..6c18fa52 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -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, } } diff --git a/tests/encode_images_with_compression.rs b/tests/encode_images_with_compression.rs index 1cf178f2..2855c516 100644 --- a/tests/encode_images_with_compression.rs +++ b/tests/encode_images_with_compression.rs @@ -6,7 +6,7 @@ use tiff::{ encoder::{ colortype::{self, ColorType}, compression::*, - TiffEncoder, TiffValue, + Compression, TiffEncoder, TiffValue, }, }; @@ -18,15 +18,12 @@ trait TestImage: From: fn reference_data(&self) -> &[::Inner]; fn generate_pixel(x: u32, y: u32) -> [::Inner; NUM_CHANNELS]; - fn compress( - &self, - encoder: &mut TiffEncoder, - compression: C, - ) where + fn compress(&self, encoder: &mut TiffEncoder) + where [::Inner]: TiffValue, { let image = encoder - .new_image_with_compression::(Self::WIDTH, Self::HEIGHT, compression) + .new_image::(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(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(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(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); } diff --git a/tests/predict.rs b/tests/predict.rs new file mode 100644 index 00000000..a61eb6cb --- /dev/null +++ b/tests/predict.rs @@ -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>( + 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::("minisblack-1c-8b.tiff", ColorType::Gray(8)); +} + +#[test] +fn test_gray_i8_predict() { + test_i8_predict::("minisblack-1c-i8b.tiff", ColorType::Gray(8)); +} + +#[test] +fn test_rgb_u8_predict() { + test_u8_predict::("rgb-3c-8b.tiff", ColorType::RGB(8)); +} + +#[test] +fn test_cmyk_u8_predict() { + test_u8_predict::("cmyk-3c-8b.tiff", ColorType::CMYK(8)); +} + +#[test] +fn test_gray_u16_predict() { + test_u16_predict::("minisblack-1c-16b.tiff", ColorType::Gray(16)); +} + +#[test] +fn test_gray_i16_predict() { + test_i16_predict::("minisblack-1c-i16b.tiff", ColorType::Gray(16)); +} + +#[test] +fn test_rgb_u16_predict() { + test_u16_predict::("rgb-3c-16b.tiff", ColorType::RGB(16)); +} + +#[test] +fn test_cmyk_u16_predict() { + test_u16_predict::("cmyk-3c-16b.tiff", ColorType::CMYK(16)); +} + +#[test] +fn test_gray_u32_predict() { + test_u32_predict::("gradient-1c-32b.tiff", ColorType::Gray(32)); +} + +#[test] +fn test_rgb_u32_predict() { + test_u32_predict::("gradient-3c-32b.tiff", ColorType::RGB(32)); +} + +#[test] +fn test_gray_u64_predict() { + test_u64_predict::("gradient-1c-64b.tiff", ColorType::Gray(64)); +} + +#[test] +fn test_rgb_u64_predict() { + test_u64_predict::("gradient-3c-64b.tiff", ColorType::RGB(64)); +} + +#[test] +fn test_ycbcr_u8_predict() { + test_u8_predict::("tiled-jpeg-ycbcr.tif", ColorType::YCbCr(8)); +} + +macro_rules! test_predict_roundtrip { + ($name:ident, $buffer:ident, $buffer_ty:ty) => { + fn $name>( + 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::(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::("minisblack-1c-8b.tiff", ColorType::Gray(8)); +} + +#[test] +fn test_gray_i8_predict_roundtrip() { + test_i8_predict_roundtrip::("minisblack-1c-i8b.tiff", ColorType::Gray(8)); +} + +#[test] +fn test_rgb_u8_predict_roundtrip() { + test_u8_predict_roundtrip::("rgb-3c-8b.tiff", ColorType::RGB(8)); +} + +#[test] +fn test_cmyk_u8_predict_roundtrip() { + test_u8_predict_roundtrip::("cmyk-3c-8b.tiff", ColorType::CMYK(8)); +} + +#[test] +fn test_gray_u16_predict_roundtrip() { + test_u16_predict_roundtrip::("minisblack-1c-16b.tiff", ColorType::Gray(16)); +} + +#[test] +fn test_gray_i16_predict_roundtrip() { + test_i16_predict_roundtrip::( + "minisblack-1c-i16b.tiff", + ColorType::Gray(16), + ); +} + +#[test] +fn test_rgb_u16_predict_roundtrip() { + test_u16_predict_roundtrip::("rgb-3c-16b.tiff", ColorType::RGB(16)); +} + +#[test] +fn test_cmyk_u16_predict_roundtrip() { + test_u16_predict_roundtrip::("cmyk-3c-16b.tiff", ColorType::CMYK(16)); +} + +#[test] +fn test_gray_u32_predict_roundtrip() { + test_u32_predict_roundtrip::("gradient-1c-32b.tiff", ColorType::Gray(32)); +} + +#[test] +fn test_rgb_u32_predict_roundtrip() { + test_u32_predict_roundtrip::("gradient-3c-32b.tiff", ColorType::RGB(32)); +} + +#[test] +fn test_gray_u64_predict_roundtrip() { + test_u64_predict_roundtrip::("gradient-1c-64b.tiff", ColorType::Gray(64)); +} + +#[test] +fn test_rgb_u64_predict_roundtrip() { + test_u64_predict_roundtrip::("gradient-3c-64b.tiff", ColorType::RGB(64)); +} + +#[test] +fn test_ycbcr_u8_predict_roundtrip() { + test_u8_predict_roundtrip::("tiled-jpeg-ycbcr.tif", ColorType::YCbCr(8)); +}