Repository: image-rs/image-tiff
Pull request #114 (2020-12-08 11:47:02)

Int8/16 support
This PR adds support for TIF files with a datatype of `int16`. Two `int16` files - gray and RGB - were generated using Python's `tifffile` and the decoding results here match theirs.


Patch:
diff --git a/src/bytecast.rs b/src/bytecast.rs
index 906ac79..0d8869f 100644
--- a/src/bytecast.rs
+++ b/src/bytecast.rs
@@ -23,9 +23,9 @@ macro_rules! integral_slice_as_bytes{($int:ty, $const:ident $(,$mut:ident)*) =>
     })*
 }}
 
-integral_slice_as_bytes!(i8, i8_as_ne_bytes);
+integral_slice_as_bytes!(i8, i8_as_ne_bytes, i8_as_ne_mut_bytes);
 integral_slice_as_bytes!(u16, u16_as_ne_bytes, u16_as_ne_mut_bytes);
-integral_slice_as_bytes!(i16, i16_as_ne_bytes);
+integral_slice_as_bytes!(i16, i16_as_ne_bytes, i16_as_ne_mut_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);
diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs
index 6ff7163..8ac4512 100644
--- a/src/decoder/mod.rs
+++ b/src/decoder/mod.rs
@@ -32,6 +32,10 @@ pub enum DecodingResult {
     F32(Vec<f32>),
     /// A vector of 64 bit IEEE floats
     F64(Vec<f64>),
+    /// A vector of 8 bit signed ints
+    I8(Vec<i8>),
+    /// A vector of 16 bit signed ints
+    I16(Vec<i16>),
 }
 
 impl DecodingResult {
@@ -83,6 +87,22 @@ impl DecodingResult {
         }
     }
 
+    fn new_i8(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
+        if size > limits.decoding_buffer_size / std::mem::size_of::<i8>() {
+            Err(TiffError::LimitsExceeded)
+        } else {
+            Ok(DecodingResult::I8(vec![0; size]))
+        }
+    }
+
+    fn new_i16(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
+        if size > limits.decoding_buffer_size / 2 {
+            Err(TiffError::LimitsExceeded)
+        } else {
+            Ok(DecodingResult::I16(vec![0; size]))
+        }
+    }
+
     pub fn as_buffer(&mut self, start: usize) -> DecodingBuffer {
         match *self {
             DecodingResult::U8(ref mut buf) => DecodingBuffer::U8(&mut buf[start..]),
@@ -91,6 +111,8 @@ impl DecodingResult {
             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..]),
+            DecodingResult::I8(ref mut buf) => DecodingBuffer::I8(&mut buf[start..]),
+            DecodingResult::I16(ref mut buf) => DecodingBuffer::I16(&mut buf[start..]),
         }
     }
 }
@@ -109,6 +131,10 @@ pub enum DecodingBuffer<'a> {
     F32(&'a mut [f32]),
     /// A slice of 64 bit IEEE floats
     F64(&'a mut [f64]),
+    /// A slice of 8 bits signed ints
+    I8(&'a mut [i8]),
+    /// A slice of 16 bits signed ints
+    I16(&'a mut [i16]),
 }
 
 impl<'a> DecodingBuffer<'a> {
@@ -120,6 +146,8 @@ impl<'a> DecodingBuffer<'a> {
             DecodingBuffer::U64(ref buf) => buf.len(),
             DecodingBuffer::F32(ref buf) => buf.len(),
             DecodingBuffer::F64(ref buf) => buf.len(),
+            DecodingBuffer::I8(ref buf) => buf.len(),
+            DecodingBuffer::I16(ref buf) => buf.len(),
         }
     }
 
@@ -131,6 +159,8 @@ impl<'a> DecodingBuffer<'a> {
             DecodingBuffer::U64(_) => 8,
             DecodingBuffer::F32(_) => 4,
             DecodingBuffer::F64(_) => 8,
+            DecodingBuffer::I8(_) => 1,
+            DecodingBuffer::I16(_) => 2,
         }
     }
 
@@ -145,6 +175,8 @@ impl<'a> DecodingBuffer<'a> {
             DecodingBuffer::U64(ref mut buf) => DecodingBuffer::U64(buf),
             DecodingBuffer::F32(ref mut buf) => DecodingBuffer::F32(buf),
             DecodingBuffer::F64(ref mut buf) => DecodingBuffer::F64(buf),
+            DecodingBuffer::I8(ref mut buf) => DecodingBuffer::I8(buf),
+            DecodingBuffer::I16(ref mut buf) => DecodingBuffer::I16(buf),
         }
     }
 }
@@ -257,6 +289,18 @@ impl Wrapping for u64 {
     }
 }
 
