From 0447d81b0724fc67eeb87485ba852fc48c6c49b2 Mon Sep 17 00:00:00 2001 From: Alan Race Date: Mon, 9 May 2022 09:56:46 +0200 Subject: [PATCH] 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)) }