From e9aa39d8956e255391ac9fba731e8b1825bff8c6 Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Thu, 6 Jan 2022 17:43:23 -0500 Subject: [PATCH 01/20] Initialize Decoder in new instead of init --- src/decoder/mod.rs | 119 +++++++++++++++++++++--------------------- src/decoder/stream.rs | 3 ++ 2 files changed, 62 insertions(+), 60 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index bd48457..b1c1346 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -525,12 +525,58 @@ fn rev_hpredict( impl Decoder { /// Create a new decoder that decodes from the stream ```r``` pub fn new(r: R) -> TiffResult> { - Decoder { - reader: SmartReader::wrap(r, ByteOrder::LittleEndian), - byte_order: ByteOrder::LittleEndian, - bigtiff: false, + let mut endianess = Vec::with_capacity(2); + r.take(2).read_to_end(&mut endianess)?; + let byte_order = match &*endianess { + b"II" => { + ByteOrder::LittleEndian + } + b"MM" => { + ByteOrder::BigEndian + } + _ => { + return Err(TiffError::FormatError( + TiffFormatError::TiffSignatureNotFound, + )) + } + }; + let mut reader = SmartReader::wrap(r, byte_order); + + let bigtiff = match reader.read_u16()? { + 42 => false, + 43 => { + // Read bytesize of offsets (in bigtiff it's alway 8 but provide a way to move to 16 some day) + if reader.read_u16()? != 8 { + return Err(TiffError::FormatError( + TiffFormatError::TiffSignatureNotFound, + )); + } + // This constant should always be 0 + if reader.read_u16()? != 0 { + return Err(TiffError::FormatError( + TiffFormatError::TiffSignatureNotFound, + )); + } + true + } + _ => { + return Err(TiffError::FormatError( + TiffFormatError::TiffSignatureInvalid, + )) + } + }; + let next_ifd = if bigtiff { + Some(reader.read_u64()?) + } else { + Some(u64::from(reader.read_u32()?)) + }; + + let decoder = Decoder { + reader, + byte_order, + bigtiff, limits: Default::default(), - next_ifd: None, + next_ifd, ifd: None, width: 0, height: 0, @@ -544,8 +590,9 @@ impl Decoder { tile_decoder: None, tile_attributes: None, seen_ifds: HashSet::new(), - } - .init() + }; + decoder.next_image()?; + Ok(decoder) } pub fn with_limits(mut self, limits: Limits) -> Decoder { @@ -599,59 +646,11 @@ impl Decoder { } } - fn read_header(&mut self) -> TiffResult<()> { - let mut endianess = Vec::with_capacity(2); - self.reader.by_ref().take(2).read_to_end(&mut endianess)?; - match &*endianess { - b"II" => { - self.byte_order = ByteOrder::LittleEndian; - self.reader.byte_order = ByteOrder::LittleEndian; - } - b"MM" => { - self.byte_order = ByteOrder::BigEndian; - self.reader.byte_order = ByteOrder::BigEndian; - } - _ => { - return Err(TiffError::FormatError( - TiffFormatError::TiffSignatureNotFound, - )) - } - } - match self.read_short()? { - 42 => self.bigtiff = false, - 43 => { - self.bigtiff = true; - // Read bytesize of offsets (in bigtiff it's alway 8 but provide a way to move to 16 some day) - if self.read_short()? != 8 { - return Err(TiffError::FormatError( - TiffFormatError::TiffSignatureNotFound, - )); - } - // This constant should always be 0 - if self.read_short()? != 0 { - return Err(TiffError::FormatError( - TiffFormatError::TiffSignatureNotFound, - )); - } - } - _ => { - return Err(TiffError::FormatError( - TiffFormatError::TiffSignatureInvalid, - )) - } - } - self.next_ifd = match self.read_ifd_offset_nonrepeating()? { - 0 => None, - n => Some(n), - }; - Ok(()) - } - - /// Initializes the decoder. - pub fn init(mut self) -> TiffResult> { - self.read_header()?; - self.next_image()?; - Ok(self) + /// Reset the decoder. + #[deprecated = "Never should have been public. Only use Decoder::new()"] + pub fn init(self) -> TiffResult> { + let Self { reader, .. } = self; + Self::new(reader.into_inner()) } /// Reads in the next image. diff --git a/src/decoder/stream.rs b/src/decoder/stream.rs index c1c990d..e64fef9 100644 --- a/src/decoder/stream.rs +++ b/src/decoder/stream.rs @@ -339,6 +339,9 @@ where pub fn wrap(reader: R, byte_order: ByteOrder) -> SmartReader { SmartReader { reader, byte_order } } + pub fn into_inner(self) -> R { + self.reader + } } impl EndianReader for SmartReader From 31da6737b9649fd6560152548c2cf0281c2ae126 Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Thu, 6 Jan 2022 20:26:27 -0500 Subject: [PATCH 02/20] More refactoring --- src/decoder/mod.rs | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index b1c1346..d1767d1 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -238,6 +238,7 @@ struct StripDecodeState { strip_index: usize, strip_offsets: Vec, strip_bytes: Vec, + rows_per_strip: u32, } #[derive(Debug)] @@ -524,9 +525,9 @@ fn rev_hpredict( impl Decoder { /// Create a new decoder that decodes from the stream ```r``` - pub fn new(r: R) -> TiffResult> { + pub fn new(mut r: R) -> TiffResult> { let mut endianess = Vec::with_capacity(2); - r.take(2).read_to_end(&mut endianess)?; + (&mut r).take(2).read_to_end(&mut endianess)?; let byte_order = match &*endianess { b"II" => { ByteOrder::LittleEndian @@ -571,7 +572,10 @@ impl Decoder { Some(u64::from(reader.read_u32()?)) }; - let decoder = Decoder { + let mut seen_ifds = HashSet::new(); + seen_ifds.insert(*next_ifd.as_ref().unwrap()); + + let mut decoder = Decoder { reader, byte_order, bigtiff, @@ -589,7 +593,7 @@ impl Decoder { strip_decoder: None, tile_decoder: None, tile_attributes: None, - seen_ifds: HashSet::new(), + seen_ifds, }; decoder.next_image()?; Ok(decoder) @@ -718,10 +722,14 @@ impl Decoder { let strip_offsets = self.get_tag_u64_vec(Tag::StripOffsets)?; let strip_bytes = self.get_tag_u64_vec(Tag::StripByteCounts)?; + let rows_per_strip = self.find_tag(Tag::RowsPerStrip)? + .unwrap_or(ifd::Value::Unsigned(self.height)) + .into_u32()?; self.strip_decoder = Some(StripDecodeState { strip_index: 0, strip_offsets, strip_bytes, + rows_per_strip, }); } (false, false, true, true) => { @@ -1337,7 +1345,7 @@ impl Decoder { /// Number of strips in image pub fn strip_count(&mut self) -> TiffResult { self.check_chunk_type(ChunkType::Strip)?; - let rows_per_strip = self.get_tag_u32(Tag::RowsPerStrip).unwrap_or(self.height); + let rows_per_strip = self.strip_decoder.as_ref().unwrap().rows_per_strip; if rows_per_strip == 0 { return Ok(0); @@ -1457,8 +1465,7 @@ impl Decoder { .ok_or(TiffError::FormatError( TiffFormatError::InconsistentSizesEncountered, ))?; - let tag_rows = self.get_tag_u32(Tag::RowsPerStrip).unwrap_or(self.height); - let rows_per_strip = usize::try_from(tag_rows)?; + let rows_per_strip = usize::try_from(self.strip_decoder.as_ref().unwrap().rows_per_strip)?; let sized_width = usize::try_from(self.width)?; let sized_height = usize::try_from(self.height)?; @@ -1622,8 +1629,7 @@ impl Decoder { self.check_chunk_type(ChunkType::Strip)?; let index = self.strip_decoder.as_ref().unwrap().strip_index; - let rows_per_strip = - usize::try_from(self.get_tag_u32(Tag::RowsPerStrip).unwrap_or(self.height))?; + let rows_per_strip = usize::try_from(self.strip_decoder.as_ref().unwrap().rows_per_strip)?; let strip_height = cmp::min( rows_per_strip, @@ -1674,8 +1680,7 @@ impl Decoder { } fn read_stripped_image(&mut self) -> TiffResult { - let rows_per_strip = - usize::try_from(self.get_tag_u32(Tag::RowsPerStrip).unwrap_or(self.height))?; + let rows_per_strip = usize::try_from(self.strip_decoder.as_ref().unwrap().rows_per_strip)?; let samples_per_strip = match usize::try_from(self.width)? .checked_mul(rows_per_strip) From dc95ce10acfe678baf8ab89a1634714407cd3e18 Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Thu, 6 Jan 2022 21:50:27 -0500 Subject: [PATCH 03/20] Remove some extremely brittle tests --- tests/decode_images.rs | 69 ------------------------------------------ 1 file changed, 69 deletions(-) diff --git a/tests/decode_images.rs b/tests/decode_images.rs index 7e33d17..d128efd 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -343,26 +343,6 @@ fn test_bad_strip_count() { } } -#[test] -fn test_too_big_buffer_size() { - let image = [ - 73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 99, 255, 255, 254, 1, 1, 4, 0, 1, - 0, 0, 0, 158, 0, 0, 251, 3, 1, 3, 255, 254, 255, 255, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, - 0, 0, 0, 0, 0, 17, 1, 4, 0, 9, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 0, 2, 0, 0, 0, 63, 0, 0, 0, - 22, 1, 4, 0, 1, 0, 0, 0, 44, 0, 0, 0, 23, 1, 4, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 1, 0, 178, - 178, - ]; - - let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap(); - - let err = decoder.read_image().unwrap_err(); - - match err { - tiff::TiffError::LimitsExceeded => {} - unexpected => panic!("Unexpected error {}", unexpected), - } -} - #[test] fn test_stripped_image_overflow() { let image = [ @@ -421,32 +401,6 @@ fn oom_2() { } } -#[test] -fn invalid_jpeg_tag() { - use tiff::tags; - use tiff::{TiffError, TiffFormatError}; - - let image = [ - 73, 73, 42, 0, 8, 0, 0, 0, 15, 0, 0, 254, 44, 1, 0, 0, 0, 0, 0, 32, 0, 0, 0, 1, 4, 0, 1, 0, - 0, 0, 0, 1, 0, 0, 91, 1, 1, 0, 0, 0, 0, 0, 242, 4, 0, 0, 0, 22, 0, 56, 77, 0, 77, 1, 0, 0, - 73, 42, 0, 1, 4, 0, 1, 0, 0, 0, 4, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 158, 0, 0, 251, 3, 1, - 3, 0, 1, 0, 0, 0, 7, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0, 17, 1, 4, 0, 1, 0, 0, 0, - 0, 0, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 4, 61, 1, 18, 0, 1, 0, 0, 0, 202, 0, 0, 0, 17, - 1, 100, 0, 129, 0, 0, 0, 0, 0, 0, 0, 232, 254, 252, 255, 254, 255, 255, 255, 1, 29, 0, 0, - 22, 1, 3, 0, 1, 0, 0, 0, 16, 0, 0, 0, 23, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 123, 73, 254, 0, - 73, - ]; - - let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap(); - - let err = decoder.read_image().unwrap_err(); - - match err { - TiffError::FormatError(TiffFormatError::InvalidTagValueType(tags::Tag::JPEGTables)) => {} - unexpected => panic!("Unexpected error {}", unexpected), - } -} - #[test] fn invalid_jpeg_tag_2() { use tiff::tags; @@ -473,29 +427,6 @@ fn invalid_jpeg_tag_2() { } } -#[test] -fn multiple_images_strip_height() { - use tiff::TiffError; - - let image = [ - 73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 2, 0, 0, 0, 61, 1, 9, 0, 46, 22, - 128, 0, 0, 0, 0, 1, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 17, 1, 4, 0, 27, 0, 0, 0, 0, 0, 0, - 0, 1, 1, 3, 0, 1, 0, 0, 0, 17, 1, 0, 231, 22, 1, 1, 0, 1, 0, 0, 0, 130, 0, 0, 0, 23, 1, 4, - 0, 14, 0, 0, 0, 0, 0, 0, 0, 133, 133, 133, 77, 77, 77, 0, 0, 22, 128, 0, 255, 255, 255, - 255, 255, - ]; - - let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap(); - - let _ = decoder.read_image().unwrap(); - let err = decoder.read_image().unwrap_err(); - - match err { - TiffError::IntSizeError => {} - unexpected => panic!("Unexpected error {}", unexpected), - } -} - #[test] fn timeout() { use tiff::{TiffError, TiffFormatError}; From a19d35233af57a1564937dab244a0a83190103ef Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Thu, 6 Jan 2022 21:51:22 -0500 Subject: [PATCH 04/20] Read predictor ahead of time --- src/decoder/mod.rs | 82 +++++++++++++++++++++------------------------- 1 file changed, 38 insertions(+), 44 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index d1767d1..91507dc 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -373,6 +373,7 @@ where sample_format: Vec, photometric_interpretation: PhotometricInterpretation, compression_method: CompressionMethod, + predictor: Predictor, chunk_type: ChunkType, strip_decoder: Option, tile_decoder: Option, @@ -529,12 +530,8 @@ impl Decoder { let mut endianess = Vec::with_capacity(2); (&mut r).take(2).read_to_end(&mut endianess)?; let byte_order = match &*endianess { - b"II" => { - ByteOrder::LittleEndian - } - b"MM" => { - ByteOrder::BigEndian - } + b"II" => ByteOrder::LittleEndian, + b"MM" => ByteOrder::BigEndian, _ => { return Err(TiffError::FormatError( TiffFormatError::TiffSignatureNotFound, @@ -589,6 +586,7 @@ impl Decoder { sample_format: vec![SampleFormat::Uint], photometric_interpretation: PhotometricInterpretation::BlackIsZero, compression_method: CompressionMethod::None, + predictor: Predictor::None, chunk_type: ChunkType::Strip, strip_decoder: None, tile_decoder: None, @@ -710,6 +708,15 @@ impl Decoder { _ => return Err(TiffUnsupportedError::UnsupportedSampleDepth(self.samples).into()), }; + self.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.ifd.as_ref().unwrap(); match ( ifd.contains_key(&Tag::StripByteCounts), @@ -722,7 +729,8 @@ impl Decoder { let strip_offsets = self.get_tag_u64_vec(Tag::StripOffsets)?; let strip_bytes = self.get_tag_u64_vec(Tag::StripByteCounts)?; - let rows_per_strip = self.find_tag(Tag::RowsPerStrip)? + let rows_per_strip = self + .find_tag(Tag::RowsPerStrip)? .unwrap_or(ifd::Value::Unsigned(self.height)) .into_u32()?; self.strip_decoder = Some(StripDecodeState { @@ -1495,24 +1503,17 @@ impl Decoder { if u32::try_from(index)? == self.strip_count()? { self.strip_decoder = None; } - if let Ok(predictor) = self.get_tag_unsigned(Tag::Predictor) { - match Predictor::from_u16(predictor) { - Some(Predictor::None) => (), - Some(Predictor::Horizontal) => { - rev_hpredict( - buffer.copy(), - (self.width, u32::try_from(strip_height)?), - usize::try_from(self.width)?, - self.colortype()?, - )?; - } - None => { - return Err(TiffError::FormatError(TiffFormatError::UnknownPredictor( - predictor, - ))) - } - Some(Predictor::__NonExhaustive) => unreachable!(), + match self.predictor { + Predictor::None => {} + Predictor::Horizontal => { + rev_hpredict( + buffer.copy(), + (self.width, u32::try_from(strip_height)?), + usize::try_from(self.width)?, + self.colortype()?, + )?; } + Predictor::__NonExhaustive => unreachable!(), } Ok(()) } @@ -1557,27 +1558,20 @@ impl Decoder { output_width, )?; - if let Ok(predictor) = self.get_tag_unsigned(Tag::Predictor) { - match Predictor::from_u16(predictor) { - Some(Predictor::None) => (), - Some(Predictor::Horizontal) => { - rev_hpredict( - result.copy(), - ( - u32::try_from(tile_width - padding_right)?, - u32::try_from(tile_length - padding_down)?, - ), - output_width, - self.colortype()?, - )?; - } - None => { - return Err(TiffError::FormatError(TiffFormatError::UnknownPredictor( - predictor, - ))) - } - Some(Predictor::__NonExhaustive) => unreachable!(), + match self.predictor { + Predictor::None => {} + Predictor::Horizontal => { + rev_hpredict( + result.copy(), + ( + u32::try_from(tile_width - padding_right)?, + u32::try_from(tile_length - padding_down)?, + ), + output_width, + self.colortype()?, + )?; } + Predictor::__NonExhaustive => unreachable!(), } Ok(()) } From 4c942ca6909d2b2ac55965f59fd285aa46e8b657 Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Thu, 6 Jan 2022 21:52:41 -0500 Subject: [PATCH 05/20] Read JPEGTables ahead of time --- src/decoder/mod.rs | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 91507dc..75b3cbe 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -374,6 +374,7 @@ where photometric_interpretation: PhotometricInterpretation, compression_method: CompressionMethod, predictor: Predictor, + jpeg_tables: Option>, chunk_type: ChunkType, strip_decoder: Option, tile_decoder: Option, @@ -586,6 +587,7 @@ impl Decoder { sample_format: vec![SampleFormat::Uint], photometric_interpretation: PhotometricInterpretation::BlackIsZero, compression_method: CompressionMethod::None, + jpeg_tables: None, predictor: Predictor::None, chunk_type: ChunkType::Strip, strip_decoder: None, @@ -679,6 +681,18 @@ impl Decoder { None => CompressionMethod::None, }; + if self.compression_method == CompressionMethod::ModernJPEG { + self.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.samples = self.find_tag_unsigned(Tag::SamplesPerPixel)?.unwrap_or(1); self.sample_format = match self.find_tag_unsigned_vec(Tag::SampleFormat)? { @@ -1378,25 +1392,11 @@ impl Decoder { } pub fn read_jpeg(&mut self) -> TiffResult { + self.check_chunk_type(ChunkType::Strip)?; + let offsets = self.get_tag_u32_vec(Tag::StripOffsets)?; let bytes = self.get_tag_u32_vec(Tag::StripByteCounts)?; - let jpeg_tables: Option> = match self.find_tag(Tag::JPEGTables) { - Ok(None) => None, - Ok(_) => { - let vec = self.get_tag_u8_vec(Tag::JPEGTables)?; - - if vec.len() < 2 { - return Err(TiffError::FormatError( - TiffFormatError::InvalidTagValueType(Tag::JPEGTables), - )); - } - - Some(vec) - } - Err(e) => return Err(e), - }; - if offsets.len() == 0 { return Err(TiffError::FormatError(TiffFormatError::RequiredTagEmpty( Tag::StripOffsets, @@ -1422,12 +1422,12 @@ impl Decoder { self.goto_offset(*offset)?; let length = bytes[idx]; - if jpeg_tables.is_some() && length < 2 { + if self.jpeg_tables.is_some() && length < 2 { return Err(TiffError::FormatError( TiffFormatError::InvalidTagValueType(Tag::JPEGTables), )); } - let jpeg_reader = JpegReader::new(&mut self.reader, length, &jpeg_tables)?; + let jpeg_reader = JpegReader::new(&mut self.reader, length, &self.jpeg_tables)?; let mut decoder = jpeg::Decoder::new(jpeg_reader); match decoder.decode() { From 2d572c0370d7e007274c35ec020074cbe9b73151 Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Thu, 6 Jan 2022 22:22:21 -0500 Subject: [PATCH 06/20] Remove Decoder::byte_order --- src/decoder/mod.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 75b3cbe..a355327 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -361,7 +361,6 @@ where R: Read + Seek, { reader: SmartReader, - byte_order: ByteOrder, bigtiff: bool, limits: Limits, next_ifd: Option, @@ -575,7 +574,6 @@ impl Decoder { let mut decoder = Decoder { reader, - byte_order, bigtiff, limits: Default::default(), next_ifd, @@ -810,7 +808,7 @@ impl Decoder { /// Returns the byte_order pub fn byte_order(&self) -> ByteOrder { - self.byte_order + self.reader.byte_order } fn read_ifd_offset_nonrepeating(&mut self) -> TiffResult { @@ -1257,6 +1255,7 @@ impl Decoder { ) -> TiffResult<()> { let color_type = self.colortype()?; let byte_len = buffer.byte_len(); + let byte_order = self.reader.byte_order; let tile_attrs = self.tile_attributes.as_mut().unwrap(); let (padding_right, padding_down) = tile_attrs.get_padding(tile); @@ -1304,7 +1303,7 @@ impl Decoder { io::copy(&mut reader.by_ref().take(len), &mut io::sink())?; } - Self::fix_endianness(&mut buffer.subrange(row_start..row_end), self.byte_order); + Self::fix_endianness(&mut buffer.subrange(row_start..row_end), byte_order); if self.photometric_interpretation == PhotometricInterpretation::WhiteIsZero { Self::invert_colors(&mut buffer.subrange(row_start..row_end), color_type); From 23fe194ad9c09b306ee26b4952da536af0bb882f Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Thu, 6 Jan 2022 22:47:37 -0500 Subject: [PATCH 07/20] Remove read_ifd_offset_nonrepeating --- src/decoder/mod.rs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index a355327..4743b9a 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -811,15 +811,6 @@ impl Decoder { self.reader.byte_order } - fn read_ifd_offset_nonrepeating(&mut self) -> TiffResult { - let result = self.read_ifd_offset()?; - if self.seen_ifds.insert(result) { - Ok(result) - } else { - Err(TiffError::FormatError(TiffFormatError::CycleInOffsets)) - } - } - #[inline] pub fn read_ifd_offset(&mut self) -> Result { if self.bigtiff { @@ -978,9 +969,13 @@ impl Decoder { }; dir.insert(tag, entry); } - self.next_ifd = match self.read_ifd_offset_nonrepeating()? { - 0 => None, - n => Some(n), + + self.next_ifd = { + let result = self.read_ifd_offset()?; + if !self.seen_ifds.insert(result) { + return Err(TiffError::FormatError(TiffFormatError::CycleInOffsets)); + } + Some(result) }; Ok(dir) } From 2d10cff97e932b106c15b70d594442d2d17fc1ff Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Thu, 6 Jan 2022 23:18:37 -0500 Subject: [PATCH 08/20] Reading fields shouldn't need the entire Decoder --- src/decoder/ifd.rs | 131 +++++++++++++++++++++++------------------- src/decoder/mod.rs | 4 +- src/decoder/stream.rs | 7 ++- 3 files changed, 80 insertions(+), 62 deletions(-) diff --git a/src/decoder/ifd.rs b/src/decoder/ifd.rs index 23416a4..b326433 100644 --- a/src/decoder/ifd.rs +++ b/src/decoder/ifd.rs @@ -341,14 +341,15 @@ impl Entry { pub fn val( &self, limits: &super::Limits, - decoder: &mut super::Decoder, + bigtiff: bool, + reader: &mut SmartReader, ) -> TiffResult { // Case 1: there are no values so we can return immediately. if self.count == 0 { return Ok(List(Vec::new())); } - let bo = decoder.byte_order(); + let bo = reader.byte_order(); let tag_size = match self.type_ { Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => 1, @@ -373,7 +374,7 @@ impl Entry { // Case 2: there is one value. if self.count == 1 { // 2a: the value is 5-8 bytes and we're in BigTiff mode. - if decoder.bigtiff && value_bytes > 4 && value_bytes <= 8 { + if bigtiff && value_bytes > 4 && value_bytes <= 8 { return Ok(match self.type_ { Type::LONG8 => UnsignedBig(self.r(bo).read_u64()?), Type::SLONG8 => SignedBig(self.r(bo).read_i64()?), @@ -419,36 +420,36 @@ impl Entry { } } Type::LONG8 => { - decoder.goto_offset(self.r(bo).read_u32()?)?; - UnsignedBig(decoder.read_long8()?) + reader.goto_offset(self.r(bo).read_u32()?.into())?; + UnsignedBig(reader.read_u64()?) } Type::SLONG8 => { - decoder.goto_offset(self.r(bo).read_u32()?)?; - SignedBig(decoder.read_slong8()?) + reader.goto_offset(self.r(bo).read_u32()?.into())?; + SignedBig(reader.read_i64()?) } Type::DOUBLE => { - decoder.goto_offset(self.r(bo).read_u32()?)?; - Double(decoder.read_double()?) + reader.goto_offset(self.r(bo).read_u32()?.into())?; + Double(reader.read_f64()?) } Type::RATIONAL => { - decoder.goto_offset(self.r(bo).read_u32()?)?; - Rational(decoder.read_long()?, decoder.read_long()?) + reader.goto_offset(self.r(bo).read_u32()?.into())?; + Rational(reader.read_u32()?, reader.read_u32()?) } Type::SRATIONAL => { - decoder.goto_offset(self.r(bo).read_u32()?)?; - SRational(decoder.read_slong()?, decoder.read_slong()?) + reader.goto_offset(self.r(bo).read_u32()?.into())?; + SRational(reader.read_i32()?, reader.read_i32()?) } Type::IFD => Ifd(self.r(bo).read_u32()?), Type::IFD8 => { - decoder.goto_offset(self.r(bo).read_u32()?)?; - IfdBig(decoder.read_long8()?) + reader.goto_offset(self.r(bo).read_u32()?.into())?; + IfdBig(reader.read_u64()?) } Type::__NonExhaustive => unreachable!(), }); } // Case 3: There is more than one value, but it fits in the offset field. - if value_bytes <= 4 || decoder.bigtiff && value_bytes <= 8 { + 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)?), @@ -535,50 +536,54 @@ impl Entry { match self.type_ { // TODO check if this could give wrong results // at a different endianess of file/computer. - Type::BYTE => self.decode_offset(self.count, bo, limits, decoder, |decoder| { - Ok(UnsignedBig(u64::from(decoder.read_byte()?))) + Type::BYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + let mut buf = [0; 1]; + reader.read_exact(&mut buf)?; + Ok(UnsignedBig(u64::from(buf[0]))) }), - Type::SBYTE => self.decode_offset(self.count, bo, limits, decoder, |decoder| { - Ok(SignedBig(i64::from(decoder.read_byte()? as i8))) + Type::SBYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + Ok(SignedBig(i64::from(reader.read_i8()? as i8))) }), - Type::SHORT => self.decode_offset(self.count, bo, limits, decoder, |decoder| { - Ok(UnsignedBig(u64::from(decoder.read_short()?))) + Type::SHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + Ok(UnsignedBig(u64::from(reader.read_u16()?))) }), - Type::SSHORT => self.decode_offset(self.count, bo, limits, decoder, |decoder| { - Ok(SignedBig(i64::from(decoder.read_sshort()?))) + Type::SSHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + Ok(SignedBig(i64::from(reader.read_i16()?))) }), - Type::LONG => self.decode_offset(self.count, bo, limits, decoder, |decoder| { - Ok(Unsigned(decoder.read_long()?)) + Type::LONG => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + Ok(Unsigned(reader.read_u32()?)) }), - Type::SLONG => self.decode_offset(self.count, bo, limits, decoder, |decoder| { - Ok(Signed(decoder.read_slong()?)) + Type::SLONG => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + Ok(Signed(reader.read_i32()?)) }), - Type::FLOAT => self.decode_offset(self.count, bo, limits, decoder, |decoder| { - Ok(Float(decoder.read_float()?)) + Type::FLOAT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + Ok(Float(reader.read_f32()?)) }), - Type::DOUBLE => self.decode_offset(self.count, bo, limits, decoder, |decoder| { - Ok(Double(decoder.read_double()?)) + Type::DOUBLE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + Ok(Double(reader.read_f64()?)) }), - Type::RATIONAL => self.decode_offset(self.count, bo, limits, decoder, |decoder| { - Ok(Rational(decoder.read_long()?, decoder.read_long()?)) + Type::RATIONAL => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + Ok(Rational(reader.read_u32()?, reader.read_u32()?)) }), - Type::SRATIONAL => self.decode_offset(self.count, bo, limits, decoder, |decoder| { - Ok(SRational(decoder.read_slong()?, decoder.read_slong()?)) + Type::SRATIONAL => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + Ok(SRational(reader.read_i32()?, reader.read_i32()?)) }), - Type::LONG8 => self.decode_offset(self.count, bo, limits, decoder, |decoder| { - Ok(UnsignedBig(decoder.read_long8()?)) + Type::LONG8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + Ok(UnsignedBig(reader.read_u64()?)) }), - Type::SLONG8 => self.decode_offset(self.count, bo, limits, decoder, |decoder| { - Ok(SignedBig(decoder.read_slong8()?)) + Type::SLONG8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + Ok(SignedBig(reader.read_i64()?)) }), - Type::IFD => self.decode_offset(self.count, bo, limits, decoder, |decoder| { - Ok(Ifd(decoder.read_long()?)) + Type::IFD => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + Ok(Ifd(reader.read_u32()?)) }), - Type::IFD8 => self.decode_offset(self.count, bo, limits, decoder, |decoder| { - Ok(IfdBig(decoder.read_long8()?)) + Type::IFD8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + Ok(IfdBig(reader.read_u64()?)) }), - Type::UNDEFINED => self.decode_offset(self.count, bo, limits, decoder, |decoder| { - Ok(Byte(u8::from(decoder.read_byte()?))) + Type::UNDEFINED => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + let mut buf = [0; 1]; + reader.read_exact(&mut buf)?; + Ok(Byte(buf[0])) }), Type::ASCII => { let n = usize::try_from(self.count)?; @@ -586,13 +591,19 @@ impl Entry { return Err(TiffError::LimitsExceeded); } - if decoder.bigtiff { - decoder.goto_offset_u64(self.r(bo).read_u64()?)? + if bigtiff { + reader.goto_offset(self.r(bo).read_u64()?)? } else { - decoder.goto_offset(self.r(bo).read_u32()?)? + reader.goto_offset(self.r(bo).read_u32()?.into())? } - let string = decoder.read_string(n)?; - Ok(Ascii(string)) + + let mut out = vec![0; n]; + reader.read_exact(&mut out)?; + // Strings may be null-terminated, so we trim anything downstream of the null byte + if let Some(first) = out.iter().position(|&b| b == 0) { + out.truncate(first); + } + Ok(Ascii(String::from_utf8(out)?)) } Type::__NonExhaustive => unreachable!(), } @@ -603,13 +614,14 @@ impl Entry { &self, value_count: u64, bo: ByteOrder, + bigtiff: bool, limits: &super::Limits, - decoder: &mut super::Decoder, + reader: &mut SmartReader, decode_fn: F, ) -> TiffResult where R: Read + Seek, - F: Fn(&mut super::Decoder) -> TiffResult, + F: Fn(&mut SmartReader) -> TiffResult, { let value_count = usize::try_from(value_count)?; if value_count > limits.decoding_buffer_size / mem::size_of::() { @@ -617,13 +629,16 @@ impl Entry { } let mut v = Vec::with_capacity(value_count); - if decoder.bigtiff { - decoder.goto_offset_u64(self.r(bo).read_u64()?)? + + let offset = if bigtiff { + self.r(bo).read_u64()? } else { - decoder.goto_offset(self.r(bo).read_u32()?)? - } + self.r(bo).read_u32()?.into() + }; + reader.goto_offset(offset)?; + for _ in 0..value_count { - v.push(decode_fn(decoder)?) + v.push(decode_fn(reader)?) } Ok(List(v)) } diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 4743b9a..874fa7d 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -988,9 +988,7 @@ impl Decoder { Some(entry) => entry.clone(), }; - let limits = self.limits.clone(); - - Ok(Some(entry.val(&limits, self)?)) + Ok(Some(entry.val(&self.limits, self.bigtiff, &mut self.reader)?)) } /// Tries to retrieve a tag and convert it to the desired unsigned type. diff --git a/src/decoder/stream.rs b/src/decoder/stream.rs index e64fef9..a482826 100644 --- a/src/decoder/stream.rs +++ b/src/decoder/stream.rs @@ -340,7 +340,12 @@ where SmartReader { reader, byte_order } } pub fn into_inner(self) -> R { - self.reader + self.reader + } +} +impl SmartReader { + pub fn goto_offset(&mut self, offset: u64) -> io::Result<()> { + self.seek(io::SeekFrom::Start(offset)).map(|_| ()) } } From 63e6ef8bba87749a646ac53499b9bcf17afee02f Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Fri, 7 Jan 2022 14:28:36 -0500 Subject: [PATCH 09/20] Add current_chunk to replace strip_index and current_tile --- src/decoder/mod.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 874fa7d..45f63b8 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -235,7 +235,6 @@ impl<'a> DecodingBuffer<'a> { #[derive(Debug)] struct StripDecodeState { - strip_index: usize, strip_offsets: Vec, strip_bytes: Vec, rows_per_strip: u32, @@ -291,7 +290,6 @@ impl TileAttributes { #[derive(Debug)] /// Stateful variables for tile decoding struct TileDecodeState { - current_tile: usize, tile_offsets: Vec, tile_bytes: Vec, } @@ -378,6 +376,7 @@ where strip_decoder: Option, tile_decoder: Option, tile_attributes: Option, + current_chunk: usize, seen_ifds: HashSet, } @@ -591,6 +590,7 @@ impl Decoder { strip_decoder: None, tile_decoder: None, tile_attributes: None, + current_chunk: 0, seen_ifds, }; decoder.next_image()?; @@ -665,6 +665,7 @@ impl Decoder { self.strip_decoder = None; self.tile_decoder = None; self.tile_attributes = None; + self.current_chunk = 0; self.photometric_interpretation = self .find_tag_unsigned(Tag::PhotometricInterpretation)? @@ -746,7 +747,6 @@ impl Decoder { .unwrap_or(ifd::Value::Unsigned(self.height)) .into_u32()?; self.strip_decoder = Some(StripDecodeState { - strip_index: 0, strip_offsets, strip_bytes, rows_per_strip, @@ -786,7 +786,6 @@ impl Decoder { tile_strip_samples, }); self.tile_decoder = Some(TileDecodeState { - current_tile: 0, tile_offsets: self.get_tag_u64_vec(Tag::TileOffsets)?, tile_bytes: self.get_tag_u64_vec(Tag::TileByteCounts)?, }); @@ -1446,7 +1445,7 @@ impl Decoder { pub fn read_strip_to_buffer(&mut self, mut buffer: DecodingBuffer) -> TiffResult<()> { self.check_chunk_type(ChunkType::Strip)?; - let index = self.strip_decoder.as_ref().unwrap().strip_index; + let index = self.current_chunk; let offset = *self .strip_decoder .as_ref() @@ -1490,7 +1489,7 @@ impl Decoder { } self.expand_strip(buffer.subrange(0..buffer_size), offset, byte_count)?; - self.strip_decoder.as_mut().unwrap().strip_index += 1; + self.current_chunk += 1; if u32::try_from(index)? == self.strip_count()? { self.strip_decoder = None; @@ -1613,7 +1612,7 @@ impl Decoder { /// Read a single strip from the image and return it as a Vector pub fn read_strip(&mut self) -> TiffResult { self.check_chunk_type(ChunkType::Strip)?; - let index = self.strip_decoder.as_ref().unwrap().strip_index; + let index = self.current_chunk; let rows_per_strip = usize::try_from(self.strip_decoder.as_ref().unwrap().rows_per_strip)?; @@ -1632,7 +1631,7 @@ impl Decoder { pub fn read_tile(&mut self) -> TiffResult { self.check_chunk_type(ChunkType::Tile)?; - let tile = self.tile_decoder.as_ref().map_or(0, |d| d.current_tile); + let tile = self.current_chunk; let tile_attrs = self.tile_attributes.as_ref().unwrap(); let (padding_right, padding_down) = tile_attrs.get_padding(tile); @@ -1644,7 +1643,7 @@ impl Decoder { self.read_tile_to_buffer(&mut result.as_buffer(0), tile, tile_width)?; - self.tile_decoder.as_mut().unwrap().current_tile += 1; + self.current_chunk += 1; Ok(result) } From a36cd6bd9014f0c57237f74a110be9e10a8e928c Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Fri, 7 Jan 2022 14:35:17 -0500 Subject: [PATCH 10/20] chunk_{offsets,bytes} to replace {tile,strip}_{offsets,bytes} --- src/decoder/mod.rs | 109 +++++++++++++++++---------------------------- 1 file changed, 40 insertions(+), 69 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 45f63b8..a3615b5 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -235,8 +235,6 @@ impl<'a> DecodingBuffer<'a> { #[derive(Debug)] struct StripDecodeState { - strip_offsets: Vec, - strip_bytes: Vec, rows_per_strip: u32, } @@ -287,13 +285,6 @@ impl TileAttributes { } } -#[derive(Debug)] -/// Stateful variables for tile decoding -struct TileDecodeState { - tile_offsets: Vec, - tile_bytes: Vec, -} - #[derive(Debug, Copy, Clone, PartialEq)] /// Chunk type of the internal representation pub enum ChunkType { @@ -374,8 +365,9 @@ where jpeg_tables: Option>, chunk_type: ChunkType, strip_decoder: Option, - tile_decoder: Option, tile_attributes: Option, + chunk_offsets: Vec, + chunk_bytes: Vec, current_chunk: usize, seen_ifds: HashSet, } @@ -588,8 +580,9 @@ impl Decoder { predictor: Predictor::None, chunk_type: ChunkType::Strip, strip_decoder: None, - tile_decoder: None, tile_attributes: None, + chunk_offsets: Vec::new(), + chunk_bytes: Vec::new(), current_chunk: 0, seen_ifds, }; @@ -663,8 +656,9 @@ impl Decoder { self.width = self.get_tag_u32(Tag::ImageWidth)?; self.height = self.get_tag_u32(Tag::ImageLength)?; self.strip_decoder = None; - self.tile_decoder = None; self.tile_attributes = None; + self.chunk_offsets.clear(); + self.chunk_bytes.clear(); self.current_chunk = 0; self.photometric_interpretation = self @@ -681,15 +675,18 @@ impl Decoder { }; if self.compression_method == CompressionMethod::ModernJPEG { - self.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.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.samples = self.find_tag_unsigned(Tag::SamplesPerPixel)?.unwrap_or(1); @@ -740,17 +737,13 @@ impl Decoder { (true, true, false, false) => { self.chunk_type = ChunkType::Strip; - let strip_offsets = self.get_tag_u64_vec(Tag::StripOffsets)?; - let strip_bytes = self.get_tag_u64_vec(Tag::StripByteCounts)?; + self.chunk_offsets = self.get_tag_u64_vec(Tag::StripOffsets)?; + self.chunk_bytes = self.get_tag_u64_vec(Tag::StripByteCounts)?; let rows_per_strip = self .find_tag(Tag::RowsPerStrip)? .unwrap_or(ifd::Value::Unsigned(self.height)) .into_u32()?; - self.strip_decoder = Some(StripDecodeState { - strip_offsets, - strip_bytes, - rows_per_strip, - }); + self.strip_decoder = Some(StripDecodeState { rows_per_strip }); } (false, false, true, true) => { self.chunk_type = ChunkType::Tile; @@ -785,10 +778,8 @@ impl Decoder { row_samples: (tile_width * samples_per_pixel), tile_strip_samples, }); - self.tile_decoder = Some(TileDecodeState { - tile_offsets: self.get_tag_u64_vec(Tag::TileOffsets)?, - tile_bytes: self.get_tag_u64_vec(Tag::TileByteCounts)?, - }); + self.chunk_offsets = self.get_tag_u64_vec(Tag::TileOffsets)?; + self.chunk_bytes = self.get_tag_u64_vec(Tag::TileByteCounts)?; } (_, _, _, _) => { return Err(TiffError::FormatError( @@ -987,7 +978,11 @@ impl Decoder { Some(entry) => entry.clone(), }; - Ok(Some(entry.val(&self.limits, self.bigtiff, &mut self.reader)?)) + Ok(Some(entry.val( + &self.limits, + self.bigtiff, + &mut self.reader, + )?)) } /// Tries to retrieve a tag and convert it to the desired unsigned type. @@ -1446,24 +1441,12 @@ impl Decoder { pub fn read_strip_to_buffer(&mut self, mut buffer: DecodingBuffer) -> TiffResult<()> { self.check_chunk_type(ChunkType::Strip)?; let index = self.current_chunk; - let offset = *self - .strip_decoder - .as_ref() - .unwrap() - .strip_offsets - .get(index) - .ok_or(TiffError::FormatError( - TiffFormatError::InconsistentSizesEncountered, - ))?; - let byte_count = *self - .strip_decoder - .as_ref() - .unwrap() - .strip_bytes - .get(index) - .ok_or(TiffError::FormatError( - TiffFormatError::InconsistentSizesEncountered, - ))?; + let offset = *self.chunk_offsets.get(index).ok_or(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + ))?; + let byte_count = *self.chunk_bytes.get(index).ok_or(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + ))?; let rows_per_strip = usize::try_from(self.strip_decoder.as_ref().unwrap().rows_per_strip)?; let sized_width = usize::try_from(self.width)?; @@ -1515,25 +1498,13 @@ impl Decoder { tile: usize, output_width: usize, ) -> TiffResult<()> { - let file_offset = *self - .tile_decoder - .as_ref() - .unwrap() - .tile_offsets - .get(tile) - .ok_or(TiffError::FormatError( - TiffFormatError::InconsistentSizesEncountered, - ))?; + let file_offset = *self.chunk_offsets.get(tile).ok_or(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + ))?; - let compressed_bytes = *self - .tile_decoder - .as_ref() - .unwrap() - .tile_bytes - .get(tile) - .ok_or(TiffError::FormatError( - TiffFormatError::InconsistentSizesEncountered, - ))?; + let compressed_bytes = *self.chunk_bytes.get(tile).ok_or(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + ))?; let tile_attrs = self.tile_attributes.as_ref().unwrap(); let tile_width = tile_attrs.tile_width; From 6ba5553d202dc1ef71d44507ce36aaaac39021d1 Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Fri, 7 Jan 2022 15:24:09 -0500 Subject: [PATCH 11/20] Compute tile attributes on demand --- src/decoder/mod.rs | 100 +++++++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 48 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index a3615b5..915b2ea 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -241,42 +241,58 @@ struct StripDecodeState { #[derive(Debug)] /// Computed values useful for tile decoding struct TileAttributes { + image_width: usize, + image_height: usize, + samples_per_pixel: usize, + tile_width: usize, tile_length: usize, - tiles_down: usize, - tiles_across: usize, - /// Length of padding for rightmost tiles in pixels - padding_right: usize, - /// length of padding for bottommost tile in pixels - padding_down: usize, - tile_samples: usize, - /// Sample count of one row of one tile - row_samples: usize, - /// Sample count of one row of tiles - tile_strip_samples: usize, } impl TileAttributes { + fn tiles_across(&self) -> usize { + (self.image_width + self.tile_width - 1) / self.tile_width + } + fn tiles_down(&self) -> usize { + (self.image_height + self.tile_length - 1) / self.tile_length + } + fn padding_right(&self) -> usize { + self.tile_width - self.image_width % self.tile_width + } + fn padding_down(&self) -> usize { + self.tile_length - self.image_height % self.tile_length + } + fn row_samples(&self) -> usize { + self.tile_width * self.samples_per_pixel + } + fn tile_samples(&self) -> usize { + self.tile_length * self.tile_width * self.samples_per_pixel + } + fn tile_strip_samples(&self) -> usize { + (self.tile_samples() * self.tiles_across()) + - (self.padding_right() * self.tile_length * self.samples_per_pixel) + } + /// Returns the tile offset in the result buffer, counted in samples fn get_offset(&self, tile: usize) -> usize { - let row = tile / self.tiles_across; - let column = tile % self.tiles_across; + let row = tile / self.tiles_across(); + let column = tile % self.tiles_across(); - (row * self.tile_strip_samples) + (column * self.row_samples) + (row * self.tile_strip_samples()) + (column * self.row_samples()) } fn get_padding(&self, tile: usize) -> (usize, usize) { - let row = tile / self.tiles_across; - let column = tile % self.tiles_across; + let row = tile / self.tiles_across(); + let column = tile % self.tiles_across(); - let padding_right = if column == self.tiles_across - 1 { - self.padding_right + let padding_right = if column == self.tiles_across() - 1 { + self.padding_right() } else { 0 }; - let padding_down = if row == self.tiles_down - 1 { - self.padding_down + let padding_down = if row == self.tiles_down() - 1 { + self.padding_down() } else { 0 }; @@ -757,29 +773,24 @@ impl Decoder { return Err(TiffFormatError::InvalidTagValueType(Tag::TileLength).into()); } - let tiles_across = (usize::try_from(self.width)? + tile_width - 1) / tile_width; - let tiles_down = (usize::try_from(self.height)? + tile_length - 1) / tile_length; - - let samples_per_pixel = self.bits_per_sample.len(); - - let tile_samples = tile_length * tile_width * samples_per_pixel; - let padding_right = (tiles_across * tile_width) - usize::try_from(self.width)?; - let tile_strip_samples = (tile_samples * tiles_across) - - (padding_right * tile_length * samples_per_pixel); - self.tile_attributes = Some(TileAttributes { + image_width: usize::try_from(self.width)?, + image_height: usize::try_from(self.height)?, + samples_per_pixel: self.bits_per_sample.len(), tile_width, tile_length, - tiles_across, - tiles_down, - tile_samples, - padding_right, - padding_down: (tiles_down * tile_length) - usize::try_from(self.height)?, - row_samples: (tile_width * samples_per_pixel), - tile_strip_samples, }); self.chunk_offsets = self.get_tag_u64_vec(Tag::TileOffsets)?; self.chunk_bytes = self.get_tag_u64_vec(Tag::TileByteCounts)?; + + let tile = self.tile_attributes.as_ref().unwrap(); + if self.chunk_offsets.len() != self.chunk_bytes.len() + || self.chunk_offsets.len() != tile.tiles_down() * tile.tiles_across() + { + return Err(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + )); + } } (_, _, _, _) => { return Err(TiffError::FormatError( @@ -1246,9 +1257,9 @@ impl Decoder { let tile_attrs = self.tile_attributes.as_mut().unwrap(); let (padding_right, padding_down) = tile_attrs.get_padding(tile); - let tile_samples = tile_attrs.tile_samples; + let tile_samples = tile_attrs.tile_samples(); let tile_length = tile_attrs.tile_length; - let row_samples = tile_attrs.row_samples; + let row_samples = tile_attrs.row_samples(); let padding_right_samples = padding_right * self.bits_per_sample.len(); self.goto_offset_u64(offset)?; @@ -1371,10 +1382,7 @@ impl Decoder { /// Number of tiles in image pub fn tile_count(&mut self) -> TiffResult { self.check_chunk_type(ChunkType::Tile)?; - let tile_attrs = self.tile_attributes.as_ref().unwrap(); - Ok(u32::try_from( - tile_attrs.tiles_across * tile_attrs.tiles_down, - )?) + Ok(u32::try_from(self.chunk_offsets.len())?) } pub fn read_jpeg(&mut self) -> TiffResult { @@ -1623,11 +1631,7 @@ impl Decoder { let width = usize::try_from(self.width)?; let mut result = self.result_buffer(width, usize::try_from(self.height)?)?; - let tile_attrs = self.tile_attributes.as_ref().unwrap(); - let tiles_across = tile_attrs.tiles_across; - let tiles_down = tile_attrs.tiles_down; - - for tile in 0..(tiles_across * tiles_down) { + for tile in 0..self.chunk_offsets.len() { let buffer_offset = self.tile_attributes.as_ref().unwrap().get_offset(tile); self.read_tile_to_buffer(&mut result.as_buffer(buffer_offset), tile, width)?; } From b385a6e75264eab3cf808ed590b2bab0b160d0b1 Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Fri, 7 Jan 2022 16:03:07 -0500 Subject: [PATCH 12/20] Move some decoder fields into a new Image struct --- src/decoder/image.rs | 96 +++++++++++ src/decoder/mod.rs | 369 ++++++++++++++++++++----------------------- 2 files changed, 265 insertions(+), 200 deletions(-) create mode 100644 src/decoder/image.rs diff --git a/src/decoder/image.rs b/src/decoder/image.rs new file mode 100644 index 0000000..3c20736 --- /dev/null +++ b/src/decoder/image.rs @@ -0,0 +1,96 @@ +use super::ChunkType; +use super::ifd::Directory; +use crate::tags::{ + CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, +}; + +#[derive(Debug)] +pub(crate) struct StripDecodeState { + pub rows_per_strip: u32, +} + +#[derive(Debug)] +/// Computed values useful for tile decoding +pub(crate) struct TileAttributes { + pub image_width: usize, + pub image_height: usize, + pub samples_per_pixel: usize, + + pub tile_width: usize, + pub tile_length: usize, +} + +impl TileAttributes { + pub fn tiles_across(&self) -> usize { + (self.image_width + self.tile_width - 1) / self.tile_width + } + pub fn tiles_down(&self) -> usize { + (self.image_height + self.tile_length - 1) / self.tile_length + } + fn padding_right(&self) -> usize { + self.tile_width - self.image_width % self.tile_width + } + fn padding_down(&self) -> usize { + self.tile_length - self.image_height % self.tile_length + } + pub fn row_samples(&self) -> usize { + self.tile_width * self.samples_per_pixel + } + pub fn tile_samples(&self) -> usize { + self.tile_length * self.tile_width * self.samples_per_pixel + } + fn tile_strip_samples(&self) -> usize { + (self.tile_samples() * self.tiles_across()) + - (self.padding_right() * self.tile_length * self.samples_per_pixel) + } + + /// Returns the tile offset in the result buffer, counted in samples + pub fn get_offset(&self, tile: usize) -> usize { + let row = tile / self.tiles_across(); + let column = tile % self.tiles_across(); + + (row * self.tile_strip_samples()) + (column * self.row_samples()) + } + + pub fn get_padding(&self, tile: usize) -> (usize, usize) { + let row = tile / self.tiles_across(); + let column = tile % self.tiles_across(); + + let padding_right = if column == self.tiles_across() - 1 { + self.padding_right() + } else { + 0 + }; + + let padding_down = if row == self.tiles_down() - 1 { + self.padding_down() + } else { + 0 + }; + + (padding_right, padding_down) + } +} + +#[derive(Debug)] +pub(crate) struct Image { + pub ifd: Option, + pub width: u32, + pub height: u32, + pub bits_per_sample: Vec, + pub samples: u8, + pub sample_format: Vec, + pub photometric_interpretation: PhotometricInterpretation, + pub compression_method: CompressionMethod, + pub predictor: Predictor, + pub jpeg_tables: Option>, + pub chunk_type: ChunkType, + pub strip_decoder: Option, + pub tile_attributes: Option, + pub chunk_offsets: Vec, + pub chunk_bytes: Vec, +} + +impl Image { + +} \ No newline at end of file diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 915b2ea..a5adf0b 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -8,6 +8,7 @@ use crate::{ }; use self::ifd::Directory; +use self::image::{Image, StripDecodeState, TileAttributes}; use crate::tags::{ CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag, Type, }; @@ -17,6 +18,7 @@ use self::stream::{ }; pub mod ifd; +mod image; mod stream; /// Result of a decoding process @@ -233,74 +235,6 @@ impl<'a> DecodingBuffer<'a> { } } -#[derive(Debug)] -struct StripDecodeState { - rows_per_strip: u32, -} - -#[derive(Debug)] -/// Computed values useful for tile decoding -struct TileAttributes { - image_width: usize, - image_height: usize, - samples_per_pixel: usize, - - tile_width: usize, - tile_length: usize, -} - -impl TileAttributes { - fn tiles_across(&self) -> usize { - (self.image_width + self.tile_width - 1) / self.tile_width - } - fn tiles_down(&self) -> usize { - (self.image_height + self.tile_length - 1) / self.tile_length - } - fn padding_right(&self) -> usize { - self.tile_width - self.image_width % self.tile_width - } - fn padding_down(&self) -> usize { - self.tile_length - self.image_height % self.tile_length - } - fn row_samples(&self) -> usize { - self.tile_width * self.samples_per_pixel - } - fn tile_samples(&self) -> usize { - self.tile_length * self.tile_width * self.samples_per_pixel - } - fn tile_strip_samples(&self) -> usize { - (self.tile_samples() * self.tiles_across()) - - (self.padding_right() * self.tile_length * self.samples_per_pixel) - } - - /// Returns the tile offset in the result buffer, counted in samples - fn get_offset(&self, tile: usize) -> usize { - let row = tile / self.tiles_across(); - let column = tile % self.tiles_across(); - - (row * self.tile_strip_samples()) + (column * self.row_samples()) - } - - fn get_padding(&self, tile: usize) -> (usize, usize) { - let row = tile / self.tiles_across(); - let column = tile % self.tiles_across(); - - let padding_right = if column == self.tiles_across() - 1 { - self.padding_right() - } else { - 0 - }; - - let padding_down = if row == self.tiles_down() - 1 { - self.padding_down() - } else { - 0 - }; - - (padding_right, padding_down) - } -} - #[derive(Debug, Copy, Clone, PartialEq)] /// Chunk type of the internal representation pub enum ChunkType { @@ -369,23 +303,9 @@ where bigtiff: bool, limits: Limits, next_ifd: Option, - ifd: Option, - width: u32, - height: u32, - bits_per_sample: Vec, - samples: u8, - sample_format: Vec, - photometric_interpretation: PhotometricInterpretation, - compression_method: CompressionMethod, - predictor: Predictor, - jpeg_tables: Option>, - chunk_type: ChunkType, - strip_decoder: Option, - tile_attributes: Option, - chunk_offsets: Vec, - chunk_bytes: Vec, current_chunk: usize, seen_ifds: HashSet, + image: Image, } trait Wrapping { @@ -584,21 +504,23 @@ impl Decoder { bigtiff, limits: Default::default(), next_ifd, - ifd: None, - width: 0, - height: 0, - bits_per_sample: vec![1], - samples: 1, - sample_format: vec![SampleFormat::Uint], - photometric_interpretation: PhotometricInterpretation::BlackIsZero, - compression_method: CompressionMethod::None, - jpeg_tables: None, - predictor: Predictor::None, - chunk_type: ChunkType::Strip, - strip_decoder: None, - tile_attributes: None, - chunk_offsets: Vec::new(), - chunk_bytes: Vec::new(), + image: Image { + ifd: None, + width: 0, + height: 0, + bits_per_sample: vec![1], + samples: 1, + sample_format: vec![SampleFormat::Uint], + photometric_interpretation: PhotometricInterpretation::BlackIsZero, + compression_method: CompressionMethod::None, + jpeg_tables: None, + predictor: Predictor::None, + chunk_type: ChunkType::Strip, + strip_decoder: None, + tile_attributes: None, + chunk_offsets: Vec::new(), + chunk_bytes: Vec::new(), + }, current_chunk: 0, seen_ifds, }; @@ -612,12 +534,12 @@ impl Decoder { } pub fn dimensions(&mut self) -> TiffResult<(u32, u32)> { - Ok((self.width, self.height)) + Ok((self.image().width, self.image().height)) } pub fn colortype(&mut self) -> TiffResult { - match self.photometric_interpretation { - PhotometricInterpretation::RGB => match self.bits_per_sample[..] { + match self.image().photometric_interpretation { + PhotometricInterpretation::RGB => match self.image().bits_per_sample[..] { [r, g, b] if [r, r] == [g, b] => Ok(ColorType::RGB(r)), [r, g, b, a] if [r, r, r] == [g, b, a] => Ok(ColorType::RGBA(r)), // FIXME: We should _ignore_ other components. In particular: @@ -627,36 +549,40 @@ impl Decoder { // > -- TIFF 6.0 Specification, Section 7, Additional Baseline requirements. _ => Err(TiffError::UnsupportedError( TiffUnsupportedError::InterpretationWithBits( - self.photometric_interpretation, - self.bits_per_sample.clone(), + self.image().photometric_interpretation, + self.image().bits_per_sample.clone(), ), )), }, - PhotometricInterpretation::CMYK => match self.bits_per_sample[..] { + PhotometricInterpretation::CMYK => match self.image().bits_per_sample[..] { [c, m, y, k] if [c, c, c] == [m, y, k] => Ok(ColorType::CMYK(c)), _ => Err(TiffError::UnsupportedError( TiffUnsupportedError::InterpretationWithBits( - self.photometric_interpretation, - self.bits_per_sample.clone(), + self.image().photometric_interpretation, + self.image().bits_per_sample.clone(), ), )), }, PhotometricInterpretation::BlackIsZero | PhotometricInterpretation::WhiteIsZero - if self.bits_per_sample.len() == 1 => + if self.image().bits_per_sample.len() == 1 => { - Ok(ColorType::Gray(self.bits_per_sample[0])) + Ok(ColorType::Gray(self.image().bits_per_sample[0])) } // TODO: this is bad we should not fail at this point _ => Err(TiffError::UnsupportedError( TiffUnsupportedError::InterpretationWithBits( - self.photometric_interpretation, - self.bits_per_sample.clone(), + self.image().photometric_interpretation, + self.image().bits_per_sample.clone(), ), )), } } + fn image(&self) -> &Image { + &self.image + } + /// Reset the decoder. #[deprecated = "Never should have been public. Only use Decoder::new()"] pub fn init(self) -> TiffResult> { @@ -668,30 +594,32 @@ 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<()> { - self.ifd = Some(self.read_ifd()?); - self.width = self.get_tag_u32(Tag::ImageWidth)?; - self.height = self.get_tag_u32(Tag::ImageLength)?; - self.strip_decoder = None; - self.tile_attributes = None; - self.chunk_offsets.clear(); - self.chunk_bytes.clear(); self.current_chunk = 0; - self.photometric_interpretation = self + 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.compression_method = match self.find_tag_unsigned(Tag::Compression)? { + 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.compression_method == CompressionMethod::ModernJPEG { - self.jpeg_tables = self + 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)?; @@ -705,9 +633,9 @@ impl Decoder { .transpose()?; } - self.samples = self.find_tag_unsigned(Tag::SamplesPerPixel)?.unwrap_or(1); + self.image.samples = self.find_tag_unsigned(Tag::SamplesPerPixel)?.unwrap_or(1); - self.sample_format = match self.find_tag_unsigned_vec(Tag::SampleFormat)? { + self.image.sample_format = match self.find_tag_unsigned_vec(Tag::SampleFormat)? { Some(vals) => { let sample_format: Vec<_> = vals .into_iter() @@ -717,7 +645,7 @@ impl Decoder { // 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.sample_format.clone(), + self.image.sample_format.clone(), ) .into()); } @@ -727,14 +655,14 @@ impl Decoder { None => vec![SampleFormat::Uint], }; - self.bits_per_sample = match self.samples { + 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.samples).into()), + _ => return Err(TiffUnsupportedError::UnsupportedSampleDepth(self.image.samples).into()), }; - self.predictor = self + self.image.predictor = self .find_tag_unsigned(Tag::Predictor)? .map(|p| { Predictor::from_u16(p) @@ -743,7 +671,7 @@ impl Decoder { .transpose()? .unwrap_or(Predictor::None); - let ifd = self.ifd.as_ref().unwrap(); + let ifd = self.image.ifd.as_ref().unwrap(); match ( ifd.contains_key(&Tag::StripByteCounts), ifd.contains_key(&Tag::StripOffsets), @@ -751,18 +679,18 @@ impl Decoder { ifd.contains_key(&Tag::TileOffsets), ) { (true, true, false, false) => { - self.chunk_type = ChunkType::Strip; + self.image.chunk_type = ChunkType::Strip; - self.chunk_offsets = self.get_tag_u64_vec(Tag::StripOffsets)?; - self.chunk_bytes = self.get_tag_u64_vec(Tag::StripByteCounts)?; + 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.height)) + .unwrap_or(ifd::Value::Unsigned(self.image.height)) .into_u32()?; - self.strip_decoder = Some(StripDecodeState { rows_per_strip }); + self.image.strip_decoder = Some(StripDecodeState { rows_per_strip }); } (false, false, true, true) => { - self.chunk_type = ChunkType::Tile; + 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)?)?; @@ -773,19 +701,19 @@ impl Decoder { return Err(TiffFormatError::InvalidTagValueType(Tag::TileLength).into()); } - self.tile_attributes = Some(TileAttributes { - image_width: usize::try_from(self.width)?, - image_height: usize::try_from(self.height)?, - samples_per_pixel: self.bits_per_sample.len(), + 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.chunk_offsets = self.get_tag_u64_vec(Tag::TileOffsets)?; - self.chunk_bytes = self.get_tag_u64_vec(Tag::TileByteCounts)?; + 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.tile_attributes.as_ref().unwrap(); - if self.chunk_offsets.len() != self.chunk_bytes.len() - || self.chunk_offsets.len() != tile.tiles_down() * tile.tiles_across() + 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, @@ -984,7 +912,7 @@ impl Decoder { /// Tries to retrieve a tag. /// Return `Ok(None)` if the tag is not present. pub fn find_tag(&mut self, tag: Tag) -> TiffResult> { - let entry = match self.ifd.as_ref().unwrap().get(&tag) { + let entry = match self.image().ifd.as_ref().unwrap().get(&tag) { None => return Ok(None), Some(entry) => entry.clone(), }; @@ -1199,10 +1127,11 @@ impl Decoder { // Construct necessary reader to perform decompression. self.goto_offset_u64(offset)?; let byte_order = self.reader.byte_order; + let compression_method = self.image().compression_method; let reader = Self::create_reader( &mut self.reader, - self.compression_method, + compression_method, length, buffer.len(), buffer.byte_len(), @@ -1235,7 +1164,7 @@ impl Decoder { Self::fix_endianness(&mut buffer, byte_order); - if self.photometric_interpretation == PhotometricInterpretation::WhiteIsZero { + if self.image().photometric_interpretation == PhotometricInterpretation::WhiteIsZero { Self::invert_colors(&mut buffer, color_type); } @@ -1255,20 +1184,22 @@ impl Decoder { let byte_len = buffer.byte_len(); let byte_order = self.reader.byte_order; - let tile_attrs = self.tile_attributes.as_mut().unwrap(); + let tile_attrs = self.image().tile_attributes.as_ref().unwrap(); let (padding_right, padding_down) = tile_attrs.get_padding(tile); let tile_samples = tile_attrs.tile_samples(); let tile_length = tile_attrs.tile_length; let row_samples = tile_attrs.row_samples(); - let padding_right_samples = padding_right * self.bits_per_sample.len(); + let padding_right_samples = padding_right * self.image().bits_per_sample.len(); self.goto_offset_u64(offset)?; - let line_samples = output_width * self.bits_per_sample.len(); + let line_samples = output_width * self.image().bits_per_sample.len(); + let compression_method = self.image().compression_method; + let photometric_interpretation = self.image().photometric_interpretation; let mut reader = Self::create_reader( &mut self.reader, - self.compression_method, + compression_method, compressed_length, tile_samples, byte_len, @@ -1303,7 +1234,7 @@ impl Decoder { Self::fix_endianness(&mut buffer.subrange(row_start..row_end), byte_order); - if self.photometric_interpretation == PhotometricInterpretation::WhiteIsZero { + if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { Self::invert_colors(&mut buffer.subrange(row_start..row_end), color_type); } } @@ -1346,10 +1277,10 @@ impl Decoder { } fn check_chunk_type(&self, expected: ChunkType) -> TiffResult<()> { - if expected != self.chunk_type { + if expected != self.image().chunk_type { return Err(TiffError::UsageError(UsageError::InvalidChunkType( expected, - self.chunk_type, + self.image().chunk_type, ))); } @@ -1358,20 +1289,20 @@ impl Decoder { /// The chunk type (Strips / Tiles) of the image pub fn get_chunk_type(&self) -> ChunkType { - self.chunk_type + self.image().chunk_type } /// Number of strips in image pub fn strip_count(&mut self) -> TiffResult { self.check_chunk_type(ChunkType::Strip)?; - let rows_per_strip = self.strip_decoder.as_ref().unwrap().rows_per_strip; + let rows_per_strip = self.image().strip_decoder.as_ref().unwrap().rows_per_strip; if rows_per_strip == 0 { return Ok(0); } // rows_per_strip - 1 can never fail since we know it's at least 1 - let height = match self.height.checked_add(rows_per_strip - 1) { + let height = match self.image().height.checked_add(rows_per_strip - 1) { Some(h) => h, None => return Err(TiffError::IntSizeError), }; @@ -1382,7 +1313,7 @@ impl Decoder { /// Number of tiles in image pub fn tile_count(&mut self) -> TiffResult { self.check_chunk_type(ChunkType::Tile)?; - Ok(u32::try_from(self.chunk_offsets.len())?) + Ok(u32::try_from(self.image().chunk_offsets.len())?) } pub fn read_jpeg(&mut self) -> TiffResult { @@ -1416,12 +1347,15 @@ impl Decoder { self.goto_offset(*offset)?; let length = bytes[idx]; - if self.jpeg_tables.is_some() && length < 2 { + if self.image().jpeg_tables.is_some() && length < 2 { return Err(TiffError::FormatError( TiffFormatError::InvalidTagValueType(Tag::JPEGTables), )); } - let jpeg_reader = JpegReader::new(&mut self.reader, length, &self.jpeg_tables)?; + // TODO: avoid this clone + let jpeg_tables = self.image().jpeg_tables.clone(); + + let jpeg_reader = JpegReader::new(&mut self.reader, length, &jpeg_tables)?; let mut decoder = jpeg::Decoder::new(jpeg_reader); match decoder.decode() { @@ -1449,16 +1383,25 @@ impl Decoder { pub fn read_strip_to_buffer(&mut self, mut buffer: DecodingBuffer) -> TiffResult<()> { self.check_chunk_type(ChunkType::Strip)?; let index = self.current_chunk; - let offset = *self.chunk_offsets.get(index).ok_or(TiffError::FormatError( - TiffFormatError::InconsistentSizesEncountered, - ))?; - let byte_count = *self.chunk_bytes.get(index).ok_or(TiffError::FormatError( - TiffFormatError::InconsistentSizesEncountered, - ))?; - let rows_per_strip = usize::try_from(self.strip_decoder.as_ref().unwrap().rows_per_strip)?; + let offset = *self + .image() + .chunk_offsets + .get(index) + .ok_or(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + ))?; + let byte_count = *self + .image() + .chunk_bytes + .get(index) + .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.width)?; - let sized_height = usize::try_from(self.height)?; + let sized_width = usize::try_from(self.image().width)?; + let sized_height = usize::try_from(self.image().height)?; let strip_height_without_padding = index .checked_mul(rows_per_strip) @@ -1470,7 +1413,7 @@ impl Decoder { let buffer_size = sized_width .checked_mul(strip_height) - .and_then(|x| x.checked_mul(self.bits_per_sample.len())) + .and_then(|x| x.checked_mul(self.image().bits_per_sample.len())) .ok_or(TiffError::LimitsExceeded)?; if buffer.len() < buffer_size { @@ -1482,16 +1425,13 @@ impl Decoder { self.expand_strip(buffer.subrange(0..buffer_size), offset, byte_count)?; self.current_chunk += 1; - if u32::try_from(index)? == self.strip_count()? { - self.strip_decoder = None; - } - match self.predictor { + match self.image().predictor { Predictor::None => {} Predictor::Horizontal => { rev_hpredict( buffer.copy(), - (self.width, u32::try_from(strip_height)?), - usize::try_from(self.width)?, + (self.image().width, u32::try_from(strip_height)?), + usize::try_from(self.image().width)?, self.colortype()?, )?; } @@ -1506,15 +1446,24 @@ impl Decoder { tile: usize, output_width: usize, ) -> TiffResult<()> { - let file_offset = *self.chunk_offsets.get(tile).ok_or(TiffError::FormatError( - TiffFormatError::InconsistentSizesEncountered, - ))?; + let file_offset = *self + .image() + .chunk_offsets + .get(tile) + .ok_or(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + ))?; - let compressed_bytes = *self.chunk_bytes.get(tile).ok_or(TiffError::FormatError( - TiffFormatError::InconsistentSizesEncountered, - ))?; + let compressed_bytes = + *self + .image() + .chunk_bytes + .get(tile) + .ok_or(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + ))?; - let tile_attrs = self.tile_attributes.as_ref().unwrap(); + let tile_attrs = self.image().tile_attributes.as_ref().unwrap(); let tile_width = tile_attrs.tile_width; let tile_length = tile_attrs.tile_length; @@ -1528,7 +1477,7 @@ impl Decoder { output_width, )?; - match self.predictor { + match self.image().predictor { Predictor::None => {} Predictor::Horizontal => { rev_hpredict( @@ -1549,14 +1498,25 @@ impl Decoder { fn result_buffer(&self, width: usize, height: usize) -> TiffResult { let buffer_size = match width .checked_mul(height) - .and_then(|x| x.checked_mul(self.bits_per_sample.len())) + .and_then(|x| x.checked_mul(self.image().bits_per_sample.len())) { Some(s) => s, None => return Err(TiffError::LimitsExceeded), }; - let max_sample_bits = self.bits_per_sample.iter().cloned().max().unwrap_or(8); - match self.sample_format.first().unwrap_or(&SampleFormat::Uint) { + let max_sample_bits = self + .image() + .bits_per_sample + .iter() + .cloned() + .max() + .unwrap_or(8); + match self + .image() + .sample_format + .first() + .unwrap_or(&SampleFormat::Uint) + { SampleFormat::Uint => match max_sample_bits { n if n <= 8 => DecodingResult::new_u8(buffer_size, &self.limits), n if n <= 16 => DecodingResult::new_u16(buffer_size, &self.limits), @@ -1593,14 +1553,15 @@ impl Decoder { self.check_chunk_type(ChunkType::Strip)?; let index = self.current_chunk; - let rows_per_strip = usize::try_from(self.strip_decoder.as_ref().unwrap().rows_per_strip)?; + 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.height)? - index * rows_per_strip, + usize::try_from(self.image().height)? - index * rows_per_strip, ); - let mut result = self.result_buffer(usize::try_from(self.width)?, strip_height)?; + let mut result = self.result_buffer(usize::try_from(self.image().width)?, strip_height)?; self.read_strip_to_buffer(result.as_buffer(0))?; Ok(result) @@ -1612,7 +1573,7 @@ impl Decoder { let tile = self.current_chunk; - let tile_attrs = self.tile_attributes.as_ref().unwrap(); + 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; @@ -1628,11 +1589,16 @@ impl Decoder { } fn read_tiled_image(&mut self) -> TiffResult { - let width = usize::try_from(self.width)?; - let mut result = self.result_buffer(width, usize::try_from(self.height)?)?; + let width = usize::try_from(self.image().width)?; + let mut result = self.result_buffer(width, usize::try_from(self.image().height)?)?; - for tile in 0..self.chunk_offsets.len() { - let buffer_offset = self.tile_attributes.as_ref().unwrap().get_offset(tile); + for tile in 0..self.image().chunk_offsets.len() { + let buffer_offset = self + .image() + .tile_attributes + .as_ref() + .unwrap() + .get_offset(tile); self.read_tile_to_buffer(&mut result.as_buffer(buffer_offset), tile, width)?; } @@ -1640,18 +1606,21 @@ impl Decoder { } fn read_stripped_image(&mut self) -> TiffResult { - let rows_per_strip = usize::try_from(self.strip_decoder.as_ref().unwrap().rows_per_strip)?; + let rows_per_strip = + usize::try_from(self.image().strip_decoder.as_ref().unwrap().rows_per_strip)?; - let samples_per_strip = match usize::try_from(self.width)? + let samples_per_strip = match usize::try_from(self.image().width)? .checked_mul(rows_per_strip) - .and_then(|x| x.checked_mul(self.bits_per_sample.len())) + .and_then(|x| x.checked_mul(self.image().bits_per_sample.len())) { Some(s) => s, None => return Err(TiffError::LimitsExceeded), }; - let mut result = - self.result_buffer(usize::try_from(self.width)?, usize::try_from(self.height)?)?; + let mut result = self.result_buffer( + usize::try_from(self.image().width)?, + usize::try_from(self.image().height)?, + )?; for i in 0..usize::try_from(self.strip_count()?)? { let r = result.as_buffer(samples_per_strip * i); @@ -1662,7 +1631,7 @@ impl Decoder { /// Decodes the entire image and return it as a Vector pub fn read_image(&mut self) -> TiffResult { - let result = match (self.chunk_type, self.compression_method) { + let result = match (self.image().chunk_type, self.image().compression_method) { (_, CompressionMethod::ModernJPEG) => self.read_jpeg()?, (ChunkType::Strip, _) => self.read_stripped_image()?, (ChunkType::Tile, _) => self.read_tiled_image()?, From 47080293db91ab1100ee0b31daed8c9a4aee78f3 Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Fri, 7 Jan 2022 16:22:17 -0500 Subject: [PATCH 13/20] 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() + } +} From 720e6586b9d4f06ab16b80dacdc183a0356d1abb Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Fri, 7 Jan 2022 20:45:48 -0500 Subject: [PATCH 14/20] Make read_ifd and read_entry stateless --- src/decoder/mod.rs | 98 +++++++++++++++++++++++++++++----------------- 1 file changed, 62 insertions(+), 36 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 994cc37..444d4a4 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -595,15 +595,27 @@ 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()?; + if self.next_ifd.is_none() { + return Err(TiffError::FormatError( + TiffFormatError::ImageFileDirectoryNotFound, + )); + } + + let (ifd, next_ifd) = Self::read_ifd( + &mut self.reader, + self.bigtiff, + self.next_ifd.take().unwrap(), + )?; + + if let Some(next) = next_ifd { + if !self.seen_ifds.insert(next) { + return Err(TiffError::FormatError(TiffFormatError::CycleInOffsets)); + } + self.next_ifd = Some(next); + } self.current_chunk = 0; - self.image = Image::from_reader( - &mut self.reader, - ifd, - &self.limits, - self.bigtiff - )?; + self.image = Image::from_reader(&mut self.reader, ifd, &self.limits, self.bigtiff)?; Ok(()) } @@ -731,43 +743,53 @@ impl Decoder { // Type 2 bytes // Count 4 bytes // Value 4 bytes either a pointer the value itself - fn read_entry(&mut self) -> TiffResult> { - let tag = Tag::from_u16_exhaustive(self.read_short()?); - let type_ = match Type::from_u16(self.read_short()?) { + fn read_entry( + reader: &mut SmartReader, + bigtiff: bool, + ) -> TiffResult> { + let tag = Tag::from_u16_exhaustive(reader.read_u16()?); + let type_ = match Type::from_u16(reader.read_u16()?) { Some(t) => t, None => { // Unknown type. Skip this entry according to spec. - self.read_long()?; - self.read_long()?; + reader.read_u32()?; + reader.read_u32()?; return Ok(None); } }; - let entry = if self.bigtiff { - ifd::Entry::new_u64(type_, self.read_long8()?, self.read_offset_u64()?) + let entry = if bigtiff { + let mut offset = [0; 8]; + + let count = reader.read_u64()?; + reader.read_exact(&mut offset)?; + ifd::Entry::new_u64(type_, count, offset) } else { - ifd::Entry::new(type_, self.read_long()?, self.read_offset()?) + let mut offset = [0; 4]; + + let count = reader.read_u32()?; + reader.read_exact(&mut offset)?; + ifd::Entry::new(type_, count, offset) }; Ok(Some((tag, entry))) } - /// Reads the next IFD - fn read_ifd(&mut self) -> TiffResult { + /// Reads the IFD starting at the indicated location. + fn read_ifd( + reader: &mut SmartReader, + bigtiff: bool, + ifd_location: u64, + ) -> TiffResult<(Directory, Option)> { + reader.goto_offset(ifd_location)?; + let mut dir: Directory = HashMap::new(); - match self.next_ifd { - None => { - return Err(TiffError::FormatError( - TiffFormatError::ImageFileDirectoryNotFound, - )) - } - Some(offset) => self.goto_offset_u64(offset)?, - } - let num_tags = if self.bigtiff { - self.read_long8()? + + let num_tags = if bigtiff { + reader.read_u64()? } else { - self.read_short()?.into() + reader.read_u16()?.into() }; for _ in 0..num_tags { - let (tag, entry) = match self.read_entry()? { + let (tag, entry) = match Self::read_entry(reader, bigtiff)? { Some(val) => val, None => { continue; @@ -776,14 +798,18 @@ impl Decoder { dir.insert(tag, entry); } - self.next_ifd = { - let result = self.read_ifd_offset()?; - if !self.seen_ifds.insert(result) { - return Err(TiffError::FormatError(TiffFormatError::CycleInOffsets)); - } - Some(result) + let next_ifd = if bigtiff { + reader.read_u64()? + } else { + reader.read_u32()?.into() }; - Ok(dir) + + let next_ifd = match next_ifd { + 0 => None, + 1.. => Some(next_ifd), + }; + + Ok((dir, next_ifd)) } /// Tries to retrieve a tag. From 7a7d4f0305d49b3b71775ebb510b287c10630155 Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Fri, 7 Jan 2022 20:47:18 -0500 Subject: [PATCH 15/20] Avoid unused warning --- src/decoder/image.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/decoder/image.rs b/src/decoder/image.rs index fc01617..67334f6 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -81,6 +81,7 @@ pub(crate) struct Image { pub width: u32, pub height: u32, pub bits_per_sample: Vec, + #[allow(unused)] pub samples: u8, pub sample_format: Vec, pub photometric_interpretation: PhotometricInterpretation, From bc2055243a3c7cc4fae1cc3764bd06477c83e428 Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Fri, 7 Jan 2022 21:00:25 -0500 Subject: [PATCH 16/20] Add consistency check between StripOffsets and StripByteCounts --- src/decoder/image.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/decoder/image.rs b/src/decoder/image.rs index 67334f6..a4a9189 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -215,6 +215,12 @@ impl Image { .unwrap_or(height); strip_decoder = Some(StripDecodeState { rows_per_strip }); tile_attributes = None; + + if chunk_offsets.len() != chunk_bytes.len() { + return Err(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + )); + } } (false, false, true, true) => { chunk_type = ChunkType::Tile; From 593479e5e32426d8eb1a44384688617fbbc7c7a3 Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Fri, 7 Jan 2022 21:01:13 -0500 Subject: [PATCH 17/20] Avoid reading tags in read_jpeg --- src/decoder/mod.rs | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 444d4a4..766336e 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -1222,33 +1222,27 @@ impl Decoder { pub fn read_jpeg(&mut self) -> TiffResult { self.check_chunk_type(ChunkType::Strip)?; - let offsets = self.get_tag_u32_vec(Tag::StripOffsets)?; - let bytes = self.get_tag_u32_vec(Tag::StripByteCounts)?; - - if offsets.len() == 0 { + if self.image().chunk_offsets.len() == 0 { return Err(TiffError::FormatError(TiffFormatError::RequiredTagEmpty( Tag::StripOffsets, ))); } - if offsets.len() != bytes.len() { - return Err(TiffError::FormatError( - TiffFormatError::InconsistentSizesEncountered, - )); - } - if offsets[0] as usize > self.limits.intermediate_buffer_size - || bytes + if self.image().chunk_offsets[0] as usize > self.limits.intermediate_buffer_size + || self.image().chunk_bytes .iter() .any(|&x| x as usize > self.limits.intermediate_buffer_size) { return Err(TiffError::LimitsExceeded); } - let mut res_img = Vec::with_capacity(offsets[0] as usize); + let mut res_img = Vec::with_capacity(self.image().chunk_offsets[0] as usize); - for (idx, offset) in offsets.iter().enumerate() { - self.goto_offset(*offset)?; - let length = bytes[idx]; + for idx in 0..self.image.chunk_offsets.len() { + let offset = self.image.chunk_offsets[idx]; + let length = self.image.chunk_bytes[idx]; + + self.goto_offset_u64(offset)?; if self.image().jpeg_tables.is_some() && length < 2 { return Err(TiffError::FormatError( @@ -1258,7 +1252,7 @@ impl Decoder { // TODO: avoid this clone let jpeg_tables = self.image().jpeg_tables.clone(); - let jpeg_reader = JpegReader::new(&mut self.reader, length, &jpeg_tables)?; + let jpeg_reader = JpegReader::new(&mut self.reader, u32::try_from(length)?, &jpeg_tables)?; let mut decoder = jpeg::Decoder::new(jpeg_reader); match decoder.decode() { From 01df985ef8f67e6670734626d866826677f6a4fd Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Wed, 12 Jan 2022 20:13:23 -0500 Subject: [PATCH 18/20] Fix formatting --- src/decoder/ifd.rs | 28 +++++++++++++++++----------- src/decoder/mod.rs | 7 +++++-- src/decoder/stream.rs | 2 +- src/decoder/tag_reader.rs | 5 +---- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/decoder/ifd.rs b/src/decoder/ifd.rs index b326433..bea51a1 100644 --- a/src/decoder/ifd.rs +++ b/src/decoder/ifd.rs @@ -562,12 +562,16 @@ impl Entry { Type::DOUBLE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { Ok(Double(reader.read_f64()?)) }), - Type::RATIONAL => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { - Ok(Rational(reader.read_u32()?, reader.read_u32()?)) - }), - Type::SRATIONAL => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { - Ok(SRational(reader.read_i32()?, reader.read_i32()?)) - }), + Type::RATIONAL => { + self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + Ok(Rational(reader.read_u32()?, reader.read_u32()?)) + }) + } + Type::SRATIONAL => { + self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + Ok(SRational(reader.read_i32()?, reader.read_i32()?)) + }) + } Type::LONG8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { Ok(UnsignedBig(reader.read_u64()?)) }), @@ -580,11 +584,13 @@ impl Entry { Type::IFD8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { Ok(IfdBig(reader.read_u64()?)) }), - Type::UNDEFINED => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { - let mut buf = [0; 1]; - reader.read_exact(&mut buf)?; - Ok(Byte(buf[0])) - }), + Type::UNDEFINED => { + self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + let mut buf = [0; 1]; + reader.read_exact(&mut buf)?; + Ok(Byte(buf[0])) + }) + } Type::ASCII => { let n = usize::try_from(self.count)?; if n > limits.decoding_buffer_size { diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 766336e..b3a5ae8 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -1229,7 +1229,9 @@ impl Decoder { } if self.image().chunk_offsets[0] as usize > self.limits.intermediate_buffer_size - || self.image().chunk_bytes + || self + .image() + .chunk_bytes .iter() .any(|&x| x as usize > self.limits.intermediate_buffer_size) { @@ -1252,7 +1254,8 @@ impl Decoder { // TODO: avoid this clone let jpeg_tables = self.image().jpeg_tables.clone(); - let jpeg_reader = JpegReader::new(&mut self.reader, u32::try_from(length)?, &jpeg_tables)?; + let jpeg_reader = + JpegReader::new(&mut self.reader, u32::try_from(length)?, &jpeg_tables)?; let mut decoder = jpeg::Decoder::new(jpeg_reader); match decoder.decode() { diff --git a/src/decoder/stream.rs b/src/decoder/stream.rs index a482826..bd247a4 100644 --- a/src/decoder/stream.rs +++ b/src/decoder/stream.rs @@ -340,7 +340,7 @@ where SmartReader { reader, byte_order } } pub fn into_inner(self) -> R { - self.reader + self.reader } } impl SmartReader { diff --git a/src/decoder/tag_reader.rs b/src/decoder/tag_reader.rs index 6691964..837da40 100644 --- a/src/decoder/tag_reader.rs +++ b/src/decoder/tag_reader.rs @@ -29,10 +29,7 @@ impl<'a, R: Read + Seek> TagReader<'a, R> { )), } } - pub fn find_tag_uint_vec>( - &mut self, - tag: Tag, - ) -> TiffResult>> { + pub fn find_tag_uint_vec>(&mut self, tag: Tag) -> TiffResult>> { self.find_tag(tag)? .map(|v| v.into_u64_vec()) .transpose()? From 2c7c8891f49d1c01ea5b3d906161df3bd5309175 Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Wed, 12 Jan 2022 20:46:17 -0500 Subject: [PATCH 19/20] Remove use half-open range pattern which is unstable on rustc 1.48 --- src/decoder/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index b3a5ae8..8ba323b 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -806,7 +806,7 @@ impl Decoder { let next_ifd = match next_ifd { 0 => None, - 1.. => Some(next_ifd), + _ => Some(next_ifd), }; Ok((dir, next_ifd)) From ca56836a79da4325c9a9c9b3539942b65691129f Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Sat, 15 Jan 2022 15:11:54 -0500 Subject: [PATCH 20/20] Add back fuzzer generated testcases --- tests/decode_images.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/decode_images.rs b/tests/decode_images.rs index d128efd..5f32de1 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -343,6 +343,19 @@ fn test_bad_strip_count() { } } +#[test] +fn fuzzer_testcase1() { + let image = [ + 73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 99, 255, 255, 254, 1, 1, 4, 0, 1, + 0, 0, 0, 158, 0, 0, 251, 3, 1, 3, 255, 254, 255, 255, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 17, 1, 4, 0, 9, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 0, 2, 0, 0, 0, 63, 0, 0, 0, + 22, 1, 4, 0, 1, 0, 0, 0, 44, 0, 0, 0, 23, 1, 4, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 1, 0, 178, + 178, + ]; + + let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); +} + #[test] fn test_stripped_image_overflow() { let image = [ @@ -401,6 +414,22 @@ fn oom_2() { } } +#[test] +fn fuzzer_testcase2() { + let image = [ + 73, 73, 42, 0, 8, 0, 0, 0, 15, 0, 0, 254, 44, 1, 0, 0, 0, 0, 0, 32, 0, 0, 0, 1, 4, 0, 1, 0, + 0, 0, 0, 1, 0, 0, 91, 1, 1, 0, 0, 0, 0, 0, 242, 4, 0, 0, 0, 22, 0, 56, 77, 0, 77, 1, 0, 0, + 73, 42, 0, 1, 4, 0, 1, 0, 0, 0, 4, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 158, 0, 0, 251, 3, 1, + 3, 0, 1, 0, 0, 0, 7, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0, 17, 1, 4, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 4, 61, 1, 18, 0, 1, 0, 0, 0, 202, 0, 0, 0, 17, + 1, 100, 0, 129, 0, 0, 0, 0, 0, 0, 0, 232, 254, 252, 255, 254, 255, 255, 255, 1, 29, 0, 0, + 22, 1, 3, 0, 1, 0, 0, 0, 16, 0, 0, 0, 23, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 123, 73, 254, 0, + 73, + ]; + + let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); +} + #[test] fn invalid_jpeg_tag_2() { use tiff::tags; @@ -427,6 +456,19 @@ fn invalid_jpeg_tag_2() { } } +#[test] +fn fuzzer_testcase3() { + let image = [ + 73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 2, 0, 0, 0, 61, 1, 9, 0, 46, 22, + 128, 0, 0, 0, 0, 1, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 17, 1, 4, 0, 27, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 3, 0, 1, 0, 0, 0, 17, 1, 0, 231, 22, 1, 1, 0, 1, 0, 0, 0, 130, 0, 0, 0, 23, 1, 4, + 0, 14, 0, 0, 0, 0, 0, 0, 0, 133, 133, 133, 77, 77, 77, 0, 0, 22, 128, 0, 255, 255, 255, + 255, 255, + ]; + + let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); +} + #[test] fn timeout() { use tiff::{TiffError, TiffFormatError};