From 8fcf49deacfa97c4d2aa486a32c3c228573f4ba5 Mon Sep 17 00:00:00 2001 From: Alan Race Date: Wed, 27 Apr 2022 16:46:48 +0200 Subject: [PATCH 1/9] Added ability to select which tile to load (based on index) Also reduced repeated code when calculating padding etc. --- src/decoder/mod.rs | 144 ++++++++++++++++++++++++++++++--------------- 1 file changed, 96 insertions(+), 48 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 3bcf1b5..f78fe1c 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -243,6 +243,28 @@ pub enum ChunkType { Tile, } +pub struct ChunkInfo { + /// Width of the chunk as specified (includes potential padding) + pub chunk_width: usize, + pub chunk_height: usize, + + /// Width of the data (excluding potential padding) + pub data_width: usize, + pub data_height: usize, +} + +impl ChunkInfo { + #[inline] + pub fn padding_right(&self) -> usize { + self.chunk_width - self.data_width + } + + #[inline] + pub fn padding_down(&self) -> usize { + self.chunk_height - self.data_height + } +} + /// Decoding limits #[derive(Clone, Debug)] pub struct Limits { @@ -1066,33 +1088,72 @@ impl Decoder { Ok(()) } + pub fn chunk_info(&self, chunk_index: usize) -> Option { + match &self.image().chunk_type { + ChunkType::Strip => { + let rows_per_strip = + usize::try_from(self.image().strip_decoder.as_ref()?.rows_per_strip).unwrap(); + + let sized_width = usize::try_from(self.image().width).unwrap(); + let sized_height = usize::try_from(self.image().height).unwrap(); + + let strip_height_without_padding = chunk_index + .checked_mul(rows_per_strip) + .and_then(|x| sized_height.checked_sub(x))?; + + // Ignore potential vertical padding on the bottommost strip + let strip_height = rows_per_strip.min(strip_height_without_padding); + + Some(ChunkInfo { + chunk_width: sized_width, + chunk_height: sized_height, + + data_width: sized_width, + data_height: strip_height, + }) + } + ChunkType::Tile => { + let tile_attrs = self.image().tile_attributes.as_ref()?; + let (padding_right, padding_down) = tile_attrs.get_padding(chunk_index); + + let tile_width = tile_attrs.tile_width - padding_right; + let tile_length = tile_attrs.tile_length - padding_down; + + Some(ChunkInfo { + chunk_width: tile_attrs.tile_width, + chunk_height: tile_attrs.tile_length, + + data_width: tile_width, + data_height: tile_length, + }) + } + } + } + /// Decompresses the tile into the supplied buffer. fn expand_tile( &mut self, mut buffer: DecodingBuffer, offset: u64, compressed_length: u64, - tile: usize, + chunk_info: &ChunkInfo, output_width: usize, ) -> TiffResult<()> { let color_type = self.colortype()?; let byte_len = buffer.byte_len(); let byte_order = self.reader.byte_order; - let tile_attrs = self.image().tile_attributes.as_ref().unwrap(); - let (padding_right, padding_down) = tile_attrs.get_padding(tile); - let tile_length = tile_attrs.tile_length; - let row_samples = tile_attrs.row_samples(); - let padding_right_samples = padding_right * self.image().bits_per_sample.len(); - self.goto_offset_u64(offset)?; let line_samples = output_width * self.image().bits_per_sample.len(); let photometric_interpretation = self.image().photometric_interpretation; + let padding_right_samples = chunk_info.padding_right() * self.image().bits_per_sample.len(); + let row_samples = chunk_info.data_width * self.image().bits_per_sample.len(); + let mut reader = Self::create_reader(self, compressed_length)?; - for row in 0..(tile_length - padding_down) { + for row_index in 0..chunk_info.data_height { let buf = match &mut buffer { DecodingBuffer::U8(buf) => &mut *buf, DecodingBuffer::I8(buf) => bytecast::i8_as_ne_mut_bytes(buf), @@ -1106,14 +1167,14 @@ impl Decoder { DecodingBuffer::F64(buf) => bytecast::f64_as_ne_mut_bytes(buf), }; - let row_start = row * line_samples; - let row_end = row_start + row_samples - padding_right_samples; + let row_start = row_index * line_samples; + let row_end = row_start + row_samples; let row = &mut buf[(row_start * byte_len)..(row_end * byte_len)]; reader.read_exact(row)?; // Skip horizontal padding - if padding_right > 0 { + if padding_right_samples > 0 { let len = u64::try_from(padding_right_samples * byte_len)?; io::copy(&mut reader.by_ref().take(len), &mut io::sink())?; } @@ -1273,7 +1334,7 @@ impl Decoder { jpeg::Error::Internal(_) => Err(TiffError::UnsupportedError( TiffUnsupportedError::UnknownInterpretation, )), - } + }; } } } @@ -1298,22 +1359,12 @@ impl Decoder { .ok_or(TiffError::FormatError( TiffFormatError::InconsistentSizesEncountered, ))?; - let rows_per_strip = - usize::try_from(self.image().strip_decoder.as_ref().unwrap().rows_per_strip)?; - let sized_width = usize::try_from(self.image().width)?; - let sized_height = usize::try_from(self.image().height)?; + let chunk_info = self.chunk_info(index).ok_or(TiffError::IntSizeError)?; - let strip_height_without_padding = index - .checked_mul(rows_per_strip) - .and_then(|x| sized_height.checked_sub(x)) - .ok_or(TiffError::IntSizeError)?; - - // Ignore potential vertical padding on the bottommost strip - let strip_height = rows_per_strip.min(strip_height_without_padding); - - let buffer_size = sized_width - .checked_mul(strip_height) + let buffer_size = chunk_info + .data_width + .checked_mul(chunk_info.data_height) .and_then(|x| x.checked_mul(self.image().bits_per_sample.len())) .ok_or(TiffError::LimitsExceeded)?; @@ -1331,7 +1382,7 @@ impl Decoder { Predictor::Horizontal => { rev_hpredict( buffer.copy(), - (self.image().width, u32::try_from(strip_height)?), + (self.image().width, u32::try_from(chunk_info.data_height)?), usize::try_from(self.image().width)?, self.colortype()?, )?; @@ -1364,17 +1415,13 @@ impl Decoder { TiffFormatError::InconsistentSizesEncountered, ))?; - let tile_attrs = self.image().tile_attributes.as_ref().unwrap(); - let tile_width = tile_attrs.tile_width; - let tile_length = tile_attrs.tile_length; - - let (padding_right, padding_down) = tile_attrs.get_padding(tile); + let chunk_info = self.chunk_info(tile).unwrap(); self.expand_tile( result.copy(), file_offset, compressed_bytes, - tile, + &chunk_info, output_width, )?; @@ -1384,8 +1431,8 @@ impl Decoder { rev_hpredict( result.copy(), ( - u32::try_from(tile_width - padding_right)?, - u32::try_from(tile_length - padding_down)?, + u32::try_from(chunk_info.data_width)?, + u32::try_from(chunk_info.data_height)?, ), output_width, self.colortype()?, @@ -1393,6 +1440,7 @@ impl Decoder { } Predictor::__NonExhaustive => unreachable!(), } + Ok(()) } @@ -1470,22 +1518,22 @@ impl Decoder { /// Read a single tile from the image and return it as a Vector pub fn read_tile(&mut self) -> TiffResult { - self.check_chunk_type(ChunkType::Tile)?; - - let tile = self.current_chunk; - - let tile_attrs = self.image().tile_attributes.as_ref().unwrap(); - 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; - - let mut result = self.result_buffer(tile_width, tile_length)?; - - self.read_tile_to_buffer(&mut result.as_buffer(0), tile, tile_width)?; + let result = self.read_tile_at(self.current_chunk); self.current_chunk += 1; + result + } + + pub fn read_tile_at(&mut self, tile_index: usize) -> TiffResult { + self.check_chunk_type(ChunkType::Tile)?; + + let chunk_info = self.chunk_info(tile_index).unwrap(); + + let mut result = self.result_buffer(chunk_info.data_width, chunk_info.data_height)?; + + self.read_tile_to_buffer(&mut result.as_buffer(0), tile_index, chunk_info.data_width)?; + Ok(result) } From 436cfaf69f99df34da9b1b5e7714e885a3e05a3d Mon Sep 17 00:00:00 2001 From: Alan Race Date: Wed, 27 Apr 2022 17:38:44 +0200 Subject: [PATCH 2/9] Added ability to select which image (IFD) to load --- src/decoder/mod.rs | 108 ++++++++++++++++++++++++++++++++------------- 1 file changed, 78 insertions(+), 30 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index f78fe1c..3e50fd3 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -234,6 +234,21 @@ impl<'a> DecodingBuffer<'a> { DecodingBuffer::I64(ref mut buf) => DecodingBuffer::I64(&mut buf[range]), } } + + fn as_u8_buf(&mut self) -> &mut [u8] { + match self { + DecodingBuffer::U8(buf) => &mut *buf, + DecodingBuffer::I8(buf) => bytecast::i8_as_ne_mut_bytes(buf), + DecodingBuffer::U16(buf) => bytecast::u16_as_ne_mut_bytes(buf), + DecodingBuffer::I16(buf) => bytecast::i16_as_ne_mut_bytes(buf), + DecodingBuffer::U32(buf) => bytecast::u32_as_ne_mut_bytes(buf), + DecodingBuffer::I32(buf) => bytecast::i32_as_ne_mut_bytes(buf), + DecodingBuffer::U64(buf) => bytecast::u64_as_ne_mut_bytes(buf), + DecodingBuffer::I64(buf) => bytecast::i64_as_ne_mut_bytes(buf), + DecodingBuffer::F32(buf) => bytecast::f32_as_ne_mut_bytes(buf), + DecodingBuffer::F64(buf) => bytecast::f64_as_ne_mut_bytes(buf), + } + } } #[derive(Debug, Copy, Clone, PartialEq)] @@ -325,8 +340,10 @@ where reader: SmartReader, bigtiff: bool, limits: Limits, - next_ifd: Option, current_chunk: usize, + next_ifd: Option, + next_ifd_index: usize, + ifds: Vec, seen_ifds: HashSet, image: Image, } @@ -521,12 +538,16 @@ impl Decoder { let mut seen_ifds = HashSet::new(); seen_ifds.insert(*next_ifd.as_ref().unwrap()); + let ifds = vec![*next_ifd.as_ref().unwrap()]; let mut decoder = Decoder { reader, bigtiff, limits: Default::default(), next_ifd, + next_ifd_index: 1, + ifds, + seen_ifds, image: Image { ifd: None, width: 0, @@ -545,7 +566,6 @@ impl Decoder { chunk_bytes: Vec::new(), }, current_chunk: 0, - seen_ifds, }; decoder.next_image()?; Ok(decoder) @@ -606,6 +626,48 @@ impl Decoder { &self.image } + /// Loads the IFD at the specified index in the list, if one exists + pub fn image_at(&mut self, ifd_index: usize) -> TiffResult<()> { + if ifd_index >= self.next_ifd_index { + // We possibly need to load in the next IFD + if self.next_ifd.is_none() { + return Err(TiffError::FormatError( + TiffFormatError::ImageFileDirectoryNotFound, + )); + } + + loop { + // Follow the list until we find the one we want + let (ifd, next_ifd) = self.next_ifd()?; + + if next_ifd.is_none() { + break; + } + + if ifd_index < self.ifds.len() { + break; + } + } + } + + if ifd_index < self.ifds.len() { + let (ifd, next_ifd) = Self::read_ifd( + &mut self.reader, + self.bigtiff, + *self.ifds.get(ifd_index).unwrap(), + )?; + + self.current_chunk = 0; + self.image = Image::from_reader(&mut self.reader, ifd, &self.limits, self.bigtiff)?; + + Ok(()) + } else { + Err(TiffError::FormatError( + TiffFormatError::ImageFileDirectoryNotFound, + )) + } + } + /// Reset the decoder. #[deprecated = "Never should have been public. Only use Decoder::new()"] pub fn init(self) -> TiffResult> { @@ -613,10 +675,7 @@ impl Decoder { Self::new(reader.into_inner()) } - /// Reads in the next image. - /// If there is no further image in the TIFF file a format error is returned. - /// To determine whether there are more images call `TIFFDecoder::more_images` instead. - pub fn next_image(&mut self) -> TiffResult<()> { + fn next_ifd(&mut self) -> TiffResult<(Directory, Option)> { if self.next_ifd.is_none() { return Err(TiffError::FormatError( TiffFormatError::ImageFileDirectoryNotFound, @@ -634,8 +693,19 @@ impl Decoder { return Err(TiffError::FormatError(TiffFormatError::CycleInOffsets)); } self.next_ifd = Some(next); + self.next_ifd_index += 1; + self.ifds.push(next); } + Ok((ifd, next_ifd)) + } + + /// Reads in the next image. + /// If there is no further image in the TIFF file a format error is returned. + /// To determine whether there are more images call `TIFFDecoder::more_images` instead. + pub fn next_image(&mut self) -> TiffResult<()> { + let (ifd, next_ifd) = self.next_ifd()?; + self.current_chunk = 0; self.image = Image::from_reader(&mut self.reader, ifd, &self.limits, self.bigtiff)?; Ok(()) @@ -1057,18 +1127,7 @@ impl Decoder { // Read into output buffer. { - let mut buffer = match &mut buffer { - DecodingBuffer::U8(buf) => &mut *buf, - DecodingBuffer::I8(buf) => bytecast::i8_as_ne_mut_bytes(buf), - DecodingBuffer::U16(buf) => bytecast::u16_as_ne_mut_bytes(buf), - DecodingBuffer::I16(buf) => bytecast::i16_as_ne_mut_bytes(buf), - DecodingBuffer::U32(buf) => bytecast::u32_as_ne_mut_bytes(buf), - DecodingBuffer::I32(buf) => bytecast::i32_as_ne_mut_bytes(buf), - DecodingBuffer::U64(buf) => bytecast::u64_as_ne_mut_bytes(buf), - DecodingBuffer::I64(buf) => bytecast::i64_as_ne_mut_bytes(buf), - DecodingBuffer::F32(buf) => bytecast::f32_as_ne_mut_bytes(buf), - DecodingBuffer::F64(buf) => bytecast::f64_as_ne_mut_bytes(buf), - }; + let mut buffer = buffer.as_u8_buf(); // Note that writing updates the slice to point to the yet unwritten part. std::io::copy(&mut reader.take(buffer.len() as u64), &mut buffer)?; @@ -1154,18 +1213,7 @@ impl Decoder { let mut reader = Self::create_reader(self, compressed_length)?; for row_index in 0..chunk_info.data_height { - let buf = match &mut buffer { - DecodingBuffer::U8(buf) => &mut *buf, - DecodingBuffer::I8(buf) => bytecast::i8_as_ne_mut_bytes(buf), - DecodingBuffer::U16(buf) => bytecast::u16_as_ne_mut_bytes(buf), - DecodingBuffer::I16(buf) => bytecast::i16_as_ne_mut_bytes(buf), - DecodingBuffer::U32(buf) => bytecast::u32_as_ne_mut_bytes(buf), - DecodingBuffer::I32(buf) => bytecast::i32_as_ne_mut_bytes(buf), - DecodingBuffer::U64(buf) => bytecast::u64_as_ne_mut_bytes(buf), - DecodingBuffer::I64(buf) => bytecast::i64_as_ne_mut_bytes(buf), - DecodingBuffer::F32(buf) => bytecast::f32_as_ne_mut_bytes(buf), - DecodingBuffer::F64(buf) => bytecast::f64_as_ne_mut_bytes(buf), - }; + let buf = buffer.as_u8_buf(); let row_start = row_index * line_samples; let row_end = row_start + row_samples; From b196d19238bd13b5e5d7594280b391aa65e01b5f Mon Sep 17 00:00:00 2001 From: Alan Race Date: Wed, 27 Apr 2022 17:50:34 +0200 Subject: [PATCH 3/9] Now using the list of offsets to know how many IFDs we have seen --- src/decoder/mod.rs | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 3e50fd3..abd3722 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -342,8 +342,7 @@ where limits: Limits, current_chunk: usize, next_ifd: Option, - next_ifd_index: usize, - ifds: Vec, + ifd_offsets: Vec, seen_ifds: HashSet, image: Image, } @@ -538,15 +537,14 @@ impl Decoder { let mut seen_ifds = HashSet::new(); seen_ifds.insert(*next_ifd.as_ref().unwrap()); - let ifds = vec![*next_ifd.as_ref().unwrap()]; + let ifd_offsets = vec![*next_ifd.as_ref().unwrap()]; let mut decoder = Decoder { reader, bigtiff, limits: Default::default(), next_ifd, - next_ifd_index: 1, - ifds, + ifd_offsets, seen_ifds, image: Image { ifd: None, @@ -628,7 +626,8 @@ impl Decoder { /// Loads the IFD at the specified index in the list, if one exists pub fn image_at(&mut self, ifd_index: usize) -> TiffResult<()> { - if ifd_index >= self.next_ifd_index { + // Check whether we have seen this IFD before, if so then the index will be less than the length of the list of ifd offsets + if ifd_index >= self.ifd_offsets.len() { // We possibly need to load in the next IFD if self.next_ifd.is_none() { return Err(TiffError::FormatError( @@ -637,24 +636,25 @@ impl Decoder { } loop { - // Follow the list until we find the one we want - let (ifd, next_ifd) = self.next_ifd()?; + // Follow the list until we find the one we want, or we reach the end, whichever happens first + let (_ifd, next_ifd) = self.next_ifd()?; if next_ifd.is_none() { break; } - if ifd_index < self.ifds.len() { + if ifd_index < self.ifd_offsets.len() { break; } } } - if ifd_index < self.ifds.len() { - let (ifd, next_ifd) = Self::read_ifd( + // If the index is within the list of ifds then we can load the selected image/IFD + if ifd_index < self.ifd_offsets.len() { + let (ifd, _next_ifd) = Self::read_ifd( &mut self.reader, self.bigtiff, - *self.ifds.get(ifd_index).unwrap(), + *self.ifd_offsets.get(ifd_index).unwrap(), )?; self.current_chunk = 0; @@ -693,8 +693,7 @@ impl Decoder { return Err(TiffError::FormatError(TiffFormatError::CycleInOffsets)); } self.next_ifd = Some(next); - self.next_ifd_index += 1; - self.ifds.push(next); + self.ifd_offsets.push(next); } Ok((ifd, next_ifd)) @@ -704,7 +703,7 @@ impl Decoder { /// If there is no further image in the TIFF file a format error is returned. /// To determine whether there are more images call `TIFFDecoder::more_images` instead. pub fn next_image(&mut self) -> TiffResult<()> { - let (ifd, next_ifd) = self.next_ifd()?; + let (ifd, _next_ifd) = self.next_ifd()?; self.current_chunk = 0; self.image = Image::from_reader(&mut self.reader, ifd, &self.limits, self.bigtiff)?; From f809d7d1bb81ed725cad82d281cb85b0d6c67fd7 Mon Sep 17 00:00:00 2001 From: Alan Race Date: Mon, 2 May 2022 08:47:05 +0200 Subject: [PATCH 4/9] Fixed compilation issue after merging --- src/decoder/mod.rs | 3 +++ src/decoder/stream.rs | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index c22f6eb..03975fc 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -1209,6 +1209,9 @@ impl Decoder { self.goto_offset_u64(offset)?; let line_samples = output_width * self.image().bits_per_sample.len(); + let padding_right_samples = chunk_info.padding_right() * self.image().bits_per_sample.len(); + let row_samples = chunk_info.data_width * self.image().bits_per_sample.len(); + let photometric_interpretation = self.image().photometric_interpretation; let compression_method = self.image().compression_method; let jpeg_tables = self.image().jpeg_tables.clone(); diff --git a/src/decoder/stream.rs b/src/decoder/stream.rs index 5e8ffdd..159c179 100644 --- a/src/decoder/stream.rs +++ b/src/decoder/stream.rs @@ -4,8 +4,6 @@ use std::convert::TryFrom; use std::io::{self, BufRead, BufReader, Read, Seek, SeekFrom, Take}; use std::sync::Arc; -use crate::tags::PhotometricInterpretation; - /// Byte order of the TIFF file. #[derive(Clone, Copy, Debug)] pub enum ByteOrder { From 4ce3c2f41fde4ae36c1c004bc94e23d9555062eb Mon Sep 17 00:00:00 2001 From: Alan Race Date: Mon, 2 May 2022 15:35:48 +0200 Subject: [PATCH 5/9] `read_tile` now returns `ChunkInfo` --- src/decoder/mod.rs | 31 +++++++++++++++---------------- tests/decode_images.rs | 2 +- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 03975fc..e0c9672 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -259,6 +259,7 @@ pub enum ChunkType { Tile, } +#[non_exhaustive] pub struct ChunkInfo { /// Width of the chunk as specified (includes potential padding) pub chunk_width: usize, @@ -626,7 +627,7 @@ impl Decoder { } /// Loads the IFD at the specified index in the list, if one exists - pub fn image_at(&mut self, ifd_index: usize) -> TiffResult<()> { + pub fn seek_to_image(&mut self, ifd_index: usize) -> TiffResult<()> { // Check whether we have seen this IFD before, if so then the index will be less than the length of the list of ifd offsets if ifd_index >= self.ifd_offsets.len() { // We possibly need to load in the next IFD @@ -651,12 +652,8 @@ impl Decoder { } // If the index is within the list of ifds then we can load the selected image/IFD - if ifd_index < self.ifd_offsets.len() { - let (ifd, _next_ifd) = Self::read_ifd( - &mut self.reader, - self.bigtiff, - *self.ifd_offsets.get(ifd_index).unwrap(), - )?; + if let Some(ifd_offset) = self.ifd_offsets.get(ifd_index) { + let (ifd, _next_ifd) = Self::read_ifd(&mut self.reader, self.bigtiff, *ifd_offset)?; self.current_chunk = 0; self.image = Image::from_reader(&mut self.reader, ifd, &self.limits, self.bigtiff)?; @@ -1151,14 +1148,13 @@ impl Decoder { Ok(()) } - pub fn chunk_info(&self, chunk_index: usize) -> Option { + fn chunk_info(&self, chunk_index: usize) -> Option { match &self.image().chunk_type { ChunkType::Strip => { - let rows_per_strip = - usize::try_from(self.image().strip_decoder.as_ref()?.rows_per_strip).unwrap(); + let rows_per_strip = self.image().strip_decoder.as_ref()?.rows_per_strip as usize; - let sized_width = usize::try_from(self.image().width).unwrap(); - let sized_height = usize::try_from(self.image().height).unwrap(); + let sized_width = self.image().width as usize; + let sized_height = self.image().height as usize; let strip_height_without_padding = chunk_index .checked_mul(rows_per_strip) @@ -1402,7 +1398,10 @@ impl Decoder { TiffFormatError::InconsistentSizesEncountered, ))?; - let chunk_info = self.chunk_info(tile).unwrap(); + // Return the same error as above if the chunk doesn't exist + let chunk_info = self.chunk_info(tile).ok_or(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + ))?; self.expand_tile( result.copy(), @@ -1504,7 +1503,7 @@ impl Decoder { } /// Read a single tile from the image and return it as a Vector - pub fn read_tile(&mut self) -> TiffResult { + pub fn read_tile(&mut self) -> TiffResult<(DecodingResult, ChunkInfo)> { let result = self.read_tile_at(self.current_chunk); self.current_chunk += 1; @@ -1512,7 +1511,7 @@ impl Decoder { result } - pub fn read_tile_at(&mut self, tile_index: usize) -> TiffResult { + pub fn read_tile_at(&mut self, tile_index: usize) -> TiffResult<(DecodingResult, ChunkInfo)> { self.check_chunk_type(ChunkType::Tile)?; let chunk_info = self.chunk_info(tile_index).unwrap(); @@ -1521,7 +1520,7 @@ impl Decoder { self.read_tile_to_buffer(&mut result.as_buffer(0), tile_index, chunk_info.data_width)?; - Ok(result) + Ok((result, chunk_info)) } fn read_tiled_image(&mut self) -> TiffResult { diff --git a/tests/decode_images.rs b/tests/decode_images.rs index d2a4419..c5f7612 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -276,7 +276,7 @@ fn test_tiled_incremental() { for tile in 0..tiles { match decoder.read_tile().unwrap() { - DecodingResult::U8(res) => { + (DecodingResult::U8(res), chunk_info) => { let sum: u64 = res.into_iter().map(::from).sum(); assert_eq!(sum, sums[tile as usize]); } From 8f34748d0159a47c2503ab85af6273694331cd2a Mon Sep 17 00:00:00 2001 From: Alan Race Date: Mon, 2 May 2022 15:39:14 +0200 Subject: [PATCH 6/9] Mirrored return value of `read_tile` to `read_strip` --- src/decoder/mod.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index e0c9672..0667dc0 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -1,8 +1,8 @@ use std::collections::{HashMap, HashSet}; use std::convert::TryFrom; use std::io::{self, Cursor, Read, Seek}; +use std::ops::Range; use std::sync::Arc; -use std::{cmp, ops::Range}; use crate::{ bytecast, ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError, @@ -1484,22 +1484,16 @@ impl Decoder { } /// Read a single strip from the image and return it as a Vector - pub fn read_strip(&mut self) -> TiffResult { + pub fn read_strip(&mut self) -> TiffResult<(DecodingResult, ChunkInfo)> { self.check_chunk_type(ChunkType::Strip)?; let index = self.current_chunk; - let rows_per_strip = - usize::try_from(self.image().strip_decoder.as_ref().unwrap().rows_per_strip)?; + let chunk_info = self.chunk_info(index).unwrap(); - let strip_height = cmp::min( - rows_per_strip, - usize::try_from(self.image().height)? - index * rows_per_strip, - ); - - let mut result = self.result_buffer(usize::try_from(self.image().width)?, strip_height)?; + let mut result = self.result_buffer(chunk_info.data_width, chunk_info.data_height)?; self.read_strip_to_buffer(result.as_buffer(0))?; - Ok(result) + Ok((result, chunk_info)) } /// Read a single tile from the image and return it as a Vector From dd8c9e4f2b4777f9587c9389f50c94f233f578b8 Mon Sep 17 00:00:00 2001 From: Alan Race Date: Mon, 2 May 2022 15:59:48 +0200 Subject: [PATCH 7/9] Added functionality for loading selected strip --- src/decoder/mod.rs | 57 +++++++++++++++++++++--------------------- src/error.rs | 2 ++ tests/decode_images.rs | 2 +- tests/encode_images.rs | 2 +- 4 files changed, 33 insertions(+), 30 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 0667dc0..c8e7a71 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -259,6 +259,7 @@ pub enum ChunkType { Tile, } +#[derive(Debug)] #[non_exhaustive] pub struct ChunkInfo { /// Width of the chunk as specified (includes potential padding) @@ -1165,7 +1166,7 @@ impl Decoder { Some(ChunkInfo { chunk_width: sized_width, - chunk_height: sized_height, + chunk_height: rows_per_strip, data_width: sized_width, data_height: strip_height, @@ -1332,18 +1333,16 @@ impl Decoder { .image() .chunk_offsets .get(index) - .ok_or(TiffError::FormatError( - TiffFormatError::InconsistentSizesEncountered, - ))?; + .ok_or(TiffFormatError::InvalidChunkIndex(index))?; let byte_count = *self .image() .chunk_bytes .get(index) - .ok_or(TiffError::FormatError( - TiffFormatError::InconsistentSizesEncountered, - ))?; + .ok_or(TiffFormatError::InvalidChunkIndex(index))?; - let chunk_info = self.chunk_info(index).ok_or(TiffError::IntSizeError)?; + let chunk_info = self + .chunk_info(index) + .ok_or(TiffFormatError::InvalidChunkIndex(index))?; let buffer_size = chunk_info .data_width @@ -1378,30 +1377,25 @@ impl Decoder { fn read_tile_to_buffer( &mut self, result: &mut DecodingBuffer, - tile: usize, + tile_index: usize, output_width: usize, ) -> TiffResult<()> { let file_offset = *self .image() .chunk_offsets - .get(tile) - .ok_or(TiffError::FormatError( - TiffFormatError::InconsistentSizesEncountered, - ))?; + .get(tile_index) + .ok_or(TiffFormatError::InvalidChunkIndex(tile_index))?; - let compressed_bytes = - *self - .image() - .chunk_bytes - .get(tile) - .ok_or(TiffError::FormatError( - TiffFormatError::InconsistentSizesEncountered, - ))?; + let compressed_bytes = *self + .image() + .chunk_bytes + .get(tile_index) + .ok_or(TiffFormatError::InvalidChunkIndex(tile_index))?; // Return the same error as above if the chunk doesn't exist - let chunk_info = self.chunk_info(tile).ok_or(TiffError::FormatError( - TiffFormatError::InconsistentSizesEncountered, - ))?; + let chunk_info = self + .chunk_info(tile_index) + .ok_or(TiffFormatError::InvalidChunkIndex(tile_index))?; self.expand_tile( result.copy(), @@ -1485,10 +1479,15 @@ impl Decoder { /// Read a single strip from the image and return it as a Vector pub fn read_strip(&mut self) -> TiffResult<(DecodingResult, ChunkInfo)> { - self.check_chunk_type(ChunkType::Strip)?; - let index = self.current_chunk; + self.read_strip_at(self.current_chunk) + } - let chunk_info = self.chunk_info(index).unwrap(); + pub fn read_strip_at(&mut self, strip_index: usize) -> TiffResult<(DecodingResult, ChunkInfo)> { + self.check_chunk_type(ChunkType::Strip)?; + + let chunk_info = self + .chunk_info(strip_index) + .ok_or(TiffFormatError::InvalidChunkIndex(strip_index))?; let mut result = self.result_buffer(chunk_info.data_width, chunk_info.data_height)?; self.read_strip_to_buffer(result.as_buffer(0))?; @@ -1508,7 +1507,9 @@ impl Decoder { pub fn read_tile_at(&mut self, tile_index: usize) -> TiffResult<(DecodingResult, ChunkInfo)> { self.check_chunk_type(ChunkType::Tile)?; - let chunk_info = self.chunk_info(tile_index).unwrap(); + let chunk_info = self + .chunk_info(tile_index) + .ok_or(TiffFormatError::InvalidChunkIndex(tile_index))?; let mut result = self.result_buffer(chunk_info.data_width, chunk_info.data_height)?; diff --git a/src/error.rs b/src/error.rs index 45ea5b0..c94c2f9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -56,6 +56,7 @@ pub enum TiffFormatError { actual_samples: usize, required_samples: usize, }, + InvalidChunkIndex(usize), InvalidTag, InvalidTagValueType(Tag), RequiredTagNotFound(Tag), @@ -100,6 +101,7 @@ impl fmt::Display for TiffFormatError { actual_samples, required_samples ) } + InvalidChunkIndex(index) => write!(fmt, "Image chunk index ({}) requested.", index), InvalidTag => write!(fmt, "Image contains invalid tag."), InvalidTagValueType(ref tag) => { write!(fmt, "Tag `{:?}` did not have the expected value type.", tag) diff --git a/tests/decode_images.rs b/tests/decode_images.rs index c5f7612..3e6b3ef 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -276,7 +276,7 @@ fn test_tiled_incremental() { for tile in 0..tiles { match decoder.read_tile().unwrap() { - (DecodingResult::U8(res), chunk_info) => { + (DecodingResult::U8(res), _chunk_info) => { let sum: u64 = res.into_iter().map(::from).sum(); assert_eq!(sum, sums[tile as usize]); } diff --git a/tests/encode_images.rs b/tests/encode_images.rs index a8e1ca3..6d34ee6 100644 --- a/tests/encode_images.rs +++ b/tests/encode_images.rs @@ -513,7 +513,7 @@ fn test_rows_per_strip() { for i in 0..50 { let img2 = [i; 2 * 100]; match decoder.read_strip().unwrap() { - DecodingResult::U8(data) => assert_eq!(&img2[..], &data[..]), + (DecodingResult::U8(data), _chunk_info) => assert_eq!(&img2[..], &data[..]), other => panic!("Incorrect strip type {:?}", other), } } From 1f501b5acbb855fc6ff2e1f02c2e523bf6054a5d Mon Sep 17 00:00:00 2001 From: Alan Race Date: Tue, 3 May 2022 09:21:23 +0200 Subject: [PATCH 8/9] Reverted `read_strip` and `read_tile` to previous call signature Added comments to ChunkInfo --- src/decoder/mod.rs | 29 ++++++++++++++++++++--------- tests/decode_images.rs | 2 +- tests/encode_images.rs | 2 +- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index c8e7a71..4139229 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -261,23 +261,32 @@ pub enum ChunkType { #[derive(Debug)] #[non_exhaustive] +/// ChunkInfo describes the properties of a chunk (either tile or strip). pub struct ChunkInfo { - /// Width of the chunk as specified (includes potential padding) + /// Width of the chunk as specified (includes potential padding). This has the same + /// value for all chunks in the image. pub chunk_width: usize, + /// Height/Length of the chunk as specified (includes potential padding). This has + /// the same value for all chunks in the image. pub chunk_height: usize, - /// Width of the data (excluding potential padding) + /// Width of the data (excluding potential padding). This can take different values + /// on the right column of the image due to padding. pub data_width: usize, + /// Height/Length of the data (excluding potential padding). This can take different values + /// on the bottom row of the image due to padding. pub data_height: usize, } impl ChunkInfo { #[inline] + /// Returns the amount of padding (pixles) on the right-side of the chunk. pub fn padding_right(&self) -> usize { self.chunk_width - self.data_width } #[inline] + /// Returns the amount of padding (pixles) on the bottom-side of the chunk. pub fn padding_down(&self) -> usize { self.chunk_height - self.data_height } @@ -1095,9 +1104,9 @@ impl Decoder { } /// Decompresses the strip into the supplied buffer. - fn expand_strip<'a>( + fn expand_strip( &mut self, - mut buffer: DecodingBuffer<'a>, + mut buffer: DecodingBuffer, offset: u64, length: u64, ) -> TiffResult<()> { @@ -1478,8 +1487,10 @@ impl Decoder { } /// Read a single strip from the image and return it as a Vector - pub fn read_strip(&mut self) -> TiffResult<(DecodingResult, ChunkInfo)> { - self.read_strip_at(self.current_chunk) + pub fn read_strip(&mut self) -> TiffResult { + let result = self.read_strip_at(self.current_chunk)?; + + Ok(result.0) } pub fn read_strip_at(&mut self, strip_index: usize) -> TiffResult<(DecodingResult, ChunkInfo)> { @@ -1496,12 +1507,12 @@ impl Decoder { } /// Read a single tile from the image and return it as a Vector - pub fn read_tile(&mut self) -> TiffResult<(DecodingResult, ChunkInfo)> { - let result = self.read_tile_at(self.current_chunk); + pub fn read_tile(&mut self) -> TiffResult { + let result = self.read_tile_at(self.current_chunk)?; self.current_chunk += 1; - result + Ok(result.0) } pub fn read_tile_at(&mut self, tile_index: usize) -> TiffResult<(DecodingResult, ChunkInfo)> { diff --git a/tests/decode_images.rs b/tests/decode_images.rs index 3e6b3ef..d2a4419 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -276,7 +276,7 @@ fn test_tiled_incremental() { for tile in 0..tiles { match decoder.read_tile().unwrap() { - (DecodingResult::U8(res), _chunk_info) => { + DecodingResult::U8(res) => { let sum: u64 = res.into_iter().map(::from).sum(); assert_eq!(sum, sums[tile as usize]); } diff --git a/tests/encode_images.rs b/tests/encode_images.rs index 6d34ee6..a8e1ca3 100644 --- a/tests/encode_images.rs +++ b/tests/encode_images.rs @@ -513,7 +513,7 @@ fn test_rows_per_strip() { for i in 0..50 { let img2 = [i; 2 * 100]; match decoder.read_strip().unwrap() { - (DecodingResult::U8(data), _chunk_info) => assert_eq!(&img2[..], &data[..]), + DecodingResult::U8(data) => assert_eq!(&img2[..], &data[..]), other => panic!("Incorrect strip type {:?}", other), } } From 72ca636625a42a581b40a65d7128b6a54490e8ab Mon Sep 17 00:00:00 2001 From: Alan Race Date: Tue, 3 May 2022 09:27:23 +0200 Subject: [PATCH 9/9] Added comments for `read_tile`, `read_strip` and `read_*_at` --- src/decoder/mod.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 4139229..df471eb 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -1486,13 +1486,20 @@ impl Decoder { } } - /// Read a single strip from the image and return it as a Vector + /// Read a single strip from the image and return it as a Vector. This method does not return + /// information on the size of the strip read and so the caller must take care of the final + /// strip in the image as the data read may be less than the expected strip size. This interface + /// may change at a future date to reflect this. + /// + /// `read_strip_at` can be used to get both the strip data and the strip size. pub fn read_strip(&mut self) -> TiffResult { let result = self.read_strip_at(self.current_chunk)?; Ok(result.0) } + /// Read the specified strip (at index `strip_index`) and return the binary data as a Vector and + /// `ChunkInfo` describing the size of the strip. pub fn read_strip_at(&mut self, strip_index: usize) -> TiffResult<(DecodingResult, ChunkInfo)> { self.check_chunk_type(ChunkType::Strip)?; @@ -1506,7 +1513,12 @@ impl Decoder { Ok((result, chunk_info)) } - /// Read a single tile from the image and return it as a Vector + /// Read a single tile from the image and return it as a Vector. This method does not return + /// information on the size of the tile read and so the caller must take care of tiles on the + /// rightmost column and the bottommost row in the image as the data read may be less than the + /// expected tile size. This interface may change at a future date to reflect this. + /// + /// `read_tile_at` can be used to get both the tile data and the tile size. pub fn read_tile(&mut self) -> TiffResult { let result = self.read_tile_at(self.current_chunk)?; @@ -1515,6 +1527,8 @@ impl Decoder { Ok(result.0) } + /// Read the specified tile (at index `tile_index`) and return the binary data as a Vector and + /// `ChunkInfo` describing the size of the tile. pub fn read_tile_at(&mut self, tile_index: usize) -> TiffResult<(DecodingResult, ChunkInfo)> { self.check_chunk_type(ChunkType::Tile)?;