diff --git a/src/decoder/image.rs b/src/decoder/image.rs index fd062d0..08060c9 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -36,6 +36,9 @@ impl TileAttributes { pub fn tiles_down(&self) -> usize { self.image_height.div_ceil(self.tile_length) } + pub fn tiles_per_plane(&self) -> usize { + self.tiles_across() * self.tiles_down() + } fn padding_right(&self) -> usize { (self.tile_width - self.image_width % self.tile_width) % self.tile_width } @@ -1016,7 +1019,15 @@ impl Image { } ChunkType::Tile => { let tile_attrs = self.tile_attributes.as_ref().unwrap(); - let (padding_right, padding_down) = tile_attrs.get_padding(chunk_index as usize); + // The chunk index counts the tiles of all planes, but the tile grid repeats per + // plane, so only the position within the plane decides the padding. + let tiles_per_plane = tile_attrs.tiles_per_plane(); + let tile = if tiles_per_plane == 0 { + 0 + } else { + chunk_index as usize % tiles_per_plane + }; + let (padding_right, padding_down) = tile_attrs.get_padding(tile); let tile_width = tile_attrs.tile_width - padding_right; let tile_length = tile_attrs.tile_length - padding_down; diff --git a/tests/decode_images.rs b/tests/decode_images.rs index d7db448..1046410 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -506,6 +506,47 @@ fn test_tiled_extra_samples_border_u8() { assert_eq!(data, expected); } +#[test] +fn test_tiled_planar_border_u16() { + // A planar (separate) tiled image whose dimensions are not multiples of the tile size. + // The tile grid repeats for every plane, but the chunk (tile) index counts all planes, so + // the border padding of the second plane used to be looked up with the wrong tile index. + // The bottom-right tile of plane 1 then claimed a full tile height, and the decoder ran + // past the end of the output buffer. + // + // Note that `read_image` only decodes the first plane, so this has to go through + // `read_image_to_buffer` to reach the tiles that were mis-sized. + let path = PathBuf::from(TEST_IMAGE_DIR).join("tiled-planar-2c-16b.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(), (30, 20)); + assert_eq!( + decoder.colortype().unwrap(), + ColorType::Multiband { + bit_depth: 16, + num_samples: 2 + } + ); + + let mut buffer = DecodingSampleBuffer::U16(Vec::new()); + decoder.read_image_to_buffer(&mut buffer).unwrap(); + let DecodingSampleBuffer::U16(data) = buffer else { + panic!("Wrong bit depth") + }; + assert_eq!(data.len(), 2 * 20 * 30); + + let mut expected = Vec::with_capacity(2 * 20 * 30); + for plane in 0..2u16 { + for y in 0..20u16 { + for x in 0..30u16 { + expected.push(1000 * plane + 10 * y + x); + } + } + } + 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-planar-2c-16b.tif b/tests/images/tiled-planar-2c-16b.tif new file mode 100644 index 0000000..771890b Binary files /dev/null and b/tests/images/tiled-planar-2c-16b.tif differ