+impl Wrapping for i8 {
+    fn wrapping_add(&self, other: Self) -> Self {
+        i8::wrapping_add(*self, other)
+    }
+}
+
+impl Wrapping for i16 {
+    fn wrapping_add(&self, other: Self) -> Self {
+        i16::wrapping_add(*self, other)
+    }
+}
+
 fn rev_hpredict_nsamp<T>(image: &mut [T], size: (u32, u32), samples: usize) -> TiffResult<()>
 where
     T: Copy + Wrapping,
@@ -320,6 +364,12 @@ fn rev_hpredict(image: DecodingBuffer, size: (u32, u32), color_type: ColorType)
                 TiffUnsupportedError::HorizontalPredictor(color_type),
             ));
         }
+        DecodingBuffer::I8(buf) => {
+            rev_hpredict_nsamp(buf, size, samples)?;
+        }
+        DecodingBuffer::I16(buf) => {
+            rev_hpredict_nsamp(buf, size, samples)?;
+        }
     }
     Ok(())
 }
@@ -872,6 +922,19 @@ impl<R: Read + Seek> Decoder<R> {
                 reader.read_u64_into(&mut buffer[..bytes / 8])?;
                 bytes / 8
             }
+            (ColorType::RGBA(8), DecodingBuffer::I8(ref mut buffer))
+            | (ColorType::RGB(8), DecodingBuffer::I8(ref mut buffer))
+            | (ColorType::CMYK(8), DecodingBuffer::I8(ref mut buffer)) => {
+                reader.read_i8_into(&mut buffer[..bytes])?;
+                bytes
+            }
+            (ColorType::RGBA(16), DecodingBuffer::I16(ref mut buffer))
+            | (ColorType::RGB(16), DecodingBuffer::I16(ref mut buffer))
+            | (ColorType::CMYK(16), DecodingBuffer::I16(ref mut buffer)) => {
+                reader.read_i16_into(&mut buffer[..bytes / 2])?;
+                bytes / 2
+            }
+
             (ColorType::Gray(64), DecodingBuffer::U64(ref mut buffer)) => {
                 reader.read_u64_into(&mut buffer[..bytes / 8])?;
                 if self.photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
@@ -899,11 +962,31 @@ impl<R: Read + Seek> Decoder<R> {
                 }
                 bytes / 2
             }
+            (ColorType::Gray(8), DecodingBuffer::I8(ref mut buffer)) => {
+                reader.read_i8_into(&mut buffer[..bytes])?;
+                if self.photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
+                    for datum in buffer[..bytes].iter_mut() {
+                        *datum = !*datum;
+                    }
+                }
+                bytes
+            }
+            // The following conversions interpret the image as in libtiff.
+            // In particular, MIN is white and MAX is black and not Zero as the name would imply.
+            (ColorType::Gray(16), DecodingBuffer::I16(ref mut buffer)) => {
+                reader.read_i16_into(&mut buffer[..bytes / 2])?;
+                if self.photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
+                    for datum in buffer[..bytes / 2].iter_mut() {
+                        *datum = !*datum;
+                    }
+                }
+                bytes / 2
+            }
             (ColorType::Gray(n), DecodingBuffer::U8(ref mut buffer)) if n <= 8 => {
                 reader.read_exact(&mut buffer[..bytes])?;
                 if self.photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
                     for byte in buffer[..bytes].iter_mut() {
-                        *byte = 0xff - *byte
+                        *byte = !*byte;
                     }
                 }
                 bytes
