From bbc49291e48361d7366ec881e145c85135f3a33e Mon Sep 17 00:00:00 2001 From: Andrew Champion Date: Wed, 11 Dec 2019 13:34:19 +0000 Subject: [PATCH] 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