diff --git a/crates/codecs/src/tiff_codec.rs b/crates/codecs/src/tiff_codec.rs index b077c214a..6f1e25b13 100644 --- a/crates/codecs/src/tiff_codec.rs +++ b/crates/codecs/src/tiff_codec.rs @@ -95,6 +95,9 @@ pub(crate) fn decode(bytes: &[u8], opts: &DecodeOptions) -> Result { fn limits(opts: &DecodeOptions) -> Limits { let mut limits = Limits::default(); limits.decoding_buffer_size = opts.max_pixels.saturating_mul(4 * 8).min(usize::MAX as u64) as usize; + // One strip or tile is read whole: the default 128 MiB refuses a single-strip uncompressed 16-bit + // export of ~22 MP (#1983). The same cap as the decoded image (32 bytes per allowed pixel) bounds it. + limits.intermediate_buffer_size = limits.decoding_buffer_size; limits.ifd_value_size = 32 << 20; limits } diff --git a/crates/codecs/tests/roundtrip.rs b/crates/codecs/tests/roundtrip.rs index af689ae5d..489255c09 100644 --- a/crates/codecs/tests/roundtrip.rs +++ b/crates/codecs/tests/roundtrip.rs @@ -263,6 +263,28 @@ fn tiff_cmyk_and_orientation() { assert!(red[0] > 0.99 && red[1] < 0.01 && red[2] < 0.01, "{red:?}"); } +/// An uncompressed 16-bit RGB export in a single strip, the shape of the file in #1983 (3923 × 5877, +/// 138 MB). Its one strip is larger than the `tiff` crate's default 128 MiB intermediate buffer, so +/// the thumbnail (and the full decode) failed with "decoder limits exceeded". +#[test] +fn tiff_single_strip_larger_than_128_mib() { + use tiff::encoder::{TiffEncoder, colortype}; + let (w, h) = (3923u32, 5877u32); + let mut cur = std::io::Cursor::new(Vec::new()); + { + let mut enc = TiffEncoder::new(&mut cur).unwrap(); + let mut im = enc.new_image::(w, h).unwrap(); + im.rows_per_strip(h).unwrap(); + im.write_data(&vec![0x8000u16; (w * h * 3) as usize]).unwrap(); + } + let bytes = cur.into_inner(); + assert!(bytes.len() > 128 << 20); + let t = decode_thumbnail(&bytes, 256).unwrap(); + assert_eq!((t.source_width, t.source_height), (w, h)); + let px = t.image.get(10, 10); + assert!(px[0].abs_diff(128) <= 2 && px[0] == px[1] && px[1] == px[2], "{px:?}"); +} + /// A one-strip, uncompressed, single-sample TIFF (little-endian) whose samples are `bits` wide and /// already packed into `strip`, one padded row after another. fn packed_tiff(width: u16, height: u16, bits: u16, photometric: u16, strip: &[u8]) -> Vec {