diff --git a/CHANGES.md b/CHANGES.md index a4989f7..ffbab4e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,12 @@ # Version 0.12.0 (unreleased) +Fixes: +- Decoding a tiled image whose width is not a multiple of the tile width no + longer corrupts the columns below the right border tiles when extra samples + are discarded (e.g. RGB with an unspecified extra sample). The horizontal + tile padding was written past the end of the output row, spilling into the + leftmost columns of the following row. + Additions: - Added the `TiffHeader` type. It can be used to create a `Decoder` in a state before the first IFD and allows opening files at an arbitrary first IFD diff --git a/src/decoder/image.rs b/src/decoder/image.rs index f18e085..8f1983b 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -1366,6 +1366,10 @@ impl Image { let photo_range = photometric_bit_end / 8..bits_per_pixel / 8; let mut encoded = vec![0u8; chunk_row_bytes]; for row in buf.chunks_mut(layout.row_stride).take(data_dims.1 as usize) { + // Clip to the chunk's data width: border chunks are stored at the full chunk + // width, and writing their horizontal padding would spill into the following + // output row (and predict/invert would run past the chunk's own columns). + let row = &mut row[..data_row_bytes]; reader.read_exact(&mut encoded)?; Self::compact_photometric_bytes(&mut encoded, row, &photo_range); diff --git a/tests/decode_images.rs b/tests/decode_images.rs index babd054..5c210ee 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -475,6 +475,37 @@ fn test_tiled_rect_rgb_u8() { test_image_sum_u8("tiled-rect-rgb-u8.tif", ColorType::RGB(8), 62081032); } +#[test] +fn test_tiled_extra_samples_border_u8() { + // Regression test for https://github.com/image-rs/image-tiff/issues/400 + // + // A tiled image whose dimensions are not multiples of the tile size, with + // an extra sample that is discarded on decode. The horizontal padding of + // the right border tiles used to be written past the end of the output + // row, spilling into the leftmost columns of the following row. The + // fixture's tile padding is filled with 0xEE so any spill is visible. + let path = PathBuf::from(TEST_IMAGE_DIR).join("tiled-extra-samples-border-u8.tif"); + let img_file = File::open(path).expect("Cannot find test image!"); + let mut decoder = Decoder::open(img_file).expect("Cannot create decoder"); + decoder.next_image().expect("Cannot read image IFD"); + assert_eq!(decoder.dimensions().unwrap(), (40, 40)); + assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8)); + + let DecodingSampleBuffer::U8(data) = decoder.read_image().unwrap() else { + panic!("Wrong bit depth") + }; + + let mut expected = Vec::with_capacity(40 * 40 * 3); + for y in 0..40u32 { + for x in 0..40u32 { + expected.push(((x * 3) % 251) as u8); + expected.push(((y * 7) % 251) as u8); + expected.push(((x + y) % 251) as u8); + } + } + assert_eq!(data, expected); +} + #[test] fn test_inner_access() { let path = PathBuf::from(TEST_IMAGE_DIR).join("tiled-rect-rgb-u8.tif"); diff --git a/tests/images/tiled-extra-samples-border-u8.tif b/tests/images/tiled-extra-samples-border-u8.tif new file mode 100644 index 0000000..119cdaa Binary files /dev/null and b/tests/images/tiled-extra-samples-border-u8.tif differ