From 47080293db91ab1100ee0b31daed8c9a4aee78f3 Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Fri, 7 Jan 2022 16:22:17 -0500 Subject: [PATCH] Move image loading to Image::from_reader --- src/decoder/image.rs | 199 ++++++++++++++++++++++++++++++++++++-- src/decoder/mod.rs | 143 ++------------------------- src/decoder/tag_reader.rs | 48 +++++++++ 3 files changed, 251 insertions(+), 139 deletions(-) create mode 100644 src/decoder/tag_reader.rs diff --git a/src/decoder/image.rs b/src/decoder/image.rs index 3c20736..fc01617 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -1,8 +1,11 @@ -use super::ChunkType; -use super::ifd::Directory; -use crate::tags::{ - CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, -}; +use super::ifd::{Directory, Value}; +use super::tag_reader::TagReader; +use super::Limits; +use super::{stream::SmartReader, ChunkType}; +use crate::tags::{CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag}; +use crate::{TiffError, TiffFormatError, TiffResult, TiffUnsupportedError}; +use std::convert::{TryFrom, TryInto}; +use std::io::{Read, Seek}; #[derive(Debug)] pub(crate) struct StripDecodeState { @@ -92,5 +95,189 @@ pub(crate) struct Image { } impl Image { + pub fn from_reader( + reader: &mut SmartReader, + ifd: Directory, + limits: &Limits, + bigtiff: bool, + ) -> TiffResult { + let mut tag_reader = TagReader { + reader, + limits, + ifd: &ifd, + bigtiff, + }; -} \ No newline at end of file + let width = tag_reader.require_tag(Tag::ImageWidth)?.into_u32()?; + let height = tag_reader.require_tag(Tag::ImageLength)?.into_u32()?; + + let photometric_interpretation = tag_reader + .find_tag(Tag::PhotometricInterpretation)? + .map(Value::into_u16) + .transpose()? + .and_then(PhotometricInterpretation::from_u16) + .ok_or(TiffUnsupportedError::UnknownInterpretation)?; + + // Try to parse both the compression method and the number, format, and bits of the included samples. + // If they are not explicitly specified, those tags are reset to their default values and not carried from previous images. + let compression_method = match tag_reader.find_tag(Tag::Compression)? { + Some(val) => CompressionMethod::from_u16(val.into_u16()?) + .ok_or(TiffUnsupportedError::UnknownCompressionMethod)?, + None => CompressionMethod::None, + }; + + let jpeg_tables = if compression_method == CompressionMethod::ModernJPEG + && ifd.contains_key(&Tag::JPEGTables) + { + let vec = tag_reader + .find_tag(Tag::JPEGTables)? + .unwrap() + .into_u8_vec()?; + if vec.len() < 2 { + return Err(TiffError::FormatError( + TiffFormatError::InvalidTagValueType(Tag::JPEGTables), + )); + } + Some(vec) + } else { + None + }; + + let samples = tag_reader + .find_tag(Tag::SamplesPerPixel)? + .map(Value::into_u16) + .transpose()? + .unwrap_or(1) + .try_into()?; + + let sample_format = match tag_reader.find_tag_uint_vec(Tag::SampleFormat)? { + Some(vals) => { + let sample_format: Vec<_> = vals + .into_iter() + .map(SampleFormat::from_u16_exhaustive) + .collect(); + + // TODO: for now, only homogenous formats across samples are supported. + if !sample_format.windows(2).all(|s| s[0] == s[1]) { + return Err(TiffUnsupportedError::UnsupportedSampleFormat(sample_format).into()); + } + + sample_format + } + None => vec![SampleFormat::Uint], + }; + + let bits_per_sample = match samples { + 1 | 3 | 4 => tag_reader + .find_tag_uint_vec(Tag::BitsPerSample)? + .unwrap_or_else(|| vec![1]), + _ => return Err(TiffUnsupportedError::UnsupportedSampleDepth(samples).into()), + }; + + let predictor = tag_reader + .find_tag(Tag::Predictor)? + .map(Value::into_u16) + .transpose()? + .map(|p| { + Predictor::from_u16(p) + .ok_or(TiffError::FormatError(TiffFormatError::UnknownPredictor(p))) + }) + .transpose()? + .unwrap_or(Predictor::None); + + let chunk_type; + let chunk_offsets; + let chunk_bytes; + let strip_decoder; + let tile_attributes; + match ( + ifd.contains_key(&Tag::StripByteCounts), + ifd.contains_key(&Tag::StripOffsets), + ifd.contains_key(&Tag::TileByteCounts), + ifd.contains_key(&Tag::TileOffsets), + ) { + (true, true, false, false) => { + chunk_type = ChunkType::Strip; + + chunk_offsets = tag_reader + .find_tag(Tag::StripOffsets)? + .unwrap() + .into_u64_vec()?; + chunk_bytes = tag_reader + .find_tag(Tag::StripByteCounts)? + .unwrap() + .into_u64_vec()?; + let rows_per_strip = tag_reader + .find_tag(Tag::RowsPerStrip)? + .map(Value::into_u32) + .transpose()? + .unwrap_or(height); + strip_decoder = Some(StripDecodeState { rows_per_strip }); + tile_attributes = None; + } + (false, false, true, true) => { + chunk_type = ChunkType::Tile; + + let tile_width = + usize::try_from(tag_reader.require_tag(Tag::TileWidth)?.into_u32()?)?; + let tile_length = + usize::try_from(tag_reader.require_tag(Tag::TileLength)?.into_u32()?)?; + + if tile_width == 0 { + return Err(TiffFormatError::InvalidTagValueType(Tag::TileWidth).into()); + } else if tile_length == 0 { + return Err(TiffFormatError::InvalidTagValueType(Tag::TileLength).into()); + } + + strip_decoder = None; + tile_attributes = Some(TileAttributes { + image_width: usize::try_from(width)?, + image_height: usize::try_from(height)?, + samples_per_pixel: bits_per_sample.len(), + tile_width, + tile_length, + }); + chunk_offsets = tag_reader + .find_tag(Tag::TileOffsets)? + .unwrap() + .into_u64_vec()?; + chunk_bytes = tag_reader + .find_tag(Tag::TileByteCounts)? + .unwrap() + .into_u64_vec()?; + + let tile = tile_attributes.as_ref().unwrap(); + if chunk_offsets.len() != chunk_bytes.len() + || chunk_offsets.len() != tile.tiles_down() * tile.tiles_across() + { + return Err(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + )); + } + } + (_, _, _, _) => { + return Err(TiffError::FormatError( + TiffFormatError::StripTileTagConflict, + )) + } + }; + + Ok(Image { + ifd: Some(ifd), + width, + height, + bits_per_sample, + samples, + sample_format, + photometric_interpretation, + compression_method, + jpeg_tables, + predictor, + chunk_type, + strip_decoder, + tile_attributes, + chunk_offsets, + chunk_bytes, + }) + } +} diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index a5adf0b..994cc37 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -8,7 +8,7 @@ use crate::{ }; use self::ifd::Directory; -use self::image::{Image, StripDecodeState, TileAttributes}; +use self::image::Image; use crate::tags::{ CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag, Type, }; @@ -20,6 +20,7 @@ use self::stream::{ pub mod ifd; mod image; mod stream; +mod tag_reader; /// Result of a decoding process #[derive(Debug)] @@ -594,139 +595,15 @@ 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 = self.read_ifd()?; + self.current_chunk = 0; - - self.image.ifd = Some(self.read_ifd()?); - self.image.width = self.get_tag_u32(Tag::ImageWidth)?; - self.image.height = self.get_tag_u32(Tag::ImageLength)?; - self.image.strip_decoder = None; - self.image.tile_attributes = None; - self.image.chunk_offsets.clear(); - self.image.chunk_bytes.clear(); - self.image.jpeg_tables = None; - - self.image.photometric_interpretation = self - .find_tag_unsigned(Tag::PhotometricInterpretation)? - .and_then(PhotometricInterpretation::from_u16) - .ok_or(TiffUnsupportedError::UnknownInterpretation)?; - - // Try to parse both the compression method and the number, format, and bits of the included samples. - // If they are not explicitly specified, those tags are reset to their default values and not carried from previous images. - self.image.compression_method = match self.find_tag_unsigned(Tag::Compression)? { - Some(val) => CompressionMethod::from_u16(val) - .ok_or(TiffUnsupportedError::UnknownCompressionMethod)?, - None => CompressionMethod::None, - }; - - if self.image.compression_method == CompressionMethod::ModernJPEG { - self.image.jpeg_tables = self - .find_tag(Tag::JPEGTables)? - .map(|_| { - let vec = self.get_tag_u8_vec(Tag::JPEGTables)?; - if vec.len() < 2 { - return Err(TiffError::FormatError( - TiffFormatError::InvalidTagValueType(Tag::JPEGTables), - )); - } - Ok(vec) - }) - .transpose()?; - } - - self.image.samples = self.find_tag_unsigned(Tag::SamplesPerPixel)?.unwrap_or(1); - - self.image.sample_format = match self.find_tag_unsigned_vec(Tag::SampleFormat)? { - Some(vals) => { - let sample_format: Vec<_> = vals - .into_iter() - .map(SampleFormat::from_u16_exhaustive) - .collect(); - - // TODO: for now, only homogenous formats across samples are supported. - if !sample_format.windows(2).all(|s| s[0] == s[1]) { - return Err(TiffUnsupportedError::UnsupportedSampleFormat( - self.image.sample_format.clone(), - ) - .into()); - } - - sample_format - } - None => vec![SampleFormat::Uint], - }; - - self.image.bits_per_sample = match self.image.samples { - 1 | 3 | 4 => self - .find_tag_unsigned_vec(Tag::BitsPerSample)? - .unwrap_or_else(|| vec![1]), - _ => return Err(TiffUnsupportedError::UnsupportedSampleDepth(self.image.samples).into()), - }; - - self.image.predictor = self - .find_tag_unsigned(Tag::Predictor)? - .map(|p| { - Predictor::from_u16(p) - .ok_or(TiffError::FormatError(TiffFormatError::UnknownPredictor(p))) - }) - .transpose()? - .unwrap_or(Predictor::None); - - let ifd = self.image.ifd.as_ref().unwrap(); - match ( - ifd.contains_key(&Tag::StripByteCounts), - ifd.contains_key(&Tag::StripOffsets), - ifd.contains_key(&Tag::TileByteCounts), - ifd.contains_key(&Tag::TileOffsets), - ) { - (true, true, false, false) => { - self.image.chunk_type = ChunkType::Strip; - - self.image.chunk_offsets = self.get_tag_u64_vec(Tag::StripOffsets)?; - self.image.chunk_bytes = self.get_tag_u64_vec(Tag::StripByteCounts)?; - let rows_per_strip = self - .find_tag(Tag::RowsPerStrip)? - .unwrap_or(ifd::Value::Unsigned(self.image.height)) - .into_u32()?; - self.image.strip_decoder = Some(StripDecodeState { rows_per_strip }); - } - (false, false, true, true) => { - self.image.chunk_type = ChunkType::Tile; - - let tile_width = usize::try_from(self.get_tag_u32(Tag::TileWidth)?)?; - let tile_length = usize::try_from(self.get_tag_u32(Tag::TileLength)?)?; - - if tile_width == 0 { - return Err(TiffFormatError::InvalidTagValueType(Tag::TileWidth).into()); - } else if tile_length == 0 { - return Err(TiffFormatError::InvalidTagValueType(Tag::TileLength).into()); - } - - self.image.tile_attributes = Some(TileAttributes { - image_width: usize::try_from(self.image.width)?, - image_height: usize::try_from(self.image.height)?, - samples_per_pixel: self.image.bits_per_sample.len(), - tile_width, - tile_length, - }); - self.image.chunk_offsets = self.get_tag_u64_vec(Tag::TileOffsets)?; - self.image.chunk_bytes = self.get_tag_u64_vec(Tag::TileByteCounts)?; - - let tile = self.image.tile_attributes.as_ref().unwrap(); - if self.image.chunk_offsets.len() != self.image.chunk_bytes.len() - || self.image.chunk_offsets.len() != tile.tiles_down() * tile.tiles_across() - { - return Err(TiffError::FormatError( - TiffFormatError::InconsistentSizesEncountered, - )); - } - } - (_, _, _, _) => { - return Err(TiffError::FormatError( - TiffFormatError::StripTileTagConflict, - )) - } - }; - + self.image = Image::from_reader( + &mut self.reader, + ifd, + &self.limits, + self.bigtiff + )?; Ok(()) } diff --git a/src/decoder/tag_reader.rs b/src/decoder/tag_reader.rs new file mode 100644 index 0000000..6691964 --- /dev/null +++ b/src/decoder/tag_reader.rs @@ -0,0 +1,48 @@ +use std::convert::TryFrom; +use std::io::{Read, Seek}; + +use crate::tags::Tag; +use crate::{TiffError, TiffFormatError, TiffResult}; + +use super::ifd::{Directory, Value}; +use super::stream::SmartReader; +use super::Limits; + +pub(crate) struct TagReader<'a, R: Read + Seek> { + pub reader: &'a mut SmartReader, + pub ifd: &'a Directory, + pub limits: &'a Limits, + pub bigtiff: bool, +} +impl<'a, R: Read + Seek> TagReader<'a, R> { + pub(crate) fn find_tag(&mut self, tag: Tag) -> TiffResult> { + Ok(match self.ifd.get(&tag) { + Some(entry) => Some(entry.clone().val(self.limits, self.bigtiff, self.reader)?), + None => None, + }) + } + pub(crate) fn require_tag(&mut self, tag: Tag) -> TiffResult { + match self.find_tag(tag)? { + Some(val) => Ok(val), + None => Err(TiffError::FormatError( + TiffFormatError::RequiredTagNotFound(tag), + )), + } + } + pub fn find_tag_uint_vec>( + &mut self, + tag: Tag, + ) -> TiffResult>> { + self.find_tag(tag)? + .map(|v| v.into_u64_vec()) + .transpose()? + .map(|v| { + v.into_iter() + .map(|u| { + T::try_from(u).map_err(|_| TiffFormatError::InvalidTagValueType(tag).into()) + }) + .collect() + }) + .transpose() + } +}