From 8fcf49deacfa97c4d2aa486a32c3c228573f4ba5 Mon Sep 17 00:00:00 2001 From: Alan Race Date: Wed, 27 Apr 2022 16:46:48 +0200 Subject: [PATCH 01/26] 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 02/26] 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 03/26] 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 04/26] 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 05/26] `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 06/26] 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 07/26] 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 08/26] 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 09/26] 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)?; From 6efa89c249da69097eb4b52af6ef69f7579a087a Mon Sep 17 00:00:00 2001 From: Alan Race Date: Wed, 27 Apr 2022 16:46:48 +0200 Subject: [PATCH 10/26] Added ability to select which tile to load (based on index) Also reduced repeated code when calculating padding etc. --- src/decoder/mod.rs | 111 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 88 insertions(+), 23 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index ab58464..f0be812 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -241,6 +241,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 { @@ -1014,6 +1036,7 @@ impl Decoder { let offset = self.image.chunk_file_range(self.current_chunk)?.0; self.goto_offset_u64(offset)?; + let chunk_info = self.chunk_info(index).ok_or(TiffError::IntSizeError)?; let byte_order = self.reader.byte_order; let output_width = usize::try_from(self.image().width)?; @@ -1030,6 +1053,58 @@ impl Decoder { Ok(()) } + fn read_tile_to_buffer( + &mut self, + result: &mut DecodingBuffer, + tile: usize, + output_width: usize, + ) -> TiffResult<()> { + let file_offset = *self + .image() + .chunk_offsets + .get(tile) + .ok_or(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + ))?; + + let compressed_bytes = + *self + .image() + .chunk_bytes + .get(tile) + .ok_or(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + ))?; + + let chunk_info = self.chunk_info(tile).unwrap(); + + self.expand_tile( + result.copy(), + file_offset, + compressed_bytes, + &chunk_info, + output_width, + )?; + + match self.image().predictor { + Predictor::None => {} + Predictor::Horizontal => { + rev_hpredict( + result.copy(), + ( + u32::try_from(chunk_info.data_width)?, + u32::try_from(chunk_info.data_height)?, + ), + output_width, + self.colortype()?, + )?; + } + Predictor::__NonExhaustive => unreachable!(), + } + + Ok(()) + } + fn result_buffer(&self, width: usize, height: usize) -> TiffResult { let buffer_size = match width .checked_mul(height) @@ -1104,32 +1179,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)?; - - let offset = self.image.chunk_file_range(tile)?.0; - self.goto_offset_u64(offset)?; - - let byte_order = self.reader.byte_order; - self.image.expand_chunk( - &mut self.reader, - result.as_buffer(0), - tile_width, - byte_order, - tile, - )?; + 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 56c20c2c200c2b9c0f4af175e213a766346da531 Mon Sep 17 00:00:00 2001 From: Alan Race Date: Wed, 27 Apr 2022 17:38:44 +0200 Subject: [PATCH 11/26] Added ability to select which image (IFD) to load --- src/decoder/mod.rs | 67 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 61 insertions(+), 6 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index f0be812..14cf693 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -323,8 +323,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, } @@ -598,12 +600,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, @@ -622,7 +628,6 @@ impl Decoder { chunk_bytes: Vec::new(), }, current_chunk: 0, - seen_ifds, }; decoder.next_image()?; Ok(decoder) @@ -645,6 +650,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> { @@ -652,10 +699,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, @@ -673,8 +717,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(()) From d8520bde1ff7313df667876c66e388de780b6fa5 Mon Sep 17 00:00:00 2001 From: Alan Race Date: Wed, 27 Apr 2022 17:50:34 +0200 Subject: [PATCH 12/26] 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 14cf693..056075d 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -325,8 +325,7 @@ where limits: Limits, current_chunk: usize, next_ifd: Option, - next_ifd_index: usize, - ifds: Vec, + ifd_offsets: Vec, seen_ifds: HashSet, image: Image, } @@ -600,15 +599,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, @@ -652,7 +650,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( @@ -661,24 +660,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; @@ -717,8 +717,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)) @@ -728,7 +727,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 6d4c4a49a1692ac97aa4f7adc65243a5030084e7 Mon Sep 17 00:00:00 2001 From: Alan Race Date: Mon, 2 May 2022 15:35:48 +0200 Subject: [PATCH 13/26] `read_tile` now returns `ChunkInfo` --- src/decoder/mod.rs | 22 +++++++++++----------- tests/decode_images.rs | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 056075d..1378852 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -241,6 +241,7 @@ pub enum ChunkType { Tile, } +#[non_exhaustive] pub struct ChunkInfo { /// Width of the chunk as specified (includes potential padding) pub chunk_width: usize, @@ -649,7 +650,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 @@ -674,12 +675,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)?; @@ -1130,7 +1127,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(), @@ -1232,7 +1232,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; @@ -1240,7 +1240,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(); @@ -1249,7 +1249,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)) } /// Decodes the entire image and return it as a Vector diff --git a/tests/decode_images.rs b/tests/decode_images.rs index 5d947dc..59ee303 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 ca4da33dd705e10df9d8c5cf77bfb7df4a4fc818 Mon Sep 17 00:00:00 2001 From: Alan Race Date: Mon, 2 May 2022 15:39:14 +0200 Subject: [PATCH 14/26] Mirrored return value of `read_tile` to `read_strip` --- src/decoder/mod.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 1378852..c80197f 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -1213,22 +1213,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 e57ca1a926a41970cec8f2638922918d33f2a643 Mon Sep 17 00:00:00 2001 From: Alan Race Date: Mon, 2 May 2022 15:59:48 +0200 Subject: [PATCH 15/26] Added functionality for loading selected strip --- src/decoder/mod.rs | 46 ++++++++++++++++++++++++------------------ src/error.rs | 2 ++ tests/decode_images.rs | 2 +- tests/encode_images.rs | 2 +- 4 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index c80197f..aeb3fa7 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -241,6 +241,7 @@ pub enum ChunkType { Tile, } +#[derive(Debug)] #[non_exhaustive] pub struct ChunkInfo { /// Width of the chunk as specified (includes potential padding) @@ -1085,6 +1086,9 @@ impl Decoder { pub fn read_strip_to_buffer(&mut self, mut buffer: DecodingBuffer) -> TiffResult<()> { self.check_chunk_type(ChunkType::Strip)?; + let chunk_info = self + .chunk_info(index) + .ok_or(TiffFormatError::InvalidChunkIndex(index))?; let offset = self.image.chunk_file_range(self.current_chunk)?.0; self.goto_offset_u64(offset)?; let chunk_info = self.chunk_info(index).ok_or(TiffError::IntSizeError)?; @@ -1107,30 +1111,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(), @@ -1214,10 +1213,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))?; @@ -1237,7 +1241,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 59ee303..56907aa 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 6bec36ab207dc0552dc00770a4fbe7860aeffde2 Mon Sep 17 00:00:00 2001 From: Alan Race Date: Tue, 3 May 2022 09:21:23 +0200 Subject: [PATCH 16/26] Reverted `read_strip` and `read_tile` to previous call signature Added comments to ChunkInfo --- src/decoder/mod.rs | 25 ++++++++++++++++++------- tests/decode_images.rs | 2 +- tests/encode_images.rs | 2 +- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index aeb3fa7..8d5bc1f 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -243,23 +243,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 } @@ -1212,8 +1221,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)> { @@ -1230,12 +1241,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 56907aa..5d947dc 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 54664d553d3d27312e4cc5bfee6d321dd283d8ce Mon Sep 17 00:00:00 2001 From: Alan Race Date: Tue, 3 May 2022 09:27:23 +0200 Subject: [PATCH 17/26] 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 8d5bc1f..58b45d1 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -1220,13 +1220,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)?; @@ -1240,7 +1247,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)?; @@ -1249,6 +1261,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)?; From 0447d81b0724fc67eeb87485ba852fc48c6c49b2 Mon Sep 17 00:00:00 2001 From: Alan Race Date: Mon, 9 May 2022 09:56:46 +0200 Subject: [PATCH 18/26] Updated to use new unified chunk handling --- src/decoder/image.rs | 120 +++++++++++++++++++++++++++++++++---------- src/decoder/mod.rs | 91 ++++++-------------------------- 2 files changed, 107 insertions(+), 104 deletions(-) diff --git a/src/decoder/image.rs b/src/decoder/image.rs index a335610..fadebf8 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -61,6 +61,39 @@ impl TileAttributes { } } +#[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). 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). 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 + } +} + #[derive(Debug)] pub(crate) struct Image { pub ifd: Option, @@ -383,13 +416,54 @@ impl Image { } } + pub(crate) fn chunk_info(&self, chunk_index: usize) -> Option { + match &self.chunk_type { + ChunkType::Strip => { + let rows_per_strip = self.strip_decoder.as_ref()?.rows_per_strip as usize; + + let sized_width = self.width as usize; + let sized_height = self.height as usize; + + 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: rows_per_strip, + + data_width: sized_width, + data_height: strip_height, + }) + } + ChunkType::Tile => { + let tile_attrs = self.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, + }) + } + } + } + pub(crate) fn expand_chunk( &self, reader: impl Read, mut buffer: DecodingBuffer, output_width: usize, byte_order: ByteOrder, - chunk: usize, + chunk_index: usize, ) -> TiffResult<()> { // Validate that the provided buffer is of the expected type. let color_type = self.colortype()?; @@ -407,9 +481,12 @@ impl Image { } } - let compressed_bytes = self.chunk_bytes.get(chunk).ok_or(TiffError::FormatError( - TiffFormatError::InconsistentSizesEncountered, - ))?; + let compressed_bytes = self + .chunk_bytes + .get(chunk_index) + .ok_or(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + ))?; let byte_len = buffer.byte_len(); let compression_method = self.compression_method; @@ -417,29 +494,16 @@ impl Image { let predictor = self.predictor; let samples = self.bits_per_sample.len(); - let padding; - let chunk_dimensions; - match self.chunk_type { - ChunkType::Strip => { - let width = usize::try_from(self.width)?; - let height = usize::try_from(self.height)?; - let strip_attrs = self.strip_decoder.as_ref().unwrap(); - chunk_dimensions = (width, usize::try_from(strip_attrs.rows_per_strip)?); - padding = (0, (chunk_dimensions.1 * (chunk + 1)).max(height) - height); - } - ChunkType::Tile => { - let tile_attrs = self.tile_attributes.as_ref().unwrap(); - padding = tile_attrs.get_padding(chunk); - chunk_dimensions = (tile_attrs.tile_width, tile_attrs.tile_length); - } - } + let chunk_info = self.chunk_info(chunk_index).ok_or(TiffError::FormatError( + TiffFormatError::InvalidChunkIndex(chunk_index), + ))?; let jpeg_tables = self.jpeg_tables.clone(); let mut reader = Self::create_reader(reader, compression_method, *compressed_bytes, jpeg_tables)?; - if output_width == chunk_dimensions.0 && padding.0 == 0 { - let total_samples = chunk_dimensions.0 * (chunk_dimensions.1 - padding.1) * samples; + if output_width == chunk_info.data_width && chunk_info.padding_right() == 0 { + let total_samples = chunk_info.data_width * chunk_info.data_height * samples; let tile = &mut buffer.as_bytes_mut()[..total_samples * byte_len]; reader.read_exact(tile)?; @@ -448,16 +512,16 @@ impl Image { super::invert_colors(&mut buffer.subrange(0..total_samples), color_type); } } else { - for row in 0..(chunk_dimensions.1 - padding.1) { + for row in 0..chunk_info.data_height { let row_start = row * output_width * samples; - let row_end = row_start + (chunk_dimensions.0 - padding.0) * samples; + let row_end = row_start + chunk_info.data_width * samples; let row = &mut buffer.as_bytes_mut()[(row_start * byte_len)..(row_end * byte_len)]; reader.read_exact(row)?; // Skip horizontal padding - if padding.0 > 0 { - let len = u64::try_from(padding.0 * samples * byte_len)?; + if chunk_info.padding_right() > 0 { + let len = u64::try_from(chunk_info.padding_right() * samples * byte_len)?; io::copy(&mut reader.by_ref().take(len), &mut io::sink())?; } @@ -474,8 +538,8 @@ impl Image { super::rev_hpredict( buffer, ( - u32::try_from(chunk_dimensions.0 - padding.0)?, - u32::try_from(chunk_dimensions.1 - padding.1)?, + u32::try_from(chunk_info.data_width)?, + u32::try_from(chunk_info.data_height)?, ), output_width, color_type, diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 58b45d1..ce2a288 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -1,14 +1,14 @@ use std::collections::{HashMap, HashSet}; use std::convert::TryFrom; use std::io::{self, Read, Seek}; -use std::{cmp, ops::Range}; +use std::ops::Range; use crate::{ bytecast, ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError, }; use self::ifd::Directory; -use self::image::Image; +use self::image::{ChunkInfo, Image}; use crate::tags::{ CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag, Type, }; @@ -241,39 +241,6 @@ pub enum ChunkType { Tile, } -#[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). 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). 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 - } -} - /// Decoding limits #[derive(Clone, Debug)] pub struct Limits { @@ -1095,12 +1062,8 @@ impl Decoder { pub fn read_strip_to_buffer(&mut self, mut buffer: DecodingBuffer) -> TiffResult<()> { self.check_chunk_type(ChunkType::Strip)?; - let chunk_info = self - .chunk_info(index) - .ok_or(TiffFormatError::InvalidChunkIndex(index))?; let offset = self.image.chunk_file_range(self.current_chunk)?.0; self.goto_offset_u64(offset)?; - let chunk_info = self.chunk_info(index).ok_or(TiffError::IntSizeError)?; let byte_order = self.reader.byte_order; let output_width = usize::try_from(self.image().width)?; @@ -1119,51 +1082,25 @@ impl Decoder { fn read_tile_to_buffer( &mut self, - result: &mut DecodingBuffer, + mut buffer: DecodingBuffer, tile_index: usize, output_width: usize, ) -> TiffResult<()> { - let file_offset = *self - .image() - .chunk_offsets - .get(tile_index) - .ok_or(TiffFormatError::InvalidChunkIndex(tile_index))?; + self.check_chunk_type(ChunkType::Tile)?; - let compressed_bytes = *self - .image() - .chunk_bytes - .get(tile_index) - .ok_or(TiffFormatError::InvalidChunkIndex(tile_index))?; + let offset = self.image.chunk_file_range(tile_index)?.0; + self.goto_offset_u64(offset)?; - // Return the same error as above if the chunk doesn't exist - let chunk_info = self - .chunk_info(tile_index) - .ok_or(TiffFormatError::InvalidChunkIndex(tile_index))?; + let byte_order = self.reader.byte_order; - self.expand_tile( - result.copy(), - file_offset, - compressed_bytes, - &chunk_info, + self.image.expand_chunk( + &mut self.reader, + buffer.copy(), output_width, + byte_order, + tile_index, )?; - match self.image().predictor { - Predictor::None => {} - Predictor::Horizontal => { - rev_hpredict( - result.copy(), - ( - u32::try_from(chunk_info.data_width)?, - u32::try_from(chunk_info.data_height)?, - ), - output_width, - self.colortype()?, - )?; - } - Predictor::__NonExhaustive => unreachable!(), - } - Ok(()) } @@ -1238,6 +1175,7 @@ impl Decoder { self.check_chunk_type(ChunkType::Strip)?; let chunk_info = self + .image() .chunk_info(strip_index) .ok_or(TiffFormatError::InvalidChunkIndex(strip_index))?; @@ -1267,12 +1205,13 @@ impl Decoder { self.check_chunk_type(ChunkType::Tile)?; let chunk_info = self + .image() .chunk_info(tile_index) .ok_or(TiffFormatError::InvalidChunkIndex(tile_index))?; 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)?; + self.read_tile_to_buffer(result.as_buffer(0), tile_index, chunk_info.data_width)?; Ok((result, chunk_info)) } From 6e398ce7e36ec6ae3169343bee8c009109ef2e09 Mon Sep 17 00:00:00 2001 From: Alan Race Date: Mon, 9 May 2022 09:58:51 +0200 Subject: [PATCH 19/26] Remove ChunkInfo from mod.rs that was added through merge --- src/decoder/mod.rs | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 87466bb..ce2a288 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -241,39 +241,6 @@ pub enum ChunkType { Tile, } -#[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). 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). 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 - } -} - /// Decoding limits #[derive(Clone, Debug)] pub struct Limits { From 8c0c13f022c1c949ba1a705b434c8293352e3385 Mon Sep 17 00:00:00 2001 From: Alan Race Date: Thu, 7 Jul 2022 15:25:01 +0200 Subject: [PATCH 20/26] Added `read_chunk_at` and `read_chunk_to_buffer` functions --- src/decoder/image.rs | 19 ++++++++------ src/decoder/mod.rs | 59 ++++++++++++++++++++++++++++++++++++++++++ tests/decode_images.rs | 2 +- tests/encode_images.rs | 2 +- 4 files changed, 72 insertions(+), 10 deletions(-) diff --git a/src/decoder/image.rs b/src/decoder/image.rs index 35fcfeb..172feb7 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -512,9 +512,12 @@ impl Image { _ => {} } - let compressed_bytes = self.chunk_bytes.get(chunk).ok_or(TiffError::FormatError( - TiffFormatError::InconsistentSizesEncountered, - ))?; + let compressed_bytes = self + .chunk_bytes + .get(chunk_index) + .ok_or(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + ))?; let byte_len = buffer.byte_len(); let compression_method = self.compression_method; @@ -535,7 +538,7 @@ impl Image { let tile = &mut buffer.as_bytes_mut()[..total_samples * byte_len]; reader.read_exact(tile)?; - for row in 0..(chunk_dimensions.1 - padding.1) { + for row in 0..chunk_info.data_height { let row_start = row * output_width * samples; let row_end = (row + 1) * output_width * samples; let row = buffer.subrange(row_start..row_end); @@ -544,13 +547,13 @@ impl Image { if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { super::invert_colors(&mut buffer.subrange(0..total_samples), color_type); } - } else if padding.0 > 0 && self.predictor == Predictor::FloatingPoint { + } else if chunk_info.padding_right() > 0 && self.predictor == Predictor::FloatingPoint { // The floating point predictor shuffles the padding bytes into the encoded output, so // this case is handled specially when needed. - let mut encoded = vec![0u8; chunk_dimensions.0 * samples * byte_len]; - for row in 0..(chunk_dimensions.1 - padding.1) { + let mut encoded = vec![0u8; chunk_info.chunk_width * samples * byte_len]; + for row in 0..chunk_info.data_height { let row_start = row * output_width * samples; - let row_end = row_start + (chunk_dimensions.0 - padding.0) * samples; + let row_end = row_start + chunk_info.data_width * samples; reader.read_exact(&mut encoded)?; match buffer.subrange(row_start..row_end) { diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 1555858..27e1465 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -1042,6 +1042,7 @@ impl Decoder { self.read_image() } + #[deprecated = "Use read_chunk_to_buffer instead"] pub fn read_strip_to_buffer(&mut self, mut buffer: DecodingBuffer) -> TiffResult<()> { self.check_chunk_type(ChunkType::Strip)?; @@ -1063,6 +1064,7 @@ impl Decoder { Ok(()) } + #[deprecated = "Use read_chunk_to_buffer instead"] fn read_tile_to_buffer( &mut self, mut buffer: DecodingBuffer, @@ -1087,6 +1089,28 @@ impl Decoder { Ok(()) } + fn read_chunk_to_buffer( + &mut self, + mut buffer: DecodingBuffer, + tile_index: usize, + output_width: usize, + ) -> TiffResult<()> { + let offset = self.image.chunk_file_range(tile_index)?.0; + self.goto_offset_u64(offset)?; + + let byte_order = self.reader.byte_order; + + self.image.expand_chunk( + &mut self.reader, + buffer.copy(), + output_width, + byte_order, + tile_index, + )?; + + Ok(()) + } + fn result_buffer(&self, width: usize, height: usize) -> TiffResult { let buffer_size = match width .checked_mul(height) @@ -1146,6 +1170,8 @@ impl Decoder { /// 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. + /// + #[deprecated = "Use read_chunk instead"] pub fn read_strip(&mut self) -> TiffResult { let result = self.read_strip_at(self.current_chunk)?; @@ -1154,6 +1180,8 @@ impl Decoder { /// Read the specified strip (at index `strip_index`) and return the binary data as a Vector and /// `ChunkInfo` describing the size of the strip. + /// + #[deprecated = "Use read_chunk instead"] pub fn read_strip_at(&mut self, strip_index: usize) -> TiffResult<(DecodingResult, ChunkInfo)> { self.check_chunk_type(ChunkType::Strip)?; @@ -1174,6 +1202,7 @@ impl Decoder { /// 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. + #[deprecated = "Use read_chunk instead"] pub fn read_tile(&mut self) -> TiffResult { let result = self.read_tile_at(self.current_chunk)?; @@ -1184,6 +1213,7 @@ impl Decoder { /// Read the specified tile (at index `tile_index`) and return the binary data as a Vector and /// `ChunkInfo` describing the size of the tile. + #[deprecated = "Use read_chunk_at instead"] pub fn read_tile_at(&mut self, tile_index: usize) -> TiffResult<(DecodingResult, ChunkInfo)> { self.check_chunk_type(ChunkType::Tile)?; @@ -1199,6 +1229,35 @@ impl Decoder { Ok((result, chunk_info)) } + /// Read a single chunk from the image and return it as a Vector. This method does not return + /// information on the size of the chunk read and so the caller must take care of chunks on the + /// rightmost column and the bottommost row in the image as the data read may be less than the + /// expected chunk size. This interface may change at a future date to reflect this. + /// + /// `read_chunk_at` can be used to get both the chunk data and the tile size. + pub fn read_chunk(&mut self) -> TiffResult { + let result = self.read_chunk_at(self.current_chunk)?; + + self.current_chunk += 1; + + Ok(result.0) + } + + /// Read the specified chunk (at index `chunk_index`) and return the binary data as a Vector and + /// `ChunkInfo` describing the size of the chunk. + pub fn read_chunk_at(&mut self, chunk_index: usize) -> TiffResult<(DecodingResult, ChunkInfo)> { + let chunk_info = self + .image() + .chunk_info(chunk_index) + .ok_or(TiffFormatError::InvalidChunkIndex(chunk_index))?; + + let mut result = self.result_buffer(chunk_info.data_width, chunk_info.data_height)?; + + self.read_chunk_to_buffer(result.as_buffer(0), chunk_index, chunk_info.data_width)?; + + Ok((result, chunk_info)) + } + /// Decodes the entire image and return it as a Vector pub fn read_image(&mut self) -> TiffResult { let width = usize::try_from(self.image().width)?; diff --git a/tests/decode_images.rs b/tests/decode_images.rs index df43524..b0f08d5 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -275,7 +275,7 @@ fn test_tiled_incremental() { assert_eq!(tiles as usize, sums.len()); for tile in 0..tiles { - match decoder.read_tile().unwrap() { + match decoder.read_chunk().unwrap() { 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 a8e1ca3..af35d91 100644 --- a/tests/encode_images.rs +++ b/tests/encode_images.rs @@ -512,7 +512,7 @@ fn test_rows_per_strip() { for i in 0..50 { let img2 = [i; 2 * 100]; - match decoder.read_strip().unwrap() { + match decoder.read_chunk().unwrap() { DecodingResult::U8(data) => assert_eq!(&img2[..], &data[..]), other => panic!("Incorrect strip type {:?}", other), } From e1f4161e0bef15fe927db73add6cc63768a9ad0e Mon Sep 17 00:00:00 2001 From: Alan Race Date: Fri, 8 Jul 2022 11:08:30 +0200 Subject: [PATCH 21/26] Removed unused functions and make InvalidChunkIndex a UsageError --- src/decoder/image.rs | 10 ++--- src/decoder/mod.rs | 88 ++---------------------------------------- src/error.rs | 10 ++++- tests/decode_images.rs | 4 +- tests/encode_images.rs | 4 +- 5 files changed, 21 insertions(+), 95 deletions(-) diff --git a/src/decoder/image.rs b/src/decoder/image.rs index 172feb7..d5cf4fb 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -7,7 +7,7 @@ use super::tag_reader::TagReader; use super::{fp_predict_f32, fp_predict_f64, DecodingBuffer, Limits}; use super::{stream::SmartReader, ChunkType}; use crate::tags::{CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag}; -use crate::{ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError}; +use crate::{ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError}; use std::convert::{TryFrom, TryInto}; use std::io::{self, Cursor, Read, Seek}; use std::sync::Arc; @@ -419,7 +419,7 @@ impl Image { pub(crate) fn chunk_info(&self, chunk_index: usize) -> Option { match &self.chunk_type { ChunkType::Strip => { - let rows_per_strip = self.strip_decoder.as_ref()?.rows_per_strip as usize; + let rows_per_strip = self.strip_decoder.as_ref().unwrap().rows_per_strip as usize; let sized_width = self.width as usize; let sized_height = self.height as usize; @@ -440,7 +440,7 @@ impl Image { }) } ChunkType::Tile => { - let tile_attrs = self.tile_attributes.as_ref()?; + let tile_attrs = self.tile_attributes.as_ref().unwrap(); let (padding_right, padding_down) = tile_attrs.get_padding(chunk_index); let tile_width = tile_attrs.tile_width - padding_right; @@ -525,8 +525,8 @@ impl Image { let predictor = self.predictor; let samples = self.bits_per_sample.len(); - let chunk_info = self.chunk_info(chunk_index).ok_or(TiffError::FormatError( - TiffFormatError::InvalidChunkIndex(chunk_index), + let chunk_info = self.chunk_info(chunk_index).ok_or(TiffError::UsageError( + UsageError::InvalidChunkIndex(chunk_index), ))?; let jpeg_tables = self.jpeg_tables.clone(); diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 27e1465..b7a6b5d 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -1064,31 +1064,6 @@ impl Decoder { Ok(()) } - #[deprecated = "Use read_chunk_to_buffer instead"] - fn read_tile_to_buffer( - &mut self, - mut buffer: DecodingBuffer, - tile_index: usize, - output_width: usize, - ) -> TiffResult<()> { - self.check_chunk_type(ChunkType::Tile)?; - - let offset = self.image.chunk_file_range(tile_index)?.0; - self.goto_offset_u64(offset)?; - - let byte_order = self.reader.byte_order; - - self.image.expand_chunk( - &mut self.reader, - buffer.copy(), - output_width, - byte_order, - tile_index, - )?; - - Ok(()) - } - fn read_chunk_to_buffer( &mut self, mut buffer: DecodingBuffer, @@ -1168,74 +1143,19 @@ impl Decoder { /// 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. - /// - #[deprecated = "Use read_chunk instead"] + #[deprecated = "Use read_chunk_at instead"] pub fn read_strip(&mut self) -> TiffResult { - let result = self.read_strip_at(self.current_chunk)?; + let result = self.read_chunk_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. - /// - #[deprecated = "Use read_chunk instead"] - pub fn read_strip_at(&mut self, strip_index: usize) -> TiffResult<(DecodingResult, ChunkInfo)> { - self.check_chunk_type(ChunkType::Strip)?; - - let chunk_info = self - .image() - .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))?; - - Ok((result, chunk_info)) - } - /// 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. - #[deprecated = "Use read_chunk instead"] - pub fn read_tile(&mut self) -> TiffResult { - let result = self.read_tile_at(self.current_chunk)?; - - self.current_chunk += 1; - - 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. #[deprecated = "Use read_chunk_at instead"] - pub fn read_tile_at(&mut self, tile_index: usize) -> TiffResult<(DecodingResult, ChunkInfo)> { - self.check_chunk_type(ChunkType::Tile)?; - - let chunk_info = self - .image() - .chunk_info(tile_index) - .ok_or(TiffFormatError::InvalidChunkIndex(tile_index))?; - - let mut result = self.result_buffer(chunk_info.data_width, chunk_info.data_height)?; - - self.read_tile_to_buffer(result.as_buffer(0), tile_index, chunk_info.data_width)?; - - Ok((result, chunk_info)) - } - - /// Read a single chunk from the image and return it as a Vector. This method does not return - /// information on the size of the chunk read and so the caller must take care of chunks on the - /// rightmost column and the bottommost row in the image as the data read may be less than the - /// expected chunk size. This interface may change at a future date to reflect this. - /// - /// `read_chunk_at` can be used to get both the chunk data and the tile size. - pub fn read_chunk(&mut self) -> TiffResult { + pub fn read_tile(&mut self) -> TiffResult { let result = self.read_chunk_at(self.current_chunk)?; self.current_chunk += 1; @@ -1249,7 +1169,7 @@ impl Decoder { let chunk_info = self .image() .chunk_info(chunk_index) - .ok_or(TiffFormatError::InvalidChunkIndex(chunk_index))?; + .ok_or(UsageError::InvalidChunkIndex(chunk_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 a31a04c..c03d371 100644 --- a/src/error.rs +++ b/src/error.rs @@ -56,7 +56,6 @@ pub enum TiffFormatError { actual_samples: usize, required_samples: usize, }, - InvalidChunkIndex(usize), InvalidTag, InvalidTagValueType(Tag), RequiredTagNotFound(Tag), @@ -101,7 +100,6 @@ 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) @@ -207,6 +205,7 @@ impl fmt::Display for TiffUnsupportedError { #[derive(Debug)] pub enum UsageError { InvalidChunkType(ChunkType, ChunkType), + InvalidChunkIndex(usize), } impl fmt::Display for UsageError { @@ -220,6 +219,7 @@ impl fmt::Display for UsageError { expected, actual ) } + InvalidChunkIndex(index) => write!(fmt, "Image chunk index ({}) requested.", index), } } } @@ -292,6 +292,12 @@ impl From for TiffError { } } +impl From for TiffError { + fn from(err: UsageError) -> TiffError { + TiffError::UsageError(err) + } +} + impl From for TiffError { fn from(_err: std::num::TryFromIntError) -> TiffError { TiffError::IntSizeError diff --git a/tests/decode_images.rs b/tests/decode_images.rs index b0f08d5..f96046e 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -275,8 +275,8 @@ fn test_tiled_incremental() { assert_eq!(tiles as usize, sums.len()); for tile in 0..tiles { - match decoder.read_chunk().unwrap() { - DecodingResult::U8(res) => { + match decoder.read_chunk_at(tile as usize).unwrap() { + (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 af35d91..8db3bac 100644 --- a/tests/encode_images.rs +++ b/tests/encode_images.rs @@ -512,8 +512,8 @@ fn test_rows_per_strip() { for i in 0..50 { let img2 = [i; 2 * 100]; - match decoder.read_chunk().unwrap() { - DecodingResult::U8(data) => assert_eq!(&img2[..], &data[..]), + match decoder.read_chunk_at(i as usize).unwrap() { + (DecodingResult::U8(data), _) => assert_eq!(&img2[..], &data[..]), other => panic!("Incorrect strip type {:?}", other), } } From 5cadbffebf899fe975b7e63a51ebde183dc00334 Mon Sep 17 00:00:00 2001 From: Alan Race Date: Mon, 11 Jul 2022 08:56:35 +0200 Subject: [PATCH 22/26] Changed to use `u32` for chunk_index --- src/decoder/image.rs | 97 ++++++++++++++++++++++-------------------- src/decoder/mod.rs | 33 ++++++++------ src/error.rs | 2 +- tests/decode_images.rs | 2 +- tests/encode_images.rs | 2 +- 5 files changed, 74 insertions(+), 62 deletions(-) diff --git a/src/decoder/image.rs b/src/decoder/image.rs index d5cf4fb..d761411 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -67,29 +67,29 @@ impl TileAttributes { pub struct ChunkInfo { /// Width of the chunk as specified (includes potential padding). This has the same /// value for all chunks in the image. - pub chunk_width: usize, + pub chunk_width: u32, /// 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, + pub chunk_height: u32, /// 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, + pub data_width: u32, /// 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, + pub data_height: u32, } impl ChunkInfo { #[inline] /// Returns the amount of padding (pixles) on the right-side of the chunk. - pub fn padding_right(&self) -> usize { + pub fn padding_right(&self) -> u32 { 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 { + pub fn padding_down(&self) -> u32 { self.chunk_height - self.data_height } } @@ -390,14 +390,20 @@ impl Image { }) } - pub(crate) fn chunk_file_range(&self, chunk: usize) -> TiffResult<(u64, u64)> { - let file_offset = self.chunk_offsets.get(chunk).ok_or(TiffError::FormatError( - TiffFormatError::InconsistentSizesEncountered, - ))?; + pub(crate) fn chunk_file_range(&self, chunk: u32) -> TiffResult<(u64, u64)> { + let file_offset = self + .chunk_offsets + .get(chunk as usize) + .ok_or(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + ))?; - let compressed_bytes = self.chunk_bytes.get(chunk).ok_or(TiffError::FormatError( - TiffFormatError::InconsistentSizesEncountered, - ))?; + let compressed_bytes = + self.chunk_bytes + .get(chunk as usize) + .ok_or(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + ))?; Ok((*file_offset, *compressed_bytes)) } @@ -416,42 +422,39 @@ impl Image { } } - pub(crate) fn chunk_info(&self, chunk_index: usize) -> Option { + pub(crate) fn chunk_info(&self, chunk_index: u32) -> Option { match &self.chunk_type { ChunkType::Strip => { - let rows_per_strip = self.strip_decoder.as_ref().unwrap().rows_per_strip as usize; - - let sized_width = self.width as usize; - let sized_height = self.height as usize; + let rows_per_strip = self.strip_decoder.as_ref().unwrap().rows_per_strip; let strip_height_without_padding = chunk_index .checked_mul(rows_per_strip) - .and_then(|x| sized_height.checked_sub(x))?; + .and_then(|x| self.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_width: self.width, chunk_height: rows_per_strip, - data_width: sized_width, + data_width: self.width, data_height: strip_height, }) } ChunkType::Tile => { let tile_attrs = self.tile_attributes.as_ref().unwrap(); - let (padding_right, padding_down) = tile_attrs.get_padding(chunk_index); + let (padding_right, padding_down) = tile_attrs.get_padding(chunk_index as usize); 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, + chunk_width: tile_attrs.tile_width as u32, + chunk_height: tile_attrs.tile_length as u32, - data_width: tile_width, - data_height: tile_length, + data_width: tile_width as u32, + data_height: tile_length as u32, }) } } @@ -463,7 +466,7 @@ impl Image { mut buffer: DecodingBuffer, output_width: usize, byte_order: ByteOrder, - chunk_index: usize, + chunk_index: u32, ) -> TiffResult<()> { // Validate that the provided buffer is of the expected type. let color_type = self.colortype()?; @@ -512,12 +515,12 @@ impl Image { _ => {} } - let compressed_bytes = self - .chunk_bytes - .get(chunk_index) - .ok_or(TiffError::FormatError( - TiffFormatError::InconsistentSizesEncountered, - ))?; + let compressed_bytes = + self.chunk_bytes + .get(chunk_index as usize) + .ok_or(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + ))?; let byte_len = buffer.byte_len(); let compression_method = self.compression_method; @@ -533,14 +536,15 @@ impl Image { let mut reader = Self::create_reader(reader, compression_method, *compressed_bytes, jpeg_tables)?; - if output_width == chunk_info.data_width && chunk_info.padding_right() == 0 { - let total_samples = chunk_info.data_width * chunk_info.data_height * samples; + if output_width == chunk_info.data_width as usize && chunk_info.padding_right() == 0 { + let total_samples = + chunk_info.data_width as usize * chunk_info.data_height as usize * samples; let tile = &mut buffer.as_bytes_mut()[..total_samples * byte_len]; reader.read_exact(tile)?; - for row in 0..chunk_info.data_height { - let row_start = row * output_width * samples; - let row_end = (row + 1) * output_width * samples; + for row in 0..chunk_info.data_height as usize { + let row_start = row as usize * output_width as usize * samples; + let row_end = (row + 1) * output_width as usize * samples; let row = buffer.subrange(row_start..row_end); super::fix_endianness_and_predict(row, samples, byte_order, predictor); } @@ -550,10 +554,10 @@ impl Image { } else if chunk_info.padding_right() > 0 && self.predictor == Predictor::FloatingPoint { // The floating point predictor shuffles the padding bytes into the encoded output, so // this case is handled specially when needed. - let mut encoded = vec![0u8; chunk_info.chunk_width * samples * byte_len]; - for row in 0..chunk_info.data_height { - let row_start = row * output_width * samples; - let row_end = row_start + chunk_info.data_width * samples; + let mut encoded = vec![0u8; chunk_info.chunk_width as usize * samples * byte_len]; + for row in 0..chunk_info.data_height as usize { + let row_start = row * output_width as usize * samples; + let row_end = row_start + chunk_info.data_width as usize * samples; reader.read_exact(&mut encoded)?; match buffer.subrange(row_start..row_end) { @@ -566,16 +570,17 @@ impl Image { } } } else { - for row in 0..chunk_info.data_height { - let row_start = row * output_width * samples; - let row_end = row_start + chunk_info.data_width * samples; + for row in 0..chunk_info.data_height as usize { + let row_start = row * output_width as usize * samples; + let row_end = row_start + chunk_info.data_width as usize * samples; let row = &mut buffer.as_bytes_mut()[(row_start * byte_len)..(row_end * byte_len)]; reader.read_exact(row)?; // Skip horizontal padding if chunk_info.padding_right() > 0 { - let len = u64::try_from(chunk_info.padding_right() * samples * byte_len)?; + let len = + u64::try_from(chunk_info.padding_right() as usize * samples * byte_len)?; io::copy(&mut reader.by_ref().take(len), &mut io::sink())?; } diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index b7a6b5d..2dd020a 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -301,7 +301,7 @@ where reader: SmartReader, bigtiff: bool, limits: Limits, - current_chunk: usize, + current_chunk: u32, next_ifd: Option, ifd_offsets: Vec, seen_ifds: HashSet, @@ -1064,13 +1064,13 @@ impl Decoder { Ok(()) } - fn read_chunk_to_buffer( + pub fn read_chunk_to_buffer( &mut self, mut buffer: DecodingBuffer, - tile_index: usize, + chunk_index: u32, output_width: usize, ) -> TiffResult<()> { - let offset = self.image.chunk_file_range(tile_index)?.0; + let offset = self.image.chunk_file_range(chunk_index)?.0; self.goto_offset_u64(offset)?; let byte_order = self.reader.byte_order; @@ -1080,7 +1080,7 @@ impl Decoder { buffer.copy(), output_width, byte_order, - tile_index, + chunk_index, )?; Ok(()) @@ -1143,9 +1143,9 @@ impl Decoder { /// 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. - #[deprecated = "Use read_chunk_at instead"] + #[deprecated = "Use read_chunk instead"] pub fn read_strip(&mut self) -> TiffResult { - let result = self.read_chunk_at(self.current_chunk)?; + let result = self.read_chunk(self.current_chunk)?; Ok(result.0) } @@ -1154,9 +1154,9 @@ impl Decoder { /// 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. - #[deprecated = "Use read_chunk_at instead"] + #[deprecated = "Use read_chunk instead"] pub fn read_tile(&mut self) -> TiffResult { - let result = self.read_chunk_at(self.current_chunk)?; + let result = self.read_chunk(self.current_chunk)?; self.current_chunk += 1; @@ -1165,15 +1165,22 @@ impl Decoder { /// Read the specified chunk (at index `chunk_index`) and return the binary data as a Vector and /// `ChunkInfo` describing the size of the chunk. - pub fn read_chunk_at(&mut self, chunk_index: usize) -> TiffResult<(DecodingResult, ChunkInfo)> { + pub fn read_chunk(&mut self, chunk_index: u32) -> TiffResult<(DecodingResult, ChunkInfo)> { let chunk_info = self .image() .chunk_info(chunk_index) .ok_or(UsageError::InvalidChunkIndex(chunk_index))?; - let mut result = self.result_buffer(chunk_info.data_width, chunk_info.data_height)?; + let mut result = self.result_buffer( + chunk_info.data_width as usize, + chunk_info.data_height as usize, + )?; - self.read_chunk_to_buffer(result.as_buffer(0), chunk_index, chunk_info.data_width)?; + self.read_chunk_to_buffer( + result.as_buffer(0), + chunk_index, + chunk_info.data_width as usize, + )?; Ok((result, chunk_info)) } @@ -1220,7 +1227,7 @@ impl Decoder { result.as_buffer(buffer_offset).copy(), width, byte_order, - chunk, + chunk as u32, )?; } diff --git a/src/error.rs b/src/error.rs index c03d371..8c8ac8b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -205,7 +205,7 @@ impl fmt::Display for TiffUnsupportedError { #[derive(Debug)] pub enum UsageError { InvalidChunkType(ChunkType, ChunkType), - InvalidChunkIndex(usize), + InvalidChunkIndex(u32), } impl fmt::Display for UsageError { diff --git a/tests/decode_images.rs b/tests/decode_images.rs index f96046e..de92c45 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -275,7 +275,7 @@ fn test_tiled_incremental() { assert_eq!(tiles as usize, sums.len()); for tile in 0..tiles { - match decoder.read_chunk_at(tile as usize).unwrap() { + match decoder.read_chunk(tile).unwrap() { (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 8db3bac..f336636 100644 --- a/tests/encode_images.rs +++ b/tests/encode_images.rs @@ -512,7 +512,7 @@ fn test_rows_per_strip() { for i in 0..50 { let img2 = [i; 2 * 100]; - match decoder.read_chunk_at(i as usize).unwrap() { + match decoder.read_chunk(i as u32).unwrap() { (DecodingResult::U8(data), _) => assert_eq!(&img2[..], &data[..]), other => panic!("Incorrect strip type {:?}", other), } From 802949f6e192073ae12c29c3c15bcabe7810f1d3 Mon Sep 17 00:00:00 2001 From: Alan Race Date: Mon, 18 Jul 2022 17:15:44 +0200 Subject: [PATCH 23/26] Removed `ChunkInfo` and added additional functions on decoder --- src/decoder/ifd.rs | 4 +- src/decoder/image.rs | 108 +++++++++------------------- src/decoder/mod.rs | 55 +++++++------- src/encoder/compression/deflate.rs | 2 +- src/encoder/compression/packbits.rs | 4 +- tests/decode_images.rs | 2 +- tests/encode_images.rs | 2 +- 7 files changed, 69 insertions(+), 108 deletions(-) diff --git a/src/decoder/ifd.rs b/src/decoder/ifd.rs index bea51a1..d028ebb 100644 --- a/src/decoder/ifd.rs +++ b/src/decoder/ifd.rs @@ -451,8 +451,8 @@ impl Entry { // Case 3: There is more than one value, but it fits in the offset field. if value_bytes <= 4 || bigtiff && value_bytes <= 8 { match self.type_ { - Type::BYTE => return Ok(offset_to_bytes(self.count as usize, self)?), - Type::SBYTE => return Ok(offset_to_sbytes(self.count as usize, self)?), + Type::BYTE => return offset_to_bytes(self.count as usize, self), + Type::SBYTE => return offset_to_sbytes(self.count as usize, self), Type::ASCII => { let mut buf = vec![0; self.count as usize]; self.r(bo).read_exact(&mut buf)?; diff --git a/src/decoder/image.rs b/src/decoder/image.rs index d761411..00cdd65 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -61,39 +61,6 @@ impl TileAttributes { } } -#[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). This has the same - /// value for all chunks in the image. - pub chunk_width: u32, - /// Height/Length of the chunk as specified (includes potential padding). This has - /// the same value for all chunks in the image. - pub chunk_height: u32, - - /// 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: u32, - /// 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: u32, -} - -impl ChunkInfo { - #[inline] - /// Returns the amount of padding (pixles) on the right-side of the chunk. - pub fn padding_right(&self) -> u32 { - 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) -> u32 { - self.chunk_height - self.data_height - } -} - #[derive(Debug)] pub(crate) struct Image { pub ifd: Option, @@ -408,39 +375,38 @@ impl Image { Ok((*file_offset, *compressed_bytes)) } - pub(crate) fn chunk_dimensions(&self) -> TiffResult<(usize, usize)> { + pub(crate) fn chunk_dimensions(&self) -> TiffResult<(u32, u32)> { match self.chunk_type { ChunkType::Strip => { - let width = usize::try_from(self.width)?; let strip_attrs = self.strip_decoder.as_ref().unwrap(); - Ok((width, usize::try_from(strip_attrs.rows_per_strip)?)) + Ok((self.width, strip_attrs.rows_per_strip)) } ChunkType::Tile => { let tile_attrs = self.tile_attributes.as_ref().unwrap(); - Ok((tile_attrs.tile_width, tile_attrs.tile_length)) + Ok(( + u32::try_from(tile_attrs.tile_width)?, + u32::try_from(tile_attrs.tile_length)?, + )) } } } - pub(crate) fn chunk_info(&self, chunk_index: u32) -> Option { - match &self.chunk_type { - ChunkType::Strip => { - let rows_per_strip = self.strip_decoder.as_ref().unwrap().rows_per_strip; + pub(crate) fn chunk_data_dimensions(&self, chunk_index: u32) -> TiffResult<(u32, u32)> { + let dims = self.chunk_dimensions()?; + match self.chunk_type { + ChunkType::Strip => { let strip_height_without_padding = chunk_index - .checked_mul(rows_per_strip) - .and_then(|x| self.height.checked_sub(x))?; + .checked_mul(dims.1) + .and_then(|x| self.height.checked_sub(x)) + .ok_or(TiffError::UsageError(UsageError::InvalidChunkIndex( + chunk_index, + )))?; // Ignore potential vertical padding on the bottommost strip - let strip_height = rows_per_strip.min(strip_height_without_padding); + let strip_height = dims.1.min(strip_height_without_padding); - Some(ChunkInfo { - chunk_width: self.width, - chunk_height: rows_per_strip, - - data_width: self.width, - data_height: strip_height, - }) + Ok((dims.0, strip_height)) } ChunkType::Tile => { let tile_attrs = self.tile_attributes.as_ref().unwrap(); @@ -449,13 +415,7 @@ impl Image { 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 as u32, - chunk_height: tile_attrs.tile_length as u32, - - data_width: tile_width as u32, - data_height: tile_length as u32, - }) + Ok((u32::try_from(tile_width)?, u32::try_from(tile_length)?)) } } } @@ -528,21 +488,21 @@ impl Image { let predictor = self.predictor; let samples = self.bits_per_sample.len(); - let chunk_info = self.chunk_info(chunk_index).ok_or(TiffError::UsageError( - UsageError::InvalidChunkIndex(chunk_index), - ))?; + let chunk_dims = self.chunk_dimensions()?; + let data_dims = self.chunk_data_dimensions(chunk_index)?; + + let padding_right = chunk_dims.0 - data_dims.0; let jpeg_tables = self.jpeg_tables.clone(); let mut reader = Self::create_reader(reader, compression_method, *compressed_bytes, jpeg_tables)?; - if output_width == chunk_info.data_width as usize && chunk_info.padding_right() == 0 { - let total_samples = - chunk_info.data_width as usize * chunk_info.data_height as usize * samples; + if output_width == data_dims.0 as usize && padding_right == 0 { + let total_samples = data_dims.0 as usize * data_dims.1 as usize * samples; let tile = &mut buffer.as_bytes_mut()[..total_samples * byte_len]; reader.read_exact(tile)?; - for row in 0..chunk_info.data_height as usize { + for row in 0..data_dims.1 as usize { let row_start = row as usize * output_width as usize * samples; let row_end = (row + 1) * output_width as usize * samples; let row = buffer.subrange(row_start..row_end); @@ -551,13 +511,14 @@ impl Image { if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { super::invert_colors(&mut buffer.subrange(0..total_samples), color_type); } - } else if chunk_info.padding_right() > 0 && self.predictor == Predictor::FloatingPoint { + } else if padding_right > 0 && self.predictor == Predictor::FloatingPoint { // The floating point predictor shuffles the padding bytes into the encoded output, so // this case is handled specially when needed. - let mut encoded = vec![0u8; chunk_info.chunk_width as usize * samples * byte_len]; - for row in 0..chunk_info.data_height as usize { + let mut encoded = vec![0u8; chunk_dims.0 as usize * samples * byte_len]; + + for row in 0..data_dims.1 as usize { let row_start = row * output_width as usize * samples; - let row_end = row_start + chunk_info.data_width as usize * samples; + let row_end = row_start + data_dims.0 as usize * samples; reader.read_exact(&mut encoded)?; match buffer.subrange(row_start..row_end) { @@ -570,17 +531,16 @@ impl Image { } } } else { - for row in 0..chunk_info.data_height as usize { + for row in 0..data_dims.1 as usize { let row_start = row * output_width as usize * samples; - let row_end = row_start + chunk_info.data_width as usize * samples; + let row_end = row_start + data_dims.0 as usize * samples; let row = &mut buffer.as_bytes_mut()[(row_start * byte_len)..(row_end * byte_len)]; reader.read_exact(row)?; // Skip horizontal padding - if chunk_info.padding_right() > 0 { - let len = - u64::try_from(chunk_info.padding_right() as usize * samples * byte_len)?; + if padding_right > 0 { + let len = u64::try_from(padding_right as usize * samples * byte_len)?; io::copy(&mut reader.by_ref().take(len), &mut io::sink())?; } diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 2dd020a..4b85126 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -8,7 +8,7 @@ use crate::{ }; use self::ifd::Directory; -use self::image::{ChunkInfo, Image}; +use self::image::Image; use crate::tags::{ CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag, Type, }; @@ -1147,7 +1147,7 @@ impl Decoder { pub fn read_strip(&mut self) -> TiffResult { let result = self.read_chunk(self.current_chunk)?; - Ok(result.0) + Ok(result) } /// Read a single tile from the image and return it as a Vector. This method does not return @@ -1160,36 +1160,37 @@ impl Decoder { self.current_chunk += 1; - Ok(result.0) + Ok(result) } - /// Read the specified chunk (at index `chunk_index`) and return the binary data as a Vector and - /// `ChunkInfo` describing the size of the chunk. - pub fn read_chunk(&mut self, chunk_index: u32) -> TiffResult<(DecodingResult, ChunkInfo)> { - let chunk_info = self - .image() - .chunk_info(chunk_index) - .ok_or(UsageError::InvalidChunkIndex(chunk_index))?; + /// Read the specified chunk (at index `chunk_index`) and return the binary data as a Vector. + pub fn read_chunk(&mut self, chunk_index: u32) -> TiffResult { + let data_dims = self.image().chunk_data_dimensions(chunk_index)?; - let mut result = self.result_buffer( - chunk_info.data_width as usize, - chunk_info.data_height as usize, - )?; + let mut result = self.result_buffer(data_dims.0 as usize, data_dims.1 as usize)?; - self.read_chunk_to_buffer( - result.as_buffer(0), - chunk_index, - chunk_info.data_width as usize, - )?; + self.read_chunk_to_buffer(result.as_buffer(0), chunk_index, data_dims.0 as usize)?; - Ok((result, chunk_info)) + Ok(result) + } + + /// Returns the default chunk size for the current image. Any given chunk in the image is at most as large as + /// the value returned here. For the size of the data (chunk minus padding), use `chunk_data_dimensions`. + pub fn chunk_dimensions(&self) -> TiffResult<(u32, u32)> { + self.image().chunk_dimensions() + } + + /// Returns the size of the data in the chunk with the specified index. This is the default size of the chunk, + /// minus any padding. + pub fn chunk_data_dimensions(&self, chunk_index: u32) -> TiffResult<(u32, u32)> { + self.image().chunk_data_dimensions(chunk_index) } /// Decodes the entire image and return it as a Vector pub fn read_image(&mut self) -> TiffResult { - let width = usize::try_from(self.image().width)?; - let height = usize::try_from(self.image().height)?; - let mut result = self.result_buffer(width, height)?; + let width = self.image().width; + let height = self.image().height; + let mut result = self.result_buffer(width as usize, height as usize)?; if width == 0 || height == 0 { return Ok(result); } @@ -1212,20 +1213,20 @@ impl Decoder { )); } - let chunks_across = (width - 1) / chunk_dimensions.0 + 1; - let strip_samples = width * chunk_dimensions.1 * samples; + let chunks_across = ((width - 1) / chunk_dimensions.0 + 1) as usize; + let strip_samples = width as usize * chunk_dimensions.1 as usize * samples; for chunk in 0..self.image().chunk_offsets.len() { self.goto_offset_u64(self.image().chunk_offsets[chunk])?; let x = chunk % chunks_across; let y = chunk / chunks_across; - let buffer_offset = y * strip_samples + x * chunk_dimensions.0 * samples; + let buffer_offset = y * strip_samples + x * chunk_dimensions.0 as usize * samples; let byte_order = self.reader.byte_order; self.image.expand_chunk( &mut self.reader, result.as_buffer(buffer_offset).copy(), - width, + width as usize, byte_order, chunk as u32, )?; diff --git a/src/encoder/compression/deflate.rs b/src/encoder/compression/deflate.rs index db64b70..5e7a261 100644 --- a/src/encoder/compression/deflate.rs +++ b/src/encoder/compression/deflate.rs @@ -53,7 +53,7 @@ impl Compression for Deflate { impl CompressionAlgorithm for Deflate { fn write_to(&mut self, writer: &mut W, bytes: &[u8]) -> Result { let mut encoder = ZlibEncoder::new(writer, self.level); - encoder.write_all(&bytes)?; + encoder.write_all(bytes)?; encoder.try_finish()?; Ok(encoder.total_out()) } diff --git a/src/encoder/compression/packbits.rs b/src/encoder/compression/packbits.rs index 464d1e7..7ba3833 100644 --- a/src/encoder/compression/packbits.rs +++ b/src/encoder/compression/packbits.rs @@ -157,7 +157,7 @@ mod tests { let mut compressed_data = Vec::::new(); let mut writer = Cursor::new(&mut compressed_data); Packbits::default() - .write_to(&mut writer, &UNCOMPRESSED_DATA) + .write_to(&mut writer, UNCOMPRESSED_DATA) .unwrap(); assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA); } @@ -207,7 +207,7 @@ mod tests { let mut compressed_data = Vec::::new(); let mut writer = Cursor::new(&mut compressed_data); Packbits::default() - .write_to(&mut writer, &TEST_DATA) + .write_to(&mut writer, TEST_DATA) .unwrap(); assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA); } diff --git a/tests/decode_images.rs b/tests/decode_images.rs index de92c45..6a262bf 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_chunk(tile).unwrap() { - (DecodingResult::U8(res), _) => { + 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 f336636..9fd9474 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_chunk(i as u32).unwrap() { - (DecodingResult::U8(data), _) => assert_eq!(&img2[..], &data[..]), + DecodingResult::U8(data) => assert_eq!(&img2[..], &data[..]), other => panic!("Incorrect strip type {:?}", other), } } From 723ef6431a68dade6e55890c7dd3bc1fc1e65860 Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Mon, 18 Jul 2022 19:36:28 -0700 Subject: [PATCH 24/26] Update src/decoder/mod.rs --- src/decoder/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 4b85126..f54d3f2 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -1176,8 +1176,8 @@ impl Decoder { /// Returns the default chunk size for the current image. Any given chunk in the image is at most as large as /// the value returned here. For the size of the data (chunk minus padding), use `chunk_data_dimensions`. - pub fn chunk_dimensions(&self) -> TiffResult<(u32, u32)> { - self.image().chunk_dimensions() + pub fn chunk_dimensions(&self) -> (u32, u32) { + self.image().chunk_dimensions().unwrap() } /// Returns the size of the data in the chunk with the specified index. This is the default size of the chunk, From 7c4a6fae0876ef5dc672078b4ee97a92746c1ece Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Mon, 18 Jul 2022 19:36:35 -0700 Subject: [PATCH 25/26] Update src/decoder/mod.rs --- src/decoder/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index f54d3f2..8c76767 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -1182,8 +1182,8 @@ impl Decoder { /// Returns the size of the data in the chunk with the specified index. This is the default size of the chunk, /// minus any padding. - pub fn chunk_data_dimensions(&self, chunk_index: u32) -> TiffResult<(u32, u32)> { - self.image().chunk_data_dimensions(chunk_index) + pub fn chunk_data_dimensions(&self, chunk_index: u32) -> (u32, u32) { + self.image().chunk_data_dimensions(chunk_index).expect("invalid chunk_index") } /// Decodes the entire image and return it as a Vector From 5003803c970095784fd5102612b34d946ca822d2 Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Sat, 23 Jul 2022 15:27:02 -0700 Subject: [PATCH 26/26] cargo fmt --- src/decoder/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 8c76767..5359339 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -1183,7 +1183,9 @@ impl Decoder { /// Returns the size of the data in the chunk with the specified index. This is the default size of the chunk, /// minus any padding. pub fn chunk_data_dimensions(&self, chunk_index: u32) -> (u32, u32) { - self.image().chunk_data_dimensions(chunk_index).expect("invalid chunk_index") + self.image() + .chunk_data_dimensions(chunk_index) + .expect("invalid chunk_index") } /// Decodes the entire image and return it as a Vector