From bbc49291e48361d7366ec881e145c85135f3a33e Mon Sep 17 00:00:00 2001 From: Andrew Champion Date: Wed, 11 Dec 2019 13:34:19 +0000 Subject: [PATCH 01/14] Add support for IEEEFP Gray(32) decoding --- README.md | 4 +- src/decoder/mod.rs | 83 ++++++++++++++++++++---- src/decoder/stream.rs | 21 ++++++ src/error.rs | 4 ++ src/tags.rs | 10 +++ tests/decode_images.rs | 6 ++ tests/images/gradient-1c-32b-float.tiff | Bin 0 -> 1357 bytes 7 files changed, 115 insertions(+), 13 deletions(-) create mode 100644 tests/images/gradient-1c-32b-float.tiff diff --git a/README.md b/README.md index df41496..557401c 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,8 @@ This table lists photometric interpretations and sample formats which are suppor | `PhotometricInterpretation` | UINT Format | IEEEFP Format | |-|-|-| -| `WhiteIsZero` | Gray(8\|16\|32\|64) | | -| `BlackIsZero` | Gray(8\|16\|32\|64) | | +| `WhiteIsZero` | Gray(8\|16\|32\|64) | Gray(32) | +| `BlackIsZero` | Gray(8\|16\|32\|64) | Gray(32) | | `RGB` | RGB(8\|16\|32\|64), RGBA(8\|16\|32\|64) | | | `RGBPalette` | | | | `Mask` | | | diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 4836c32..fd94253 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -6,7 +6,7 @@ use std::cmp; use {ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError}; use self::ifd::Directory; -use tags::{CompressionMethod, PhotometricInterpretation, Predictor, Tag, Type}; +use tags::{CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag, Type}; use self::stream::{ByteOrder, EndianReader, LZWReader, DeflateReader, PackBitsReader, SmartReader}; @@ -24,6 +24,8 @@ pub enum DecodingResult { U32(Vec), /// A vector of 64 bit unsigned ints U64(Vec), + /// A vector of 32 bit IEEE floats + F32(Vec), } impl DecodingResult { @@ -59,12 +61,21 @@ impl DecodingResult { } } + fn new_f32(size: usize, limits: &Limits) -> TiffResult { + if size > limits.decoding_buffer_size / std::mem::size_of::() { + Err(TiffError::LimitsExceeded) + } else { + Ok(DecodingResult::F32(vec![0.0; size])) + } + } + pub fn as_buffer(&mut self, start: usize) -> DecodingBuffer { match *self { DecodingResult::U8(ref mut buf) => DecodingBuffer::U8(&mut buf[start..]), DecodingResult::U16(ref mut buf) => DecodingBuffer::U16(&mut buf[start..]), DecodingResult::U32(ref mut buf) => DecodingBuffer::U32(&mut buf[start..]), DecodingResult::U64(ref mut buf) => DecodingBuffer::U64(&mut buf[start..]), + DecodingResult::F32(ref mut buf) => DecodingBuffer::F32(&mut buf[start..]), } } } @@ -79,6 +90,8 @@ pub enum DecodingBuffer<'a> { U32(&'a mut [u32]), /// A slice of 64 bit unsigned ints U64(&'a mut [u64]), + /// A slice of 32 bit IEEE floats + F32(&'a mut [f32]), } impl<'a> DecodingBuffer<'a> { @@ -88,6 +101,7 @@ impl<'a> DecodingBuffer<'a> { DecodingBuffer::U16(ref buf) => buf.len(), DecodingBuffer::U32(ref buf) => buf.len(), DecodingBuffer::U64(ref buf) => buf.len(), + DecodingBuffer::F32(ref buf) => buf.len(), } } @@ -97,6 +111,7 @@ impl<'a> DecodingBuffer<'a> { DecodingBuffer::U16(_) => 2, DecodingBuffer::U32(_) => 4, DecodingBuffer::U64(_) => 8, + DecodingBuffer::F32(_) => 4, } } @@ -109,6 +124,7 @@ impl<'a> DecodingBuffer<'a> { DecodingBuffer::U16(ref mut buf) => DecodingBuffer::U16(buf), DecodingBuffer::U32(ref mut buf) => DecodingBuffer::U32(buf), DecodingBuffer::U64(ref mut buf) => DecodingBuffer::U64(buf), + DecodingBuffer::F32(ref mut buf) => DecodingBuffer::F32(buf), } } } @@ -164,6 +180,7 @@ where height: u32, bits_per_sample: Vec, samples: u8, + sample_format: Vec, photometric_interpretation: PhotometricInterpretation, compression_method: CompressionMethod, strip_decoder: Option, @@ -197,6 +214,12 @@ impl Wrapping for u64 { } } +impl Wrapping for f32 { + fn wrapping_add(&self, other: Self) -> Self { + self + other + } +} + fn rev_hpredict_nsamp(image: &mut [T], size: (u32, u32), samples: usize) -> TiffResult<()> where T: Copy + Wrapping, @@ -238,6 +261,9 @@ fn rev_hpredict(image: DecodingBuffer, size: (u32, u32), color_type: ColorType) DecodingBuffer::U64(buf) => { rev_hpredict_nsamp(buf, size, samples)?; } + DecodingBuffer::F32(buf) => { + rev_hpredict_nsamp(buf, size, samples)?; + } } Ok(()) } @@ -255,6 +281,7 @@ impl Decoder { height: 0, bits_per_sample: vec![1], samples: 1, + sample_format: vec![SampleFormat::UINT], photometric_interpretation: PhotometricInterpretation::BlackIsZero, compression_method: CompressionMethod::None, strip_decoder: None, @@ -370,6 +397,18 @@ impl Decoder { if let Some(val) = self.find_tag_unsigned(Tag::SamplesPerPixel)? { self.samples = val; } + if let Some(vals) = self.find_tag_unsigned_vec(Tag::SampleFormat)? { + self.sample_format = vals.iter() + .map(|&v| SampleFormat::from_u16(v) + .ok_or_else(|| TiffUnsupportedError::UnsupportedSampleFormat(vals.clone())) + ) + .collect::>()?; + + // FIXME: for now, only homogenous formats across samples are supported. + if !self.sample_format.windows(2).all(|s| s[0] == s[1]) { + return Err(TiffUnsupportedError::UnsupportedSampleFormat(vals).into()) + } + } match self.samples { 1 | 3 | 4 => if let Some(val) = self.find_tag_unsigned_vec(Tag::BitsPerSample)? { self.bits_per_sample = val; @@ -675,6 +714,16 @@ impl Decoder { } bytes } + (ColorType::Gray(32), DecodingBuffer::F32(ref mut buffer)) => { + reader.read_f32_into(&mut buffer[..bytes / 4])?; + if self.photometric_interpretation == PhotometricInterpretation::WhiteIsZero { + for datum in buffer[..bytes / 4].iter_mut() { + // FIXME: assumes [0, 1) range for floats + *datum = 1.0 - *datum + } + } + bytes / 4 + } (type_, _) => { return Err(TiffError::UnsupportedError( TiffUnsupportedError::UnsupportedColorType(type_), @@ -787,16 +836,28 @@ impl Decoder { fn result_buffer(&self, height: usize) -> TiffResult { let buffer_size = usize::try_from(self.width)? * height * self.bits_per_sample.len(); - match self.bits_per_sample.iter().cloned().max().unwrap_or(8) { - n if n <= 8 => DecodingResult::new_u8(buffer_size, &self.limits), - n if n <= 16 => DecodingResult::new_u16(buffer_size, &self.limits), - n if n <= 32 => DecodingResult::new_u32(buffer_size, &self.limits), - n if n <= 64 => DecodingResult::new_u64(buffer_size, &self.limits), - n => { - Err(TiffError::UnsupportedError( - TiffUnsupportedError::UnsupportedBitsPerChannel(n), - )) - } + let max_sample_bits = self.bits_per_sample.iter().cloned().max().unwrap_or(8); + match self.sample_format.first().unwrap_or(&SampleFormat::UINT) { + SampleFormat::UINT => match max_sample_bits { + n if n <= 8 => DecodingResult::new_u8(buffer_size, &self.limits), + n if n <= 16 => DecodingResult::new_u16(buffer_size, &self.limits), + n if n <= 32 => DecodingResult::new_u32(buffer_size, &self.limits), + n if n <= 64 => DecodingResult::new_u64(buffer_size, &self.limits), + n => { + Err(TiffError::UnsupportedError( + TiffUnsupportedError::UnsupportedBitsPerChannel(n), + )) + } + }, + SampleFormat::IEEEFP => match max_sample_bits { + 32 => DecodingResult::new_f32(buffer_size, &self.limits), + n => { + Err(TiffError::UnsupportedError( + TiffUnsupportedError::UnsupportedBitsPerChannel(n), + )) + } + }, + format => Err(TiffUnsupportedError::UnsupportedSampleFormat(vec![format.to_u16()]).into()), } } diff --git a/src/decoder/stream.rs b/src/decoder/stream.rs index caf4e3c..d7645e1 100644 --- a/src/decoder/stream.rs +++ b/src/decoder/stream.rs @@ -100,6 +100,27 @@ pub trait EndianReader: Read { ByteOrder::BigEndian => ::read_i32::(self), } } + + /// Reads an f32 + #[inline(always)] + fn read_f32(&mut self) -> Result { + match self.byte_order() { + ByteOrder::LittleEndian => ::read_f32::(self), + ByteOrder::BigEndian => ::read_f32::(self), + } + } + + #[inline(always)] + fn read_f32_into(&mut self, buffer: &mut [f32]) -> Result<(), io::Error> { + match self.byte_order() { + ByteOrder::LittleEndian => { + ::read_f32_into::(self, buffer) + } + ByteOrder::BigEndian => { + ::read_f32_into::(self, buffer) + } + } + } } /// Reader that decompresses DEFLATE streams diff --git a/src/error.rs b/src/error.rs index 02acbe0..4c965fd 100644 --- a/src/error.rs +++ b/src/error.rs @@ -114,6 +114,7 @@ pub enum TiffUnsupportedError { UnknownCompressionMethod, UnsupportedCompressionMethod(CompressionMethod), UnsupportedSampleDepth(u8), + UnsupportedSampleFormat(Vec), UnsupportedColorType(ColorType), UnsupportedBitsPerChannel(u8), UnsupportedPlanarConfig(Option), @@ -148,6 +149,9 @@ impl fmt::Display for TiffUnsupportedError { UnsupportedSampleDepth(samples) => { write!(fmt, "{} samples per pixel is unsupported.", samples) } + UnsupportedSampleFormat(ref formats) => { + write!(fmt, "Sample format {:?} is unsupported.", formats) + } UnsupportedColorType(color_type) => { write!(fmt, "Color type {:?} is unsupported", color_type) } diff --git a/src/tags.rs b/src/tags.rs index fabb74d..21b9709 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -95,6 +95,7 @@ pub enum Tag(u16) unknown("A private or extension tag") { PlanarConfiguration = 284, ResolutionUnit = 296, // TODO add support RowsPerStrip = 278, + SampleFormat = 339, SamplesPerPixel = 277, Software = 305, StripByteCounts = 279, @@ -180,4 +181,13 @@ pub enum ResolutionUnit(u16) { Inch = 2, Centimeter = 3, } +} + +tags! { +pub enum SampleFormat(u16) { + UINT = 1, + INT = 2, + IEEEFP = 3, + VOID = 4, +} } \ No newline at end of file diff --git a/tests/decode_images.rs b/tests/decode_images.rs index 9fc8676..93ea06f 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -32,6 +32,7 @@ test_image_sum!(test_image_sum_u8, U8, u64); test_image_sum!(test_image_sum_u16, U16, u64); test_image_sum!(test_image_sum_u32, U32, u64); test_image_sum!(test_image_sum_u64, U64, u64); +test_image_sum!(test_image_sum_f32, F32, f32); /// Tests that a decoder can be constructed for an image and the color type /// read from the IFD and is of the appropriate type, but the type is @@ -82,6 +83,11 @@ fn test_gray_u64() { test_image_sum_u64("gradient-1c-64b.tiff", ColorType::Gray(64), 549892913787); } +#[test] +fn test_gray_f32() { + test_image_sum_f32("gradient-1c-32b-float.tiff", ColorType::Gray(32), 128.03194); +} + #[test] fn test_rgb_u8() { test_image_sum_u8("rgb-3c-8b.tiff", ColorType::RGB(8), 7842108); diff --git a/tests/images/gradient-1c-32b-float.tiff b/tests/images/gradient-1c-32b-float.tiff new file mode 100644 index 0000000000000000000000000000000000000000..7acb00f01ec684ef2dfa7ccd775e70509af9add8 GIT binary patch literal 1357 zcmZ9Mc~Fx_7{RBqe}JI4nX%>Vd^5 zitzxd;(=HxQmkVwFlrTa6f}dyHqt{|ZLQT7=`q5n{m^uV>CWu#?|t6q+1B277(^SPS7u2#N^Xl1=(Wlv9r&hMC?jjo%)x}=z zzR8-de9Km7eqB&l;3Qu)lqq~9Ya!M+3< z?UqCZ@hMbookrFI6`kl8^d;mQR7(EaHjsmK6B)OaQCG`{ zROz{u3LCdkPeTRm7wsgwuPbS_%1D2E?WM^BRU}NSp{Bu)$ZFR?YD+mpH@-PSj_pUu z+;@y@I**e)rh)kKMyioFQJ=AyJ{UYj&l*nC^0S{)<-l2zxV4h*UK{OjK2PC^U((*e zOT<*SQ<3R1tr1-%m)uVBDY;H-9ZeKa+f9Q@zM}g(d+4~UnbzI7O%7$f^hN9URD9tF zTGn`%7IfVse$3A#k9$C|H4jOodPFXDzY~8{AKBOUleyzB%Gm!8txA7NH#ZEBu=^Q_ zZ5Vvw$>3o*504M?v8c@o19Pph<)jU^h6%9w*a%#TvPEgC9fFhWA?+H8KblA3N~8l6 z&m3?f^EEI>9I^|y(s`ai&@^V9bwL2P%-oV3Y9wEQtg+$cuf#;G{hAc1G;n<&{A2)oTe=(P*RCVMFggQsD}%IWB?n}I#T5IosE6BE~l zqWJPGWQEGGBZP%_1;O715LV8{_58Qst_Xu#GY7{q=R%=+8!g`N;Biqn+RsIxzhWM& zBj%&wyGV3~Er7`<3jC62gnhOU=bIM6{o@$4H^*Ycg~dp%mqXCF1gEOv;2W?Mz1x?; zqaz;Ps}dkJFNbDQA`;_PK=^4A>O?Ei5w{B0HYl*jHyMZ4r@%is6$#!-gcqiv@@hJg zJu;v+sL%&BW~aZ42Z@=uQ=ow%Aq#?>Y}``kV0+$boT<`+#5&ZT*2AzO7wYMGxSXGl zYEc1vDGkLFpM2| zd?uRf|H}O&PCVI?m&6@iSaj$g_EwHFNA~;kz7r4a<(tfulCD*xs+3uJabSu#I4D`1mYJ>4PaX0x|7*Px{{=jd#}WVl literal 0 HcmV?d00001 From 97c888f16687f4d8b4790e364974764517bbd95a Mon Sep 17 00:00:00 2001 From: Andrew Champion Date: Thu, 6 Feb 2020 18:41:00 +0000 Subject: [PATCH 02/14] Add support for IEEEFP RGB/RGBA/CMYK(32) decoding --- Pipfile | 11 +++++++++++ README.md | 4 ++-- src/decoder/mod.rs | 6 ++++++ tests/decode_images.rs | 10 ++++++++++ tests/images/cmyk-3c-32b-float.tiff | Bin 0 -> 6754 bytes tests/images/gradient-3c-32b-float.tiff | Bin 0 -> 3477 bytes 6 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 Pipfile create mode 100644 tests/images/cmyk-3c-32b-float.tiff create mode 100644 tests/images/gradient-3c-32b-float.tiff diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..b723d01 --- /dev/null +++ b/Pipfile @@ -0,0 +1,11 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] + +[packages] + +[requires] +python_version = "3.7" diff --git a/README.md b/README.md index 557401c..d1b8453 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,10 @@ This table lists photometric interpretations and sample formats which are suppor |-|-|-| | `WhiteIsZero` | Gray(8\|16\|32\|64) | Gray(32) | | `BlackIsZero` | Gray(8\|16\|32\|64) | Gray(32) | -| `RGB` | RGB(8\|16\|32\|64), RGBA(8\|16\|32\|64) | | +| `RGB` | RGB(8\|16\|32\|64), RGBA(8\|16\|32\|64) | RGB(32), RGBA(32) | | `RGBPalette` | | | | `Mask` | | | -| `CMYK` | CMYK(8\|16\|32\|64) | | +| `CMYK` | CMYK(8\|16\|32\|64) | CMYK(32) | | `YCbCr` | | | | `CIELab` | | | diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index fd94253..00456bd 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -672,6 +672,12 @@ impl Decoder { reader.read_u32_into(&mut buffer[..bytes / 4])?; bytes / 4 } + (ColorType::RGBA(32), DecodingBuffer::F32(ref mut buffer)) + | (ColorType::RGB(32), DecodingBuffer::F32(ref mut buffer)) + | (ColorType::CMYK(32), DecodingBuffer::F32(ref mut buffer)) => { + reader.read_f32_into(&mut buffer[..bytes / 4])?; + bytes / 4 + } (ColorType::RGBA(64), DecodingBuffer::U64(ref mut buffer)) | (ColorType::RGB(64), DecodingBuffer::U64(ref mut buffer)) | (ColorType::CMYK(64), DecodingBuffer::U64(ref mut buffer)) => { diff --git a/tests/decode_images.rs b/tests/decode_images.rs index 93ea06f..5e05002 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -58,6 +58,11 @@ fn test_cmyk_u16() { test_image_sum_u16("cmyk-3c-16b.tiff", ColorType::CMYK(16), 2181426827); } +#[test] +fn test_cmyk_f32() { + test_image_sum_f32("cmyk-3c-32b-float.tiff", ColorType::CMYK(32), 496.0405); +} + #[test] fn test_gray_u8() { test_image_sum_u8("minisblack-1c-8b.tiff", ColorType::Gray(8), 2840893); @@ -113,6 +118,11 @@ fn test_rgb_u64() { test_image_sum_u64("gradient-3c-64b.tiff", ColorType::RGB(64), 2030834111716); } +#[test] +fn test_rgb_f32() { + test_image_sum_f32("gradient-3c-32b-float.tiff", ColorType::RGB(32), 472.8405); +} + #[test] fn test_string_tags() { // these files have null-terminated strings for their Software tag. One has extra bytes after diff --git a/tests/images/cmyk-3c-32b-float.tiff b/tests/images/cmyk-3c-32b-float.tiff new file mode 100644 index 0000000000000000000000000000000000000000..9ad77d8fa1100f25d80785e4f5a1e58f6f34e0a9 GIT binary patch literal 6754 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 literal 0 HcmV?d00001 From c2084835fa85f011c087ed9a766262bc72803b61 Mon Sep 17 00:00:00 2001 From: Andrew Champion Date: Thu, 6 Feb 2020 19:05:21 +0000 Subject: [PATCH 03/14] Add support for IEEEFP Gray/RGB/RGBA/CMYK(64) decoding --- README.md | 8 ++--- src/decoder/mod.rs | 42 ++++++++++++++++++++++++ src/decoder/stream.rs | 21 ++++++++++++ tests/decode_images.rs | 6 ++++ tests/images/gradient-1c-64b-float.tiff | Bin 0 -> 2368 bytes 5 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 tests/images/gradient-1c-64b-float.tiff diff --git a/README.md b/README.md index d1b8453..b807724 100644 --- a/README.md +++ b/README.md @@ -17,12 +17,12 @@ This table lists photometric interpretations and sample formats which are suppor | `PhotometricInterpretation` | UINT Format | IEEEFP Format | |-|-|-| -| `WhiteIsZero` | Gray(8\|16\|32\|64) | Gray(32) | -| `BlackIsZero` | Gray(8\|16\|32\|64) | Gray(32) | -| `RGB` | RGB(8\|16\|32\|64), RGBA(8\|16\|32\|64) | RGB(32), RGBA(32) | +| `WhiteIsZero` | Gray(8\|16\|32\|64) | Gray(32\|64) | +| `BlackIsZero` | Gray(8\|16\|32\|64) | Gray(32\|64) | +| `RGB` | RGB(8\|16\|32\|64), RGBA(8\|16\|32\|64) | RGB(32\|64), RGBA(32\|64) | | `RGBPalette` | | | | `Mask` | | | -| `CMYK` | CMYK(8\|16\|32\|64) | CMYK(32) | +| `CMYK` | CMYK(8\|16\|32\|64) | CMYK(32\|64) | | `YCbCr` | | | | `CIELab` | | | diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 00456bd..6ec0168 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -26,6 +26,8 @@ pub enum DecodingResult { U64(Vec), /// A vector of 32 bit IEEE floats F32(Vec), + /// A vector of 64 bit IEEE floats + F64(Vec), } impl DecodingResult { @@ -69,6 +71,14 @@ impl DecodingResult { } } + fn new_f64(size: usize, limits: &Limits) -> TiffResult { + if size > limits.decoding_buffer_size / std::mem::size_of::() { + Err(TiffError::LimitsExceeded) + } else { + Ok(DecodingResult::F64(vec![0.0; size])) + } + } + pub fn as_buffer(&mut self, start: usize) -> DecodingBuffer { match *self { DecodingResult::U8(ref mut buf) => DecodingBuffer::U8(&mut buf[start..]), @@ -76,6 +86,7 @@ impl DecodingResult { DecodingResult::U32(ref mut buf) => DecodingBuffer::U32(&mut buf[start..]), DecodingResult::U64(ref mut buf) => DecodingBuffer::U64(&mut buf[start..]), DecodingResult::F32(ref mut buf) => DecodingBuffer::F32(&mut buf[start..]), + DecodingResult::F64(ref mut buf) => DecodingBuffer::F64(&mut buf[start..]), } } } @@ -92,6 +103,8 @@ pub enum DecodingBuffer<'a> { U64(&'a mut [u64]), /// A slice of 32 bit IEEE floats F32(&'a mut [f32]), + /// A slice of 64 bit IEEE floats + F64(&'a mut [f64]), } impl<'a> DecodingBuffer<'a> { @@ -102,6 +115,7 @@ impl<'a> DecodingBuffer<'a> { DecodingBuffer::U32(ref buf) => buf.len(), DecodingBuffer::U64(ref buf) => buf.len(), DecodingBuffer::F32(ref buf) => buf.len(), + DecodingBuffer::F64(ref buf) => buf.len(), } } @@ -112,6 +126,7 @@ impl<'a> DecodingBuffer<'a> { DecodingBuffer::U32(_) => 4, DecodingBuffer::U64(_) => 8, DecodingBuffer::F32(_) => 4, + DecodingBuffer::F64(_) => 8, } } @@ -125,6 +140,7 @@ impl<'a> DecodingBuffer<'a> { DecodingBuffer::U32(ref mut buf) => DecodingBuffer::U32(buf), DecodingBuffer::U64(ref mut buf) => DecodingBuffer::U64(buf), DecodingBuffer::F32(ref mut buf) => DecodingBuffer::F32(buf), + DecodingBuffer::F64(ref mut buf) => DecodingBuffer::F64(buf), } } } @@ -220,6 +236,12 @@ impl Wrapping for f32 { } } +impl Wrapping for f64 { + fn wrapping_add(&self, other: Self) -> Self { + self + other + } +} + fn rev_hpredict_nsamp(image: &mut [T], size: (u32, u32), samples: usize) -> TiffResult<()> where T: Copy + Wrapping, @@ -264,6 +286,9 @@ fn rev_hpredict(image: DecodingBuffer, size: (u32, u32), color_type: ColorType) DecodingBuffer::F32(buf) => { rev_hpredict_nsamp(buf, size, samples)?; } + DecodingBuffer::F64(buf) => { + rev_hpredict_nsamp(buf, size, samples)?; + } } Ok(()) } @@ -678,6 +703,12 @@ impl Decoder { reader.read_f32_into(&mut buffer[..bytes / 4])?; bytes / 4 } + (ColorType::RGBA(64), DecodingBuffer::F64(ref mut buffer)) + | (ColorType::RGB(64), DecodingBuffer::F64(ref mut buffer)) + | (ColorType::CMYK(64), DecodingBuffer::F64(ref mut buffer)) => { + reader.read_f64_into(&mut buffer[..bytes / 8])?; + bytes / 8 + } (ColorType::RGBA(64), DecodingBuffer::U64(ref mut buffer)) | (ColorType::RGB(64), DecodingBuffer::U64(ref mut buffer)) | (ColorType::CMYK(64), DecodingBuffer::U64(ref mut buffer)) => { @@ -730,6 +761,16 @@ impl Decoder { } bytes / 4 } + (ColorType::Gray(64), DecodingBuffer::F64(ref mut buffer)) => { + reader.read_f64_into(&mut buffer[..bytes / 8])?; + if self.photometric_interpretation == PhotometricInterpretation::WhiteIsZero { + for datum in buffer[..bytes / 8].iter_mut() { + // FIXME: assumes [0, 1) range for floats + *datum = 1.0 - *datum + } + } + bytes / 8 + } (type_, _) => { return Err(TiffError::UnsupportedError( TiffUnsupportedError::UnsupportedColorType(type_), @@ -857,6 +898,7 @@ impl Decoder { }, SampleFormat::IEEEFP => match max_sample_bits { 32 => DecodingResult::new_f32(buffer_size, &self.limits), + 64 => DecodingResult::new_f64(buffer_size, &self.limits), n => { Err(TiffError::UnsupportedError( TiffUnsupportedError::UnsupportedBitsPerChannel(n), diff --git a/src/decoder/stream.rs b/src/decoder/stream.rs index d7645e1..cdee074 100644 --- a/src/decoder/stream.rs +++ b/src/decoder/stream.rs @@ -121,6 +121,27 @@ pub trait EndianReader: Read { } } } + + /// Reads an f64 + #[inline(always)] + fn read_f64(&mut self) -> Result { + match self.byte_order() { + ByteOrder::LittleEndian => ::read_f64::(self), + ByteOrder::BigEndian => ::read_f64::(self), + } + } + + #[inline(always)] + fn read_f64_into(&mut self, buffer: &mut [f64]) -> Result<(), io::Error> { + match self.byte_order() { + ByteOrder::LittleEndian => { + ::read_f64_into::(self, buffer) + } + ByteOrder::BigEndian => { + ::read_f64_into::(self, buffer) + } + } + } } /// Reader that decompresses DEFLATE streams diff --git a/tests/decode_images.rs b/tests/decode_images.rs index 5e05002..f8642c2 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -33,6 +33,7 @@ test_image_sum!(test_image_sum_u16, U16, u64); test_image_sum!(test_image_sum_u32, U32, u64); test_image_sum!(test_image_sum_u64, U64, u64); test_image_sum!(test_image_sum_f32, F32, f32); +test_image_sum!(test_image_sum_f64, F64, f64); /// Tests that a decoder can be constructed for an image and the color type /// read from the IFD and is of the appropriate type, but the type is @@ -93,6 +94,11 @@ fn test_gray_f32() { test_image_sum_f32("gradient-1c-32b-float.tiff", ColorType::Gray(32), 128.03194); } +#[test] +fn test_gray_f64() { + test_image_sum_f64("gradient-1c-64b-float.tiff", ColorType::Gray(64), 128.0319210877642); +} + #[test] fn test_rgb_u8() { test_image_sum_u8("rgb-3c-8b.tiff", ColorType::RGB(8), 7842108); diff --git a/tests/images/gradient-1c-64b-float.tiff b/tests/images/gradient-1c-64b-float.tiff new file mode 100644 index 0000000000000000000000000000000000000000..a2f843cdbed9f4a1042e1c75840991f5b90ea38a GIT binary patch literal 2368 zcmbu)dr(wW90%~TZaT>%pqM$@*kvhm7-nrL%Wx*ol#g+gQC?2!sMiP-9}|MQqCJiy zjW|XosN*wk7!c_CN)%Ay073%dgNK^Wn+h2SWMV!lr+dD?b%y`jndNi7zwiB>yG)uC z%${eA`Ln0&IcBp?_}IK>Yhd%9RoBaA$1~hI+r4Ks@6+S#YwN`ZTC+3Ox4(CnTMxb1 z@KCYBkVh+NS>7qqle`vxg#rADKqBeWBzYP0p>yV9?YM- zasbXxI0)BmJEYWR79CdVL92d*b^Hh{cNUU0N8#}7V@mDb<|3t$UsH^E%8nAu!-|i? z!%I$}zm{K$T=nr!N_|XanNq$RE}?b;vgwiK$R#JCXrED`GGB8A%G$Y=DvJ+qlNSO{ zqW7tRV(FYJm08n8$bSQz7Z3H8L(o{$ z3gw@!^QxEI-B2X$fqK^inwN}fz&?emP zUJO~-akAnP ztB^-JE~(y3TLiV_GRQ(#ZZtp%HP4`u-0n7xn#B-J^dK zLRL?PqB;e#&$mJ?C7;|^M*Wr!>WzUn)xMTF0-7TtAq$vCxnVQZqYgs3p%Q8v?o(gr zcMI!=z@}L>i0QE0#(tPcJJ8Iv2>?9piAonbR zY)d-SLOf7Btc0?(iyS=oceSs-7YYTN1dZm^FTPP15(608iTce?Ap8~~%)zC;!rCe74`3D!EIk1Ji5hUjFAnmit&|V#rCeVP+2c+q0(y6;eHr;W6b0dw&o6@f$|lO8M=0-X Kq+D~aYwUl_b(c#3 literal 0 HcmV?d00001 From a41e33e42a0126315152e397f3053d045cf4edf6 Mon Sep 17 00:00:00 2001 From: Andrew Champion Date: Thu, 6 Feb 2020 19:23:45 +0000 Subject: [PATCH 04/14] Fix wrong inner types for color types --- src/encoder/colortype.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/encoder/colortype.rs b/src/encoder/colortype.rs index 2f97066..a625b78 100644 --- a/src/encoder/colortype.rs +++ b/src/encoder/colortype.rs @@ -82,14 +82,14 @@ impl ColorType for RGBA16 { pub struct RGBA32; impl ColorType for RGBA32 { - type Inner = u16; + type Inner = u32; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32]; } pub struct RGBA64; impl ColorType for RGBA64 { - type Inner = u16; + type Inner = u64; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64]; } From 8d4bdce7aa69bf0fde5315f3e631f9dc7ebb3148 Mon Sep 17 00:00:00 2001 From: Andrew Champion Date: Thu, 6 Feb 2020 20:32:18 +0000 Subject: [PATCH 05/14] Add support for IEEEFP 32 and 64 bit encoding --- src/encoder/colortype.rs | 84 +++++++++++++++++++++++++++++++++++++++- src/encoder/mod.rs | 64 ++++++++++++++++++++++++++++++ src/encoder/writer.rs | 14 +++++++ src/tags.rs | 2 + tests/encode_images.rs | 22 +++++++++++ 5 files changed, 185 insertions(+), 1 deletion(-) diff --git a/src/encoder/colortype.rs b/src/encoder/colortype.rs index a625b78..9ee9bc6 100644 --- a/src/encoder/colortype.rs +++ b/src/encoder/colortype.rs @@ -1,4 +1,4 @@ -use tags::PhotometricInterpretation; +use tags::{PhotometricInterpretation, SampleFormat}; /// Trait for different colortypes that can be encoded. pub trait ColorType { @@ -8,6 +8,8 @@ pub trait ColorType { const TIFF_VALUE: PhotometricInterpretation; /// The value of the tiff tag `BitsPerSample` const BITS_PER_SAMPLE: &'static [u16]; + /// The value of the tiff tag `SampleFormat` + const SAMPLE_FORMAT: &'static [SampleFormat]; } pub struct Gray8; @@ -15,6 +17,7 @@ impl ColorType for Gray8 { type Inner = u8; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const BITS_PER_SAMPLE: &'static [u16] = &[8]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::UINT]; } pub struct Gray16; @@ -22,6 +25,7 @@ impl ColorType for Gray16 { type Inner = u16; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const BITS_PER_SAMPLE: &'static [u16] = &[16]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::UINT]; } pub struct Gray32; @@ -29,6 +33,15 @@ impl ColorType for Gray32 { type Inner = u32; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const BITS_PER_SAMPLE: &'static [u16] = &[32]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::UINT]; +} + +pub struct Gray32Float; +impl ColorType for Gray32Float { + type Inner = f32; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; + const BITS_PER_SAMPLE: &'static [u16] = &[32]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP]; } pub struct Gray64; @@ -36,6 +49,15 @@ impl ColorType for Gray64 { type Inner = u64; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const BITS_PER_SAMPLE: &'static [u16] = &[64]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::UINT]; +} + +pub struct Gray64Float; +impl ColorType for Gray64Float { + type Inner = f64; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; + const BITS_PER_SAMPLE: &'static [u16] = &[64]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP]; } pub struct RGB8; @@ -43,6 +65,7 @@ impl ColorType for RGB8 { type Inner = u8; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::UINT; 3]; } pub struct RGB16; @@ -50,6 +73,7 @@ impl ColorType for RGB16 { type Inner = u16; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::UINT; 3]; } pub struct RGB32; @@ -57,6 +81,15 @@ impl ColorType for RGB32 { type Inner = u32; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::UINT; 3]; +} + +pub struct RGB32Float; +impl ColorType for RGB32Float { + type Inner = f32; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; + const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3]; } pub struct RGB64; @@ -64,6 +97,15 @@ impl ColorType for RGB64 { type Inner = u64; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::UINT; 3]; +} + +pub struct RGB64Float; +impl ColorType for RGB64Float { + type Inner = f64; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; + const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3]; } pub struct RGBA8; @@ -71,6 +113,7 @@ impl ColorType for RGBA8 { type Inner = u8; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::UINT; 4]; } pub struct RGBA16; @@ -78,6 +121,7 @@ impl ColorType for RGBA16 { type Inner = u16; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::UINT; 4]; } pub struct RGBA32; @@ -85,6 +129,15 @@ impl ColorType for RGBA32 { type Inner = u32; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::UINT; 4]; +} + +pub struct RGBA32Float; +impl ColorType for RGBA32Float { + type Inner = f32; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; + const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4]; } pub struct RGBA64; @@ -92,6 +145,15 @@ impl ColorType for RGBA64 { type Inner = u64; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::UINT; 4]; +} + +pub struct RGBA64Float; +impl ColorType for RGBA64Float { + type Inner = f64; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; + const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4]; } pub struct CMYK8; @@ -99,6 +161,7 @@ impl ColorType for CMYK8 { type Inner = u8; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::UINT; 4]; } pub struct CMYK16; @@ -106,6 +169,7 @@ impl ColorType for CMYK16 { type Inner = u16; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::UINT; 4]; } pub struct CMYK32; @@ -113,6 +177,15 @@ impl ColorType for CMYK32 { type Inner = u32; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::UINT; 4]; +} + +pub struct CMYK32Float; +impl ColorType for CMYK32Float { + type Inner = f32; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; + const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4]; } pub struct CMYK64; @@ -120,4 +193,13 @@ impl ColorType for CMYK64 { type Inner = u64; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::UINT; 4]; +} + +pub struct CMYK64Float; +impl ColorType for CMYK64Float { + type Inner = f64; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; + const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4]; } diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index 22e39d3..1d4ac3f 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -153,6 +153,40 @@ impl TiffValue for [u64] { } } +impl TiffValue for [f32] { + const BYTE_LEN: u32 = 4; + const FIELD_TYPE: Type = Type::FLOAT; + + fn count(&self) -> u32 { + self.len() as u32 + } + + fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { + // We write using nativeedian so this sould be safe + let slice = + unsafe { ::std::slice::from_raw_parts(self.as_ptr() as *const u8, self.len() * 4) }; + writer.write_bytes(slice)?; + Ok(()) + } +} + +impl TiffValue for [f64] { + const BYTE_LEN: u32 = 8; + const FIELD_TYPE: Type = Type::DOUBLE; + + fn count(&self) -> u32 { + self.len() as u32 + } + + fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { + // We write using nativeedian so this sould be safe + let slice = + unsafe { ::std::slice::from_raw_parts(self.as_ptr() as *const u8, self.len() * 8) }; + writer.write_bytes(slice)?; + Ok(()) + } +} + impl TiffValue for [Rational] { const BYTE_LEN: u32 = 8; const FIELD_TYPE: Type = Type::RATIONAL; @@ -283,6 +317,34 @@ impl TiffValue for u64 { } } +impl TiffValue for f32 { + const BYTE_LEN: u32 = 4; + const FIELD_TYPE: Type = Type::FLOAT; + + fn count(&self) -> u32 { + 1 + } + + fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { + writer.write_f32(*self)?; + Ok(()) + } +} + +impl TiffValue for f64 { + const BYTE_LEN: u32 = 8; + const FIELD_TYPE: Type = Type::DOUBLE; + + fn count(&self) -> u32 { + 1 + } + + fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { + writer.write_f64(*self)?; + Ok(()) + } +} + impl TiffValue for Rational { const BYTE_LEN: u32 = 8; const FIELD_TYPE: Type = Type::RATIONAL; @@ -593,6 +655,8 @@ impl<'a, W: 'a + Write + Seek, T: ColorType> ImageEncoder<'a, W, T> { encoder.write_tag(Tag::Compression, tags::CompressionMethod::None.to_u16())?; encoder.write_tag(Tag::BitsPerSample, ::BITS_PER_SAMPLE)?; + let sample_format: Vec<_> = ::SAMPLE_FORMAT.iter().map(|s| s.to_u16()).collect(); + encoder.write_tag(Tag::SampleFormat, &sample_format[..])?; encoder.write_tag(Tag::PhotometricInterpretation, ::TIFF_VALUE.to_u16())?; encoder.write_tag(Tag::RowsPerStrip, u32::try_from(rows_per_strip)?)?; diff --git a/src/encoder/writer.rs b/src/encoder/writer.rs index ba4c4c5..8d3929d 100644 --- a/src/encoder/writer.rs +++ b/src/encoder/writer.rs @@ -93,6 +93,20 @@ impl TiffWriter { Ok(()) } + pub fn write_f32(&mut self, n: f32) -> Result<(), io::Error> { + self.writer.write_f32::(n)?; + self.offset += 4; + + Ok(()) + } + + pub fn write_f64(&mut self, n: f64) -> Result<(), io::Error> { + self.writer.write_f64::(n)?; + self.offset += 8; + + Ok(()) + } + pub fn pad_word_boundary(&mut self) -> Result<(), io::Error> { if self.offset % 4 != 0 { let padding = [0, 0, 0]; diff --git a/src/tags.rs b/src/tags.rs index 21b9709..240873e 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -127,6 +127,8 @@ pub enum Type(u16) { SSHORT = 8, SLONG = 9, SRATIONAL = 10, + FLOAT = 11, + DOUBLE = 12, /// BigTIFF 64-bit unsigned integer LONG8 = 16, } diff --git a/tests/encode_images.rs b/tests/encode_images.rs index 9d1ecfd..6902cff 100644 --- a/tests/encode_images.rs +++ b/tests/encode_images.rs @@ -94,6 +94,8 @@ test_roundtrip!(test_u8_roundtrip, U8, u8); test_roundtrip!(test_u16_roundtrip, U16, u16); test_roundtrip!(test_u32_roundtrip, U32, u32); test_roundtrip!(test_u64_roundtrip, U64, u64); +test_roundtrip!(test_f32_roundtrip, F32, f32); +test_roundtrip!(test_f64_roundtrip, F64, f64); #[test] fn test_gray_u8_roundtrip() { @@ -145,6 +147,26 @@ fn test_rgb_u64_roundtrip() { test_u64_roundtrip::("gradient-3c-64b.tiff", ColorType::RGB(64)); } +#[test] +fn test_gray_f32_roundtrip() { + test_f32_roundtrip::("gradient-1c-32b-float.tiff", ColorType::Gray(32)); +} + +#[test] +fn test_rgb_f32_roundtrip() { + test_f32_roundtrip::("gradient-3c-32b-float.tiff", ColorType::RGB(32)); +} + +#[test] +fn test_cmyk_f32_roundtrip() { + test_f32_roundtrip::("cmyk-3c-32b-float.tiff", ColorType::CMYK(32)); +} + +#[test] +fn test_gray_f64_roundtrip() { + test_f64_roundtrip::("gradient-1c-64b-float.tiff", ColorType::Gray(64)); +} + #[test] fn test_multiple_byte() { let mut data = Cursor::new(Vec::new()); From ae4ae9c36e6bca4157e71935665020612e871b6c Mon Sep 17 00:00:00 2001 From: Andrew Champion Date: Mon, 10 Feb 2020 12:44:14 +0000 Subject: [PATCH 06/14] Fix capitalization case of sample format enum --- src/decoder/mod.rs | 6 +++--- src/encoder/colortype.rs | 32 ++++++++++++++++---------------- src/tags.rs | 6 +++--- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 6ec0168..3e3ab97 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -306,7 +306,7 @@ impl Decoder { height: 0, bits_per_sample: vec![1], samples: 1, - sample_format: vec![SampleFormat::UINT], + sample_format: vec![SampleFormat::Uint], photometric_interpretation: PhotometricInterpretation::BlackIsZero, compression_method: CompressionMethod::None, strip_decoder: None, @@ -884,8 +884,8 @@ impl Decoder { let buffer_size = usize::try_from(self.width)? * height * self.bits_per_sample.len(); let max_sample_bits = self.bits_per_sample.iter().cloned().max().unwrap_or(8); - match self.sample_format.first().unwrap_or(&SampleFormat::UINT) { - SampleFormat::UINT => match max_sample_bits { + match self.sample_format.first().unwrap_or(&SampleFormat::Uint) { + SampleFormat::Uint => match max_sample_bits { n if n <= 8 => DecodingResult::new_u8(buffer_size, &self.limits), n if n <= 16 => DecodingResult::new_u16(buffer_size, &self.limits), n if n <= 32 => DecodingResult::new_u32(buffer_size, &self.limits), diff --git a/src/encoder/colortype.rs b/src/encoder/colortype.rs index 9ee9bc6..3311388 100644 --- a/src/encoder/colortype.rs +++ b/src/encoder/colortype.rs @@ -17,7 +17,7 @@ impl ColorType for Gray8 { type Inner = u8; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const BITS_PER_SAMPLE: &'static [u16] = &[8]; - const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::UINT]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint]; } pub struct Gray16; @@ -25,7 +25,7 @@ impl ColorType for Gray16 { type Inner = u16; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const BITS_PER_SAMPLE: &'static [u16] = &[16]; - const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::UINT]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint]; } pub struct Gray32; @@ -33,7 +33,7 @@ impl ColorType for Gray32 { type Inner = u32; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const BITS_PER_SAMPLE: &'static [u16] = &[32]; - const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::UINT]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint]; } pub struct Gray32Float; @@ -49,7 +49,7 @@ impl ColorType for Gray64 { type Inner = u64; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const BITS_PER_SAMPLE: &'static [u16] = &[64]; - const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::UINT]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint]; } pub struct Gray64Float; @@ -65,7 +65,7 @@ impl ColorType for RGB8 { type Inner = u8; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; 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]; } pub struct RGB16; @@ -73,7 +73,7 @@ impl ColorType for RGB16 { type Inner = u16; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; 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]; } pub struct RGB32; @@ -81,7 +81,7 @@ impl ColorType for RGB32 { type Inner = u32; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; 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]; } pub struct RGB32Float; @@ -97,7 +97,7 @@ impl ColorType for RGB64 { type Inner = u64; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; 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]; } pub struct RGB64Float; @@ -113,7 +113,7 @@ impl ColorType for RGBA8 { type Inner = u8; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; 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]; } pub struct RGBA16; @@ -121,7 +121,7 @@ impl ColorType for RGBA16 { type Inner = u16; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; 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]; } pub struct RGBA32; @@ -129,7 +129,7 @@ impl ColorType for RGBA32 { type Inner = u32; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; 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]; } pub struct RGBA32Float; @@ -145,7 +145,7 @@ impl ColorType for RGBA64 { type Inner = u64; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; 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]; } pub struct RGBA64Float; @@ -161,7 +161,7 @@ impl ColorType for CMYK8 { type Inner = u8; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; 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]; } pub struct CMYK16; @@ -169,7 +169,7 @@ impl ColorType for CMYK16 { type Inner = u16; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; 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]; } pub struct CMYK32; @@ -177,7 +177,7 @@ impl ColorType for CMYK32 { type Inner = u32; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; 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]; } pub struct CMYK32Float; @@ -193,7 +193,7 @@ impl ColorType for CMYK64 { type Inner = u64; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; 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]; } pub struct CMYK64Float; diff --git a/src/tags.rs b/src/tags.rs index 240873e..c1449ca 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -187,9 +187,9 @@ pub enum ResolutionUnit(u16) { tags! { pub enum SampleFormat(u16) { - UINT = 1, - INT = 2, + Uint = 1, + Int = 2, IEEEFP = 3, - VOID = 4, + Void = 4, } } \ No newline at end of file From d79d8c3353f821b6b13fbcb27dbba8e46f37daa3 Mon Sep 17 00:00:00 2001 From: Andrew Champion Date: Mon, 10 Feb 2020 12:44:35 +0000 Subject: [PATCH 07/14] Make sample format FIXME a TODO (minor) --- src/decoder/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 3e3ab97..f4d00f1 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -429,7 +429,7 @@ impl Decoder { ) .collect::>()?; - // FIXME: for now, only homogenous formats across samples are supported. + // TODO: for now, only homogenous formats across samples are supported. if !self.sample_format.windows(2).all(|s| s[0] == s[1]) { return Err(TiffUnsupportedError::UnsupportedSampleFormat(vals).into()) } From 0ec8e14910040b310e2f98936a15b8156c3638e3 Mon Sep 17 00:00:00 2001 From: Andrew Champion Date: Mon, 10 Feb 2020 13:11:08 +0000 Subject: [PATCH 08/14] Tags: generate from_u16_exhaustive for all tags with unknown variants --- src/tags.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/tags.rs b/src/tags.rs index c1449ca..e4813ef 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -39,16 +39,24 @@ macro_rules! tags { } } - tags!($name, $ty); + tags!($name, $ty, $($unknown_doc)*); }; // For u16 tags, provide direct inherent primitive conversion methods. - ($name:tt, u16) => { + ($name:tt, u16, $($unknown_doc:literal)*) => { impl $name { #[inline(always)] pub fn from_u16(val: u16) -> Option { Self::__from_inner_type(val).ok() } + $( + #[inline(always)] + pub fn from_u16_exhaustive(val: u16) -> Self { + $unknown_doc; + Self::__from_inner_type(val).unwrap_or_else(|_| $name::Unknown(val)) + } + )* + #[inline(always)] pub fn to_u16(&self) -> u16 { Self::__to_inner_type(self) @@ -57,7 +65,7 @@ macro_rules! tags { }; // For other tag types, do nothing for now. With concat_idents one could // provide inherent conversion methods for all types. - ($name:tt, $ty:tt) => {}; + ($name:tt, $ty:tt, $($unknown_doc:literal)*) => {}; } // Note: These tags appear in the order they are mentioned in the TIFF reference @@ -109,12 +117,6 @@ pub enum Tag(u16) unknown("A private or extension tag") { } } -impl Tag { - pub fn from_u16_exhaustive(val: u16) -> Self { - Self::from_u16(val).unwrap_or_else(|| Tag::Unknown(val)) - } -} - tags! { /// The type of an IFD entry (a 2 byte field). pub enum Type(u16) { From 71f3aa8b15d85bbe8f98ac969204d96c2652f84a Mon Sep 17 00:00:00 2001 From: Andrew Champion Date: Mon, 10 Feb 2020 13:12:50 +0000 Subject: [PATCH 09/14] Include unknown variant for sample format Since it is known that libtiff produces other sample format variants we do not support, represent these as variants. This also allows returning the sample formats in errors so that composition of formats and known and unknown formats are clearly reported. --- src/decoder/mod.rs | 12 +++++------- src/error.rs | 4 ++-- src/tags.rs | 2 +- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index f4d00f1..398280e 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -423,15 +423,13 @@ impl Decoder { self.samples = val; } if let Some(vals) = self.find_tag_unsigned_vec(Tag::SampleFormat)? { - self.sample_format = vals.iter() - .map(|&v| SampleFormat::from_u16(v) - .ok_or_else(|| TiffUnsupportedError::UnsupportedSampleFormat(vals.clone())) - ) - .collect::>()?; + self.sample_format = vals.into_iter() + .map(SampleFormat::from_u16_exhaustive) + .collect(); // TODO: for now, only homogenous formats across samples are supported. if !self.sample_format.windows(2).all(|s| s[0] == s[1]) { - return Err(TiffUnsupportedError::UnsupportedSampleFormat(vals).into()) + return Err(TiffUnsupportedError::UnsupportedSampleFormat(self.sample_format.clone()).into()) } } match self.samples { @@ -905,7 +903,7 @@ impl Decoder { )) } }, - format => Err(TiffUnsupportedError::UnsupportedSampleFormat(vec![format.to_u16()]).into()), + format => Err(TiffUnsupportedError::UnsupportedSampleFormat(vec![format.clone()]).into()), } } diff --git a/src/error.rs b/src/error.rs index 4c965fd..a86f404 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,7 +4,7 @@ use std::io; use std::string; use decoder::ifd::{Value}; -use tags::{CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Tag}; +use tags::{CompressionMethod, PhotometricInterpretation, PlanarConfiguration, SampleFormat, Tag}; use miniz_oxide::inflate::TINFLStatus; use ColorType; @@ -114,7 +114,7 @@ pub enum TiffUnsupportedError { UnknownCompressionMethod, UnsupportedCompressionMethod(CompressionMethod), UnsupportedSampleDepth(u8), - UnsupportedSampleFormat(Vec), + UnsupportedSampleFormat(Vec), UnsupportedColorType(ColorType), UnsupportedBitsPerChannel(u8), UnsupportedPlanarConfig(Option), diff --git a/src/tags.rs b/src/tags.rs index e4813ef..f13034d 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -188,7 +188,7 @@ pub enum ResolutionUnit(u16) { } tags! { -pub enum SampleFormat(u16) { +pub enum SampleFormat(u16) unknown("An unknown extension sample format") { Uint = 1, Int = 2, IEEEFP = 3, From 54c3ebf358ffa6b115d8028744f3285683f0f19d Mon Sep 17 00:00:00 2001 From: Etienne Prothon Date: Tue, 4 Aug 2020 16:55:48 +0200 Subject: [PATCH 10/14] Merge with master and remove duplicate SampleFormat tag --- src/tags.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tags.rs b/src/tags.rs index 3983f09..fb96a5d 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -103,7 +103,6 @@ pub enum Tag(u16) unknown("A private or extension tag") { PlanarConfiguration = 284, ResolutionUnit = 296, // TODO add support RowsPerStrip = 278, - SampleFormat = 339, SamplesPerPixel = 277, Software = 305, StripByteCounts = 279, From 3b0da4d1ae3cb58ea47357319a6c9e3915216311 Mon Sep 17 00:00:00 2001 From: Etienne Prothon Date: Wed, 5 Aug 2020 14:25:02 +0200 Subject: [PATCH 11/14] feat: Add decoding value and get tags as f32 or f64 --- src/decoder/ifd.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++-- src/decoder/mod.rs | 22 ++++++++++++++++ src/error.rs | 2 +- 3 files changed, 83 insertions(+), 3 deletions(-) diff --git a/src/decoder/ifd.rs b/src/decoder/ifd.rs index 444597e..7276069 100644 --- a/src/decoder/ifd.rs +++ b/src/decoder/ifd.rs @@ -9,14 +9,16 @@ use super::stream::{ByteOrder, EndianReader, SmartReader}; use tags::{Tag, Type}; use {TiffError, TiffFormatError, TiffResult, TiffUnsupportedError}; -use self::Value::{Ascii, List, Rational, Unsigned, Signed, SRational}; +use self::Value::{Ascii, List, Rational, Unsigned, Signed, SRational, Float, Double}; #[allow(unused_qualifications)] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq)] pub enum Value { Signed(i32), Unsigned(u32), + Float(f32), + Double(f64), List(Vec), Rational(u32, u32), SRational(i32, i32), @@ -44,6 +46,24 @@ impl Value { } } + pub fn into_f32(self) -> TiffResult { + match self { + Float(val) => Ok(val), + val => Err(TiffError::FormatError( + TiffFormatError::SignedIntegerExpected(val), + )), + } + } + + pub fn into_f64(self) -> TiffResult { + match self { + Double(val) => Ok(val), + val => Err(TiffError::FormatError( + TiffFormatError::SignedIntegerExpected(val), + )), + } + } + pub fn into_u32_vec(self) -> TiffResult> { match self { List(vec) => { @@ -84,6 +104,38 @@ impl Value { )), } } + + pub fn into_f32_vec(self) -> TiffResult> { + match self { + List(vec) => { + let mut new_vec = Vec::with_capacity(vec.len()); + for v in vec { + new_vec.push(v.into_f32()?) + } + Ok(new_vec) + } + Float(val) => Ok(vec![val]), + val => Err(TiffError::FormatError( + TiffFormatError::UnsignedIntegerExpected(val), + )), + } + } + + pub fn into_f64_vec(self) -> TiffResult> { + match self { + List(vec) => { + let mut new_vec = Vec::with_capacity(vec.len()); + for v in vec { + new_vec.push(v.into_f64()?) + } + Ok(new_vec) + } + Double(val) => Ok(vec![val]), + val => Err(TiffError::FormatError( + TiffFormatError::UnsignedIntegerExpected(val), + )), + } + } } #[derive(Clone)] @@ -169,6 +221,12 @@ impl Entry { (Type::SLONG, n) => self.decode_offset(n, bo, limits, decoder, |decoder| { Ok(Signed(decoder.read_slong()?)) }), + (Type::FLOAT, n) => self.decode_offset(n, bo, limits, decoder, |decoder| { + Ok(Float(decoder.read_float()?)) + }), + (Type::DOUBLE, n) => self.decode_offset(n, bo, limits, decoder, |decoder| { + Ok(Double(decoder.read_double()?)) + }), (Type::RATIONAL, 1) => { decoder.goto_offset(self.r(bo).read_u32()?)?; let numerator = decoder.read_long()?; diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 318af19..c4b8fed 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -483,6 +483,18 @@ impl Decoder { self.reader.read_i32() } + /// Reads a TIFF float value + #[inline] + pub fn read_float(&mut self) -> Result { + self.reader.read_f32() + } + + /// Reads a TIFF double value + #[inline] + pub fn read_double(&mut self) -> Result { + self.reader.read_f64() + } + /// Reads a string #[inline] pub fn read_string(&mut self, length: usize) -> TiffResult { @@ -625,6 +637,16 @@ impl Decoder { self.get_tag(tag)?.into_u32_vec() } + /// Tries to retrieve a tag and convert it to the desired type. + pub fn get_tag_f32_vec(&mut self, tag: Tag) -> TiffResult> { + self.get_tag(tag)?.into_f32_vec() + } + + /// Tries to retrieve a tag and convert it to the desired type. + pub fn get_tag_f64_vec(&mut self, tag: Tag) -> TiffResult> { + self.get_tag(tag)?.into_f64_vec() + } + /// Decompresses the strip into the supplied buffer. /// Returns the number of bytes read. fn expand_strip<'a>( diff --git a/src/error.rs b/src/error.rs index a86f404..32f50bf 100644 --- a/src/error.rs +++ b/src/error.rs @@ -35,7 +35,7 @@ pub enum TiffError { /// /// The list of variants may grow to incorporate errors of future features. Matching against this /// exhaustively is not covered by interface stability guarantees. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq)] pub enum TiffFormatError { TiffSignatureNotFound, TiffSignatureInvalid, From 768e0d4af7c742cd08777556085b8d542583f7d0 Mon Sep 17 00:00:00 2001 From: Etienne Prothon Date: Wed, 5 Aug 2020 15:34:28 +0200 Subject: [PATCH 12/14] feat: Add tag to single f32 or f64 --- src/decoder/mod.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index c4b8fed..fced943 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -632,6 +632,16 @@ impl Decoder { self.get_tag(tag)?.into_u32() } + /// Tries to retrieve a tag and convert it to the desired type. + pub fn get_tag_f32(&mut self, tag: Tag) -> TiffResult { + self.get_tag(tag)?.into_f32() + } + + /// Tries to retrieve a tag and convert it to the desired type. + pub fn get_tag_f64(&mut self, tag: Tag) -> TiffResult { + self.get_tag(tag)?.into_f64() + } + /// Tries to retrieve a tag and convert it to the desired type. pub fn get_tag_u32_vec(&mut self, tag: Tag) -> TiffResult> { self.get_tag(tag)?.into_u32_vec() From 200355d3bc74bee307ac047072249a8e99b1d4f8 Mon Sep 17 00:00:00 2001 From: Etienne Prothon Date: Mon, 17 Aug 2020 18:32:43 +0200 Subject: [PATCH 13/14] Mark decoding buffer f32 and f64 as unimplemented --- src/decoder/mod.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index fced943..07e6a47 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -283,11 +283,13 @@ fn rev_hpredict(image: DecodingBuffer, size: (u32, u32), color_type: ColorType) DecodingBuffer::U64(buf) => { rev_hpredict_nsamp(buf, size, samples)?; } - DecodingBuffer::F32(buf) => { - rev_hpredict_nsamp(buf, size, samples)?; + DecodingBuffer::F32(_buf) => { + unimplemented!() + // rev_hpredict_nsamp(buf, size, samples)?; } - DecodingBuffer::F64(buf) => { - rev_hpredict_nsamp(buf, size, samples)?; + DecodingBuffer::F64(_buf) => { + unimplemented!() + // rev_hpredict_nsamp(buf, size, samples)?; } } Ok(()) From d8bfa84d06112900bc46989e4edf4bf9b970ea5a Mon Sep 17 00:00:00 2001 From: Andreas Molzer Date: Mon, 31 Aug 2020 19:53:06 +0200 Subject: [PATCH 14/14] Add notes for unsupported predictors --- src/decoder/mod.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 07e6a47..d559dd9 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -284,12 +284,20 @@ fn rev_hpredict(image: DecodingBuffer, size: (u32, u32), color_type: ColorType) rev_hpredict_nsamp(buf, size, samples)?; } DecodingBuffer::F32(_buf) => { - unimplemented!() + // FIXME: check how this is defined. + // See issue #89. // rev_hpredict_nsamp(buf, size, samples)?; + return Err(TiffError::UnsupportedError( + TiffUnsupportedError::HorizontalPredictor(color_type) + )); } DecodingBuffer::F64(_buf) => { - unimplemented!() + //FIXME: check how this is defined. + // See issue #89. // rev_hpredict_nsamp(buf, size, samples)?; + return Err(TiffError::UnsupportedError( + TiffUnsupportedError::HorizontalPredictor(color_type) + )); } } Ok(())