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 0000000..7acb00f Binary files /dev/null and b/tests/images/gradient-1c-32b-float.tiff differ