Repository: image-rs/image-tiff
Pull request #70 (2020-02-06 20:39:20)

Floating point sample support
Opportunities here for cleanup and deduplication, but those can come later.


Patch:
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<Value>),
     Rational(u32, u32),
     RationalBig(u64, u64),
@@ -70,6 +72,24 @@ impl Value {
         }
     }
 
+    pub fn into_f32(self) -> TiffResult<f32> {
+        match self {
+            Float(val) => Ok(val),
+            val => Err(TiffError::FormatError(
+                TiffFormatError::SignedIntegerExpected(val),
+            )),
+        }
+    }
+
+    pub fn into_f64(self) -> TiffResult<f64> {
+        match self {
+            Double(val) => Ok(val),
+            val => Err(TiffError::FormatError(
+                TiffFormatError::SignedIntegerExpected(val),
+            )),
+        }
+    }
+
     pub fn into_u32_vec(self) -> TiffResult<Vec<u32>> {
         match self {
             List(vec) => {
@@ -119,6 +139,38 @@ impl Value {
         }
     }
 
+    pub fn into_f32_vec(self) -> TiffResult<Vec<f32>> {
+        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<Vec<f64>> {
+        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<Vec<u64>> {
         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<u32>),
     /// A vector of 64 bit unsigned ints
     U64(Vec<u64>),
+    /// A vector of 32 bit IEEE floats
+    F32(Vec<f32>),
+    /// A vector of 64 bit IEEE floats
+    F64(Vec<f64>),
 }
 
 impl DecodingResult {
@@ -59,12 +63,30 @@ impl DecodingResult {
         }
     }
 
+    fn new_f32(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
+        if size > limits.decoding_buffer_size / std::mem::size_of::<f32>() {
+            Err(TiffError::LimitsExceeded)
+        } else {
+            Ok(DecodingResult::F32(vec![0.0; size]))
+        }
+    }
+
+    fn new_f64(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
+        if size > limits.decoding_buffer_size / std::mem::size_of::<f64>() {
+            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<u8>,
     samples: u8,
+    sample_format: Vec<SampleFormat>,
     photometric_interpretation: PhotometricInterpretation,
     compression_method: CompressionMethod,
     strip_decoder: Option<StripDecodeState>,
@@ -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<T>(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<R: Read + Seek> Decoder<R> {
             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<R: Read + Seek> Decoder<R> {
         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<R: Read + Seek> Decoder<R> {
         self.reader.read_i32()
     }
 
+    /// Reads a TIFF float value
+    #[inline]
+    pub fn read_float(&mut self) -> Result<f32, io::Error> {
+        self.reader.read_f32()
+    }
+
+    /// Reads a TIFF double value
+    #[inline]
+    pub fn read_double(&mut self) -> Result<f64, io::Error> {
+        self.reader.read_f64()
+    }
+
     #[inline]
     pub fn read_long8(&mut self) -> Result<u64, io::Error> {
         self.reader.read_u64()
     }
 
-
     /// Reads a string
     #[inline]
     pub fn read_string(&mut self, length: usize) -> TiffResult<String> {
@@ -620,6 +703,16 @@ impl<R: Read + Seek> Decoder<R> {
         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<f32> {
+        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<f64> {
+        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<Vec<u32>> {
         self.get_tag(tag)?.into_u32_vec()
@@ -628,6 +721,16 @@ impl<R: Read + Seek> Decoder<R> {
         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<Vec<f32>> {
+        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<Vec<f64>> {
+        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<R: Read + Seek> Decoder<R> {
                 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<R: Read + Seek> Decoder<R> {
                 }
                 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<R: Read + Seek> Decoder<R> {
     fn result_buffer(&self, height: usize) -> TiffResult<DecodingResult> {
         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<f32, io::Error> {
+        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<f64, io::Error> {
+        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<W: Write>(&self, writer: &mut TiffWriter<W>) -> 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<W: Write>(&self, writer: &mut TiffWriter<W>) -> 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<W: Write>(&self, writer: &mut TiffWriter<W>) -> 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<W: Write>(&self, writer: &mut TiffWriter<W>) -> 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, <T>::BITS_PER_SAMPLE)?;
+        let sample_format: Vec<_> = <T>::SAMPLE_FORMAT.iter().map(|s| s.to_u16()).collect();
+        encoder.write_tag(Tag::SampleFormat, &sample_format[..])?;
         encoder.write_tag(Tag::PhotometricInterpretation, <T>::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<W: Write> TiffWriter<W> {
         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<SampleFormat>),
     UnsupportedColorType(ColorType),
     UnsupportedBitsPerChannel(u8),
     UnsupportedPlanarConfig(Option<PlanarConfiguration>),
@@ -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> {
                 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


Test patch:
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::<colortype::RGB64>("gradient-3c-64b.tiff", ColorType::RGB(64));
 }
 
+#[test]
+fn test_gray_f32_roundtrip() {
+    test_f32_roundtrip::<colortype::Gray32Float>("gradient-1c-32b-float.tiff", ColorType::Gray(32));
+}
+
+#[test]
+fn test_rgb_f32_roundtrip() {
+    test_f32_roundtrip::<colortype::RGB32Float>("gradient-3c-32b-float.tiff", ColorType::RGB(32));
+}
+
+#[test]
+fn test_cmyk_f32_roundtrip() {
+    test_f32_roundtrip::<colortype::CMYK32Float>("cmyk-3c-32b-float.tiff", ColorType::CMYK(32));
+}
+
+#[test]
+fn test_gray_f64_roundtrip() {
+    test_f64_roundtrip::<colortype::Gray64Float>("gradient-1c-64b-float.tiff", ColorType::Gray(64));
+}
+
 #[test]
 fn test_multiple_byte() {
     let mut data = Cursor::new(Vec::new());