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 df41496..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) | | -| `BlackIsZero` | Gray(8\|16\|32\|64) | | -| `RGB` | RGB(8\|16\|32\|64), RGBA(8\|16\|32\|64) | | +| `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` | CMYK(8\|16\|32\|64) | CMYK(32\|64) | | `YCbCr` | | | | `CIELab` | | | diff --git a/src/bytecast.rs b/src/bytecast.rs index c6a0f50..906ac79 100644 --- a/src/bytecast.rs +++ b/src/bytecast.rs @@ -29,3 +29,5 @@ integral_slice_as_bytes!(i16, i16_as_ne_bytes); integral_slice_as_bytes!(u32, u32_as_ne_bytes, u32_as_ne_mut_bytes); integral_slice_as_bytes!(i32, i32_as_ne_bytes); integral_slice_as_bytes!(u64, u64_as_ne_bytes, u64_as_ne_mut_bytes); +integral_slice_as_bytes!(f32, f32_as_ne_bytes, f32_as_ne_mut_bytes); +integral_slice_as_bytes!(f64, f64_as_ne_bytes, f64_as_ne_mut_bytes); diff --git a/src/decoder/ifd.rs b/src/decoder/ifd.rs index fae499a..8667299 100644 --- a/src/decoder/ifd.rs +++ b/src/decoder/ifd.rs @@ -9,16 +9,18 @@ use super::stream::{ByteOrder, EndianReader, SmartReader}; use tags::{Tag, Type}; use {TiffError, TiffFormatError, TiffResult, TiffUnsupportedError}; -use self::Value::{Ascii, List, Rational, Unsigned, Signed, SRational, RationalBig, UnsignedBig, SignedBig, SRationalBig}; +use self::Value::{Ascii, List, Float, Double, Rational, Unsigned, Signed, SRational, RationalBig, UnsignedBig, SignedBig, SRationalBig}; #[allow(unused_qualifications)] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq)] pub enum Value { Signed(i32), SignedBig(i64), Unsigned(u32), UnsignedBig(u64), + Float(f32), + Double(f64), List(Vec), Rational(u32, u32), RationalBig(u64, u64), @@ -70,6 +72,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) => { @@ -119,6 +139,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), + )), + } + } + pub fn into_u64_vec(self) -> TiffResult> { match self { List(vec) => { @@ -337,6 +389,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, n) => self.decode_offset(n, bo, limits, decoder, |decoder| { Ok(Rational(decoder.read_long()?, decoder.read_long()?)) }), diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 43a45f8..fcb1736 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,10 @@ pub enum DecodingResult { U32(Vec), /// A vector of 64 bit unsigned ints U64(Vec), + /// A vector of 32 bit IEEE floats + F32(Vec), + /// A vector of 64 bit IEEE floats + F64(Vec), } impl DecodingResult { @@ -59,12 +63,30 @@ 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])) + } + } + + 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..]), 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..]), + DecodingResult::F64(ref mut buf) => DecodingBuffer::F64(&mut buf[start..]), } } } @@ -79,6 +101,10 @@ 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]), + /// A slice of 64 bit IEEE floats + F64(&'a mut [f64]), } impl<'a> DecodingBuffer<'a> { @@ -88,6 +114,8 @@ 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(), + DecodingBuffer::F64(ref buf) => buf.len(), } } @@ -97,6 +125,8 @@ impl<'a> DecodingBuffer<'a> { DecodingBuffer::U16(_) => 2, DecodingBuffer::U32(_) => 4, DecodingBuffer::U64(_) => 8, + DecodingBuffer::F32(_) => 4, + DecodingBuffer::F64(_) => 8, } } @@ -109,6 +139,8 @@ 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), + DecodingBuffer::F64(ref mut buf) => DecodingBuffer::F64(buf), } } } @@ -165,6 +197,7 @@ where height: u32, bits_per_sample: Vec, samples: u8, + sample_format: Vec, photometric_interpretation: PhotometricInterpretation, compression_method: CompressionMethod, strip_decoder: Option, @@ -198,6 +231,18 @@ impl Wrapping for u64 { } } +impl Wrapping for f32 { + fn wrapping_add(&self, other: Self) -> Self { + self + other + } +} + +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, @@ -239,6 +284,22 @@ fn rev_hpredict(image: DecodingBuffer, size: (u32, u32), color_type: ColorType) DecodingBuffer::U64(buf) => { rev_hpredict_nsamp(buf, size, samples)?; } + DecodingBuffer::F32(_buf) => { + // 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) => { + //FIXME: check how this is defined. + // See issue #89. + // rev_hpredict_nsamp(buf, size, samples)?; + return Err(TiffError::UnsupportedError( + TiffUnsupportedError::HorizontalPredictor(color_type) + )); + } } Ok(()) } @@ -257,6 +318,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, @@ -388,6 +450,16 @@ 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.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(self.sample_format.clone()).into()) + } + } match self.samples { 1 | 3 | 4 => if let Some(val) = self.find_tag_unsigned_vec(Tag::BitsPerSample)? { self.bits_per_sample = val; @@ -448,12 +520,23 @@ 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() + } + #[inline] pub fn read_long8(&mut self) -> Result { self.reader.read_u64() } - /// Reads a string #[inline] pub fn read_string(&mut self, length: usize) -> TiffResult { @@ -620,6 +703,16 @@ impl Decoder { self.get_tag(tag)?.into_u64() } + /// 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() @@ -628,6 +721,16 @@ impl Decoder { self.get_tag(tag)?.into_u64_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>( @@ -700,6 +803,18 @@ 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::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)) => { @@ -742,6 +857,26 @@ 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 + } + (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_), @@ -854,16 +989,29 @@ 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), + 64 => DecodingResult::new_f64(buffer_size, &self.limits), + n => { + Err(TiffError::UnsupportedError( + TiffUnsupportedError::UnsupportedBitsPerChannel(n), + )) + } + }, + format => Err(TiffUnsupportedError::UnsupportedSampleFormat(vec![format.clone()]).into()), } } diff --git a/src/decoder/stream.rs b/src/decoder/stream.rs index db9d36c..2796fe4 100644 --- a/src/decoder/stream.rs +++ b/src/decoder/stream.rs @@ -122,6 +122,60 @@ pub trait EndianReader: Read { } Ok(()) } + + /// Reads an f32 + #[inline(always)] + fn read_f32(&mut self) -> Result { + let mut n = [0u8; 4]; + self.read_exact(&mut n)?; + Ok(f32::from_bits(match self.byte_order() { + ByteOrder::LittleEndian => u32::from_le_bytes(n), + ByteOrder::BigEndian => u32::from_be_bytes(n), + })) + } + + #[inline(always)] + fn read_f32_into(&mut self, buffer: &mut [f32]) -> Result<(), io::Error> { + self.read_exact(bytecast::f32_as_ne_mut_bytes(buffer))?; + match self.byte_order() { + ByteOrder::LittleEndian => + for n in buffer { + *n = f32::from_bits(u32::from_le(n.to_bits())); + }, + ByteOrder::BigEndian => + for n in buffer { + *n = f32::from_bits(u32::from_be(n.to_bits())); + }, + } + Ok(()) + } + + /// Reads an f64 + #[inline(always)] + fn read_f64(&mut self) -> Result { + let mut n = [0u8; 8]; + self.read_exact(&mut n)?; + Ok(f64::from_bits(match self.byte_order() { + ByteOrder::LittleEndian => u64::from_le_bytes(n), + ByteOrder::BigEndian => u64::from_be_bytes(n), + })) + } + + #[inline(always)] + fn read_f64_into(&mut self, buffer: &mut [f64]) -> Result<(), io::Error> { + self.read_exact(bytecast::f64_as_ne_mut_bytes(buffer))?; + match self.byte_order() { + ByteOrder::LittleEndian => + for n in buffer { + *n = f64::from_bits(u64::from_le(n.to_bits())); + }, + ByteOrder::BigEndian => + for n in buffer { + *n = f64::from_bits(u64::from_be(n.to_bits())); + }, + } + Ok(()) + } } /// Reader that decompresses DEFLATE streams diff --git a/src/encoder/colortype.rs b/src/encoder/colortype.rs index 2f97066..3311388 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,20 +121,39 @@ 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; 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]; + 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; 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]; + 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 eb62d33..63c52b5 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -141,6 +141,38 @@ 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 = bytecast::f32_as_ne_bytes(self); + 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 = bytecast::f64_as_ne_bytes(self); + writer.write_bytes(slice)?; + Ok(()) + } +} + impl TiffValue for [Rational] { const BYTE_LEN: u32 = 8; const FIELD_TYPE: Type = Type::RATIONAL; @@ -271,6 +303,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; @@ -570,6 +630,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 bc1679d..33df28c 100644 --- a/src/encoder/writer.rs +++ b/src/encoder/writer.rs @@ -81,6 +81,20 @@ impl TiffWriter { Ok(()) } + pub fn write_f32(&mut self, n: f32) -> Result<(), io::Error> { + self.writer.write_all(&u32::to_ne_bytes(n.to_bits()))?; + self.offset += 4; + + Ok(()) + } + + pub fn write_f64(&mut self, n: f64) -> Result<(), io::Error> { + self.writer.write_all(&u64::to_ne_bytes(n.to_bits()))?; + 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/error.rs b/src/error.rs index 02acbe0..32f50bf 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; @@ -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, @@ -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 47caaff..fb96a5d 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 @@ -113,12 +121,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) { @@ -131,6 +133,8 @@ pub enum Type(u16) { SSHORT = 8, SLONG = 9, SRATIONAL = 10, + FLOAT = 11, + DOUBLE = 12, /// BigTIFF 64-bit unsigned integer LONG8 = 16, } @@ -185,4 +189,13 @@ pub enum ResolutionUnit(u16) { Inch = 2, Centimeter = 3, } +} + +tags! { +pub enum SampleFormat(u16) unknown("An unknown extension sample format") { + 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 09685e3..90d77ac 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -32,6 +32,8 @@ 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); +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 @@ -57,6 +59,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); @@ -82,6 +89,16 @@ 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_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); @@ -107,6 +124,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/encode_images.rs b/tests/encode_images.rs index 9ba1cd3..f080418 100644 --- a/tests/encode_images.rs +++ b/tests/encode_images.rs @@ -98,6 +98,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() { @@ -149,6 +151,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()); diff --git a/tests/images/cmyk-3c-32b-float.tiff b/tests/images/cmyk-3c-32b-float.tiff new file mode 100644 index 0000000..9ad77d8 Binary files /dev/null and b/tests/images/cmyk-3c-32b-float.tiff differ 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 diff --git a/tests/images/gradient-1c-64b-float.tiff b/tests/images/gradient-1c-64b-float.tiff new file mode 100644 index 0000000..a2f843c Binary files /dev/null and b/tests/images/gradient-1c-64b-float.tiff differ diff --git a/tests/images/gradient-3c-32b-float.tiff b/tests/images/gradient-3c-32b-float.tiff new file mode 100644 index 0000000..de3fa10 Binary files /dev/null and b/tests/images/gradient-3c-32b-float.tiff differ