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 c525ed2..00cdd65 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; @@ -357,28 +357,65 @@ 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)) } - 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_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(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 = dims.1.min(strip_height_without_padding); + + Ok((dims.0, strip_height)) + } + ChunkType::Tile => { + let tile_attrs = self.tile_attributes.as_ref().unwrap(); + 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; + + Ok((u32::try_from(tile_width)?, u32::try_from(tile_length)?)) } } } @@ -389,7 +426,7 @@ impl Image { mut buffer: DecodingBuffer, output_width: usize, byte_order: ByteOrder, - chunk: usize, + chunk_index: u32, ) -> TiffResult<()> { // Validate that the provided buffer is of the expected type. let color_type = self.colortype()?; @@ -438,9 +475,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 as usize) + .ok_or(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + ))?; let byte_len = buffer.byte_len(); let compression_method = self.compression_method; @@ -448,48 +488,37 @@ 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_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_dimensions.0 && padding.0 == 0 { - let total_samples = chunk_dimensions.0 * (chunk_dimensions.1 - padding.1) * 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_dimensions.1 - padding.1) { - let row_start = row * output_width * samples; - let row_end = (row + 1) * output_width * samples; + 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); super::fix_endianness_and_predict(row, samples, byte_order, predictor); } 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 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 row_start = row * output_width * samples; - let row_end = row_start + (chunk_dimensions.0 - padding.0) * samples; + 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 + data_dims.0 as usize * samples; reader.read_exact(&mut encoded)?; match buffer.subrange(row_start..row_end) { @@ -502,16 +531,16 @@ impl Image { } } } else { - for row in 0..(chunk_dimensions.1 - padding.1) { - let row_start = row * output_width * samples; - let row_end = row_start + (chunk_dimensions.0 - padding.0) * samples; + for row in 0..data_dims.1 as usize { + let row_start = row * output_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 padding.0 > 0 { - let len = u64::try_from(padding.0 * 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 c4299af..5359339 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -1,7 +1,7 @@ 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, @@ -301,8 +301,9 @@ where reader: SmartReader, bigtiff: bool, limits: Limits, + current_chunk: u32, next_ifd: Option, - current_chunk: usize, + ifd_offsets: Vec, seen_ifds: HashSet, image: Image, } @@ -559,12 +560,15 @@ impl Decoder { let mut seen_ifds = HashSet::new(); seen_ifds.insert(*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, + ifd_offsets, + seen_ifds, image: Image { ifd: None, width: 0, @@ -583,7 +587,6 @@ impl Decoder { chunk_bytes: Vec::new(), }, current_chunk: 0, - seen_ifds, }; decoder.next_image()?; Ok(decoder) @@ -606,6 +609,46 @@ impl Decoder { &self.image } + /// Loads the IFD at the specified index in the list, if one exists + 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 + if self.next_ifd.is_none() { + return Err(TiffError::FormatError( + TiffFormatError::ImageFileDirectoryNotFound, + )); + } + + loop { + // 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.ifd_offsets.len() { + break; + } + } + } + + // If the index is within the list of ifds then we can load the selected image/IFD + 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)?; + + 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 +656,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 +674,18 @@ impl Decoder { return Err(TiffError::FormatError(TiffFormatError::CycleInOffsets)); } self.next_ifd = Some(next); + self.ifd_offsets.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(()) @@ -992,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)?; @@ -1013,6 +1064,28 @@ impl Decoder { Ok(()) } + pub fn read_chunk_to_buffer( + &mut self, + mut buffer: DecodingBuffer, + chunk_index: u32, + output_width: usize, + ) -> TiffResult<()> { + let offset = self.image.chunk_file_range(chunk_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, + chunk_index, + )?; + + Ok(()) + } + fn result_buffer(&self, width: usize, height: usize) -> TiffResult { let buffer_size = match width .checked_mul(height) @@ -1066,61 +1139,60 @@ 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. + #[deprecated = "Use read_chunk instead"] pub fn read_strip(&mut self) -> TiffResult { - 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 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)?; - self.read_strip_to_buffer(result.as_buffer(0))?; + let result = self.read_chunk(self.current_chunk)?; Ok(result) } - /// 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. + #[deprecated = "Use read_chunk instead"] 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_chunk(self.current_chunk)?; self.current_chunk += 1; Ok(result) } + /// 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(data_dims.0 as usize, data_dims.1 as usize)?; + + self.read_chunk_to_buffer(result.as_buffer(0), chunk_index, data_dims.0 as usize)?; + + 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) -> (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, + /// 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") + } + /// 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); } @@ -1143,22 +1215,22 @@ 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, + 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/src/error.rs b/src/error.rs index 0f91ad8..8c8ac8b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -205,6 +205,7 @@ impl fmt::Display for TiffUnsupportedError { #[derive(Debug)] pub enum UsageError { InvalidChunkType(ChunkType, ChunkType), + InvalidChunkIndex(u32), } impl fmt::Display for UsageError { @@ -218,6 +219,7 @@ impl fmt::Display for UsageError { expected, actual ) } + InvalidChunkIndex(index) => write!(fmt, "Image chunk index ({}) requested.", index), } } } @@ -290,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 df43524..6a262bf 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(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 a8e1ca3..9fd9474 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(i as u32).unwrap() { DecodingResult::U8(data) => assert_eq!(&img2[..], &data[..]), other => panic!("Incorrect strip type {:?}", other), }