Repository: image-rs/image-tiff
Pull request #111 (2020-12-05 00:43:18)

Large strip size
Closes: #107


Patch:
diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs
index e1845e8..301732e 100644
--- a/src/encoder/mod.rs
+++ b/src/encoder/mod.rs
@@ -589,6 +589,9 @@ impl<'a, W: Write + Seek> Drop for DirectoryEncoder<'a, W> {
 /// // You can encode tags here
 /// image.encoder().write_tag(Tag::Artist, "Image-tiff").unwrap();
 ///
+/// // Strip size can be configured before writing data
+/// image.rows_per_strip(2).unwrap();
+///
 /// let mut idx = 0;
 /// while image.next_strip_sample_count() > 0 {
 ///     let sample_count = image.next_strip_sample_count() as usize;
@@ -622,8 +625,9 @@ impl<'a, W: 'a + Write + Seek, T: ColorType> ImageEncoder<'a, W, T> {
         let row_samples = u64::from(width) * u64::try_from(<T>::BITS_PER_SAMPLE.len())?;
         let row_bytes = row_samples * u64::from(<T::Inner>::BYTE_LEN);
 
-        // As per tiff spec each strip should be about 8k long
-        let rows_per_strip = (8000 + row_bytes - 1) / row_bytes;
+        // Limit the strip size to prevent potential memory and security issues.
+        // Also keep the multiple strip handling 'oiled'
+        let rows_per_strip = (1_000_000 + row_bytes - 1) / row_bytes;
 
         let strip_count = (u64::from(height) + rows_per_strip - 1) / rows_per_strip;
 
@@ -755,6 +759,28 @@ impl<'a, W: 'a + Write + Seek, T: ColorType> ImageEncoder<'a, W, T> {
         self.encoder.write_tag(Tag::YResolution, value).unwrap();
     }
 
+    /// Set image number of lines per strip
+    ///
+    /// This function needs to be called before any calls to `write_data` or
+    /// `write_strip` and will return an error otherwise.
+    pub fn rows_per_strip(&mut self, value: u32) -> TiffResult<()> {
+        if self.strip_idx != 0 {
+            return Err(io::Error::new(
+                io::ErrorKind::InvalidInput,
+                "Cannot change strip size after data was written",
+            )
+            .into());
+        }
+        // Write tag as 32 bits
+        self.encoder.write_tag(Tag::RowsPerStrip, value)?;
+
+        let value: u64 = value as u64;
+        self.strip_count = (self.height as u64 + value - 1) / value;
+        self.rows_per_strip = value;
+
+        Ok(())
+    }
+
     fn finish_internal(&mut self) -> TiffResult<()> {
         self.encoder
             .write_tag(Tag::StripOffsets, &*self.strip_offsets)?;


Test patch:
diff --git a/tests/encode_images.rs b/tests/encode_images.rs
index e5c6a8c..7ba8eb3 100644
--- a/tests/encode_images.rs
+++ b/tests/encode_images.rs
@@ -360,3 +360,42 @@ fn test_multipage_image() {
         assert_eq!(img_decoder.dimensions().unwrap(), (3, 3));
     }
 }
+
+#[test]
+/// verify rows per strip setting
+fn test_rows_per_strip() {
+    let mut file = Cursor::new(Vec::new());
+    {
+        let mut img_encoder = TiffEncoder::new(&mut file).unwrap();
+
+        let mut image = img_encoder.new_image::<colortype::Gray8>(100, 100).unwrap();
+        assert_eq!(image.next_strip_sample_count(), 100 * 100);
+        image.rows_per_strip(2).unwrap();
+        assert_eq!(image.next_strip_sample_count(), 2 * 100);
+
+        let img2: Vec<u8> = vec![0; 2 * 100];
+        image.write_strip(&img2[..]).unwrap();
+        assert!(image.rows_per_strip(5).is_err());
+        for i in 1..50 {
+            let img2: Vec<u8> = vec![i; 2 * 100];
+            image.write_strip(&img2[..]).unwrap();
+        }
+        assert!(image.write_strip(&img2[..]).is_err());
+        image.finish().unwrap();
+    }
+
+    file.seek(SeekFrom::Start(0)).unwrap();
+    {
+        let mut decoder = Decoder::new(&mut file).unwrap();
+        assert_eq!(decoder.get_tag_u64(Tag::RowsPerStrip).unwrap(), 2);
+        assert_eq!(decoder.strip_count().unwrap(), 50);
+
+        for i in 0..50 {
+            let img2 = [i; 2 * 100];
+            match decoder.read_strip().unwrap() {
+                DecodingResult::U8(data) => assert_eq!(&img2[..], &data[..]),
+                other => panic!("Incorrect strip type {:?}", other),
+            }
+        }
+    }
+}