@@ -1017,7 +1100,6 @@ impl<R: Read + Seek> Decoder<R> {
 
     pub fn read_strip_to_buffer(&mut self, mut buffer: DecodingBuffer) -> TiffResult<()> {
         self.initialize_strip_decoder()?;
-
         let index = self.strip_decoder.as_ref().unwrap().strip_index;
         let offset = *self
             .strip_decoder
@@ -1037,7 +1119,6 @@ impl<R: Read + Seek> Decoder<R> {
             .ok_or(TiffError::FormatError(
                 TiffFormatError::InconsistentSizesEncountered,
             ))?;
-
         let tag_rows = self.get_tag_u32(Tag::RowsPerStrip).unwrap_or(self.height);
         let rows_per_strip = usize::try_from(tag_rows)?;
 
@@ -1055,13 +1136,11 @@ impl<R: Read + Seek> Decoder<R> {
         }
 
         let units_read = self.expand_strip(buffer.copy(), offset, byte_count, buffer_size)?;
-
         self.strip_decoder.as_mut().unwrap().strip_index += 1;
 
         if u32::try_from(index)? == self.strip_count()? {
             self.strip_decoder = None;
         }
-
         if units_read < buffer_size {
             return Err(TiffError::FormatError(
                 TiffFormatError::InconsistentStripSamples {
@@ -1112,6 +1191,13 @@ impl<R: Read + Seek> Decoder<R> {
                     TiffUnsupportedError::UnsupportedBitsPerChannel(n),
                 )),
             },
+            SampleFormat::Int => match max_sample_bits {
+                n if n <= 8 => DecodingResult::new_i8(buffer_size, &self.limits),
+                n if (n > 8) & (n <= 16) => DecodingResult::new_i16(buffer_size, &self.limits),
+                n => Err(TiffError::UnsupportedError(
+                    TiffUnsupportedError::UnsupportedBitsPerChannel(n),
+                )),
+            },
             format => {
                 Err(TiffUnsupportedError::UnsupportedSampleFormat(vec![format.clone()]).into())
             }
@@ -1132,7 +1218,6 @@ impl<R: Read + Seek> Decoder<R> {
         );
 
         let mut result = self.result_buffer(strip_height)?;
-
         self.read_strip_to_buffer(result.as_buffer(0))?;
 
         Ok(result)
@@ -1154,7 +1239,8 @@ impl<R: Read + Seek> Decoder<R> {
         let mut result = self.result_buffer(usize::try_from(self.height)?)?;
 
         for i in 0..usize::try_from(self.strip_count()?)? {
-            self.read_strip_to_buffer(result.as_buffer(samples_per_strip * i))?;
+            let r = result.as_buffer(samples_per_strip * i);
+            self.read_strip_to_buffer(r)?;
         }
         Ok(result)
     }
diff --git a/src/decoder/stream.rs b/src/decoder/stream.rs
index 9b89046..862da76 100644
--- a/src/decoder/stream.rs
+++ b/src/decoder/stream.rs
@@ -48,6 +48,35 @@ pub trait EndianReader: Read {
         Ok(())
     }
 
+    /// Reads an i8
+    #[inline(always)]
+    fn read_i8(&mut self) -> Result<i8, io::Error> {
+        let mut n = [0u8; 1];
+        self.read_exact(&mut n)?;
+        Ok(match self.byte_order() {
+            ByteOrder::LittleEndian => i8::from_le_bytes(n),
+            ByteOrder::BigEndian => i8::from_be_bytes(n),
+        })
+    }
+
+    #[inline(always)]
+    fn read_i8_into(&mut self, buffer: &mut [i8]) -> Result<(), io::Error> {
+        self.read_exact(bytecast::i8_as_ne_mut_bytes(buffer))?;
+        match self.byte_order() {
+            ByteOrder::LittleEndian => {
+                for n in buffer {
+                    *n = i8::from_le(*n);
+                }
+            }
+            ByteOrder::BigEndian => {
+                for n in buffer {
+                    *n = i8::from_be(*n);
+                }
+            }
+        }
+        Ok(())
+    }
+
     /// Reads an i16
     #[inline(always)]
     fn read_i16(&mut self) -> Result<i16, io::Error> {
@@ -59,6 +88,24 @@ pub trait EndianReader: Read {
         })
     }
 
+    #[inline(always)]
+    fn read_i16_into(&mut self, buffer: &mut [i16]) -> Result<(), io::Error> {
+        self.read_exact(bytecast::i16_as_ne_mut_bytes(buffer))?;
+        match self.byte_order() {
+            ByteOrder::LittleEndian => {
+                for n in buffer {
+                    *n = i16::from_le(*n);
+                }
+            }
+            ByteOrder::BigEndian => {
+                for n in buffer {
+                    *n = i16::from_be(*n);
+                }
+            }
+        }
+        Ok(())
+    }
+
     /// Reads an u32
     #[inline(always)]
     fn read_u32(&mut self) -> Result<u32, io::Error> {


Test patch:
diff --git a/tests/decode_images.rs b/tests/decode_images.rs
index 2336178..1af340f 100644
--- a/tests/decode_images.rs
+++ b/tests/decode_images.rs
@@ -29,7 +29,9 @@ macro_rules! test_image_sum {
 }
 
 test_image_sum!(test_image_sum_u8, U8, u64);
+test_image_sum!(test_image_sum_i8, I8, i64);
 test_image_sum!(test_image_sum_u16, U16, u64);
+test_image_sum!(test_image_sum_i16, I16, i64);
 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);
@@ -135,6 +137,26 @@ fn test_rgb_f32() {
     test_image_sum_f32("gradient-3c-32b-float.tiff", ColorType::RGB(32), 472.8405);
 }
 
+#[test]
+fn test_int8() {
+    test_image_sum_i8("int8.tif", ColorType::Gray(8), 3111)
+}
+
+#[test]
+fn test_int8_rgb() {
+    test_image_sum_i8("int8_rgb.tif", ColorType::RGB(8), -10344)
+}
+
+#[test]
+fn test_int16() {
+    test_image_sum_i16("int16.tif", ColorType::Gray(16), 354396);
+}
+
+#[test]
+fn test_int16_rgb() {
+    test_image_sum_i16("int16_rgb.tif", ColorType::RGB(16), 1063188);
+}
+
 #[test]
 fn test_string_tags() {
     // these files have null-terminated strings for their Software tag. One has extra bytes after