diff --git a/src/decoder/ifd.rs b/src/decoder/ifd.rs index 23416a4..bea51a1 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,64 +536,80 @@ 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, 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()?)) }), - Type::SRATIONAL => self.decode_offset(self.count, bo, limits, decoder, |decoder| { - Ok(SRational(decoder.read_slong()?, decoder.read_slong()?)) + Type::SLONG8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + Ok(SignedBig(reader.read_i64()?)) }), - Type::LONG8 => self.decode_offset(self.count, bo, limits, decoder, |decoder| { - Ok(UnsignedBig(decoder.read_long8()?)) + Type::IFD => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + Ok(Ifd(reader.read_u32()?)) }), - Type::SLONG8 => self.decode_offset(self.count, bo, limits, decoder, |decoder| { - Ok(SignedBig(decoder.read_slong8()?)) - }), - Type::IFD => self.decode_offset(self.count, bo, limits, decoder, |decoder| { - Ok(Ifd(decoder.read_long()?)) - }), - Type::IFD8 => self.decode_offset(self.count, bo, limits, decoder, |decoder| { - Ok(IfdBig(decoder.read_long8()?)) - }), - Type::UNDEFINED => self.decode_offset(self.count, bo, limits, decoder, |decoder| { - Ok(Byte(u8::from(decoder.read_byte()?))) + 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::ASCII => { let n = usize::try_from(self.count)?; if n > limits.decoding_buffer_size { 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 +620,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 +635,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/image.rs b/src/decoder/image.rs new file mode 100644 index 0000000..a4a9189 --- /dev/null +++ b/src/decoder/image.rs @@ -0,0 +1,290 @@ +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 { + 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, + #[allow(unused)] + 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 { + pub fn from_reader( + reader: &mut SmartReader, + ifd: Directory, + limits: &Limits, + bigtiff: bool, + ) -> TiffResult { + let mut tag_reader = TagReader { + reader, + limits, + ifd: &ifd, + bigtiff, + }; + + 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; + + if chunk_offsets.len() != chunk_bytes.len() { + return Err(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + )); + } + } + (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 bd48457..8ba323b 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; use crate::tags::{ CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag, Type, }; @@ -17,7 +18,9 @@ use self::stream::{ }; pub mod ifd; +mod image; mod stream; +mod tag_reader; /// Result of a decoding process #[derive(Debug)] @@ -233,68 +236,6 @@ impl<'a> DecodingBuffer<'a> { } } -#[derive(Debug)] -struct StripDecodeState { - strip_index: usize, - strip_offsets: Vec, - strip_bytes: Vec, -} - -#[derive(Debug)] -/// Computed values useful for tile decoding -struct TileAttributes { - 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 { - /// 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)] -/// Stateful variables for tile decoding -struct TileDecodeState { - current_tile: usize, - tile_offsets: Vec, - tile_bytes: Vec, -} - #[derive(Debug, Copy, Clone, PartialEq)] /// Chunk type of the internal representation pub enum ChunkType { @@ -360,23 +301,12 @@ where R: Read + Seek, { reader: SmartReader, - byte_order: ByteOrder, 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, - chunk_type: ChunkType, - strip_decoder: Option, - tile_decoder: Option, - tile_attributes: Option, + current_chunk: usize, seen_ifds: HashSet, + image: Image, } trait Wrapping { @@ -524,28 +454,79 @@ 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, + pub fn new(mut r: R) -> TiffResult> { + 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, + _ => { + 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 mut seen_ifds = HashSet::new(); + seen_ifds.insert(*next_ifd.as_ref().unwrap()); + + let mut decoder = Decoder { + reader, + bigtiff, limits: Default::default(), - next_ifd: None, - 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, - chunk_type: ChunkType::Strip, - strip_decoder: None, - tile_decoder: None, - tile_attributes: None, - seen_ifds: HashSet::new(), - } - .init() + next_ifd, + 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, + }; + decoder.next_image()?; + Ok(decoder) } pub fn with_limits(mut self, limits: Limits) -> Decoder { @@ -554,12 +535,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: @@ -569,208 +550,72 @@ 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 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(()) + fn image(&self) -> &Image { + &self.image } - /// 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. /// 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_decoder = None; - self.tile_attributes = None; + if self.next_ifd.is_none() { + return Err(TiffError::FormatError( + TiffFormatError::ImageFileDirectoryNotFound, + )); + } - self.photometric_interpretation = self - .find_tag_unsigned(Tag::PhotometricInterpretation)? - .and_then(PhotometricInterpretation::from_u16) - .ok_or(TiffUnsupportedError::UnknownInterpretation)?; + let (ifd, next_ifd) = Self::read_ifd( + &mut self.reader, + self.bigtiff, + self.next_ifd.take().unwrap(), + )?; - // 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)? { - Some(val) => CompressionMethod::from_u16(val) - .ok_or(TiffUnsupportedError::UnknownCompressionMethod)?, - None => CompressionMethod::None, - }; - - self.samples = self.find_tag_unsigned(Tag::SamplesPerPixel)?.unwrap_or(1); - - self.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.sample_format.clone(), - ) - .into()); - } - - sample_format + if let Some(next) = next_ifd { + if !self.seen_ifds.insert(next) { + return Err(TiffError::FormatError(TiffFormatError::CycleInOffsets)); } - None => vec![SampleFormat::Uint], - }; - - self.bits_per_sample = match self.samples { - 1 | 3 | 4 => self - .find_tag_unsigned_vec(Tag::BitsPerSample)? - .unwrap_or_else(|| vec![1]), - _ => return Err(TiffUnsupportedError::UnsupportedSampleDepth(self.samples).into()), - }; - - let ifd = self.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.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.strip_decoder = Some(StripDecodeState { - strip_index: 0, - strip_offsets, - strip_bytes, - }); - } - (false, false, true, true) => { - self.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()); - } - - 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 { - 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.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)?, - }); - } - (_, _, _, _) => { - return Err(TiffError::FormatError( - TiffFormatError::StripTileTagConflict, - )) - } - }; + self.next_ifd = Some(next); + } + self.current_chunk = 0; + self.image = Image::from_reader(&mut self.reader, ifd, &self.limits, self.bigtiff)?; Ok(()) } @@ -781,16 +626,7 @@ impl Decoder { /// Returns the byte_order pub fn byte_order(&self) -> ByteOrder { - self.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)) - } + self.reader.byte_order } #[inline] @@ -907,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; @@ -951,24 +797,34 @@ impl Decoder { }; dir.insert(tag, entry); } - self.next_ifd = match self.read_ifd_offset_nonrepeating()? { - 0 => None, - n => Some(n), + + let next_ifd = if bigtiff { + reader.read_u64()? + } else { + reader.read_u32()?.into() }; - Ok(dir) + + let next_ifd = match next_ifd { + 0 => None, + _ => Some(next_ifd), + }; + + Ok((dir, next_ifd)) } /// 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(), }; - 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. @@ -1174,10 +1030,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(), @@ -1210,7 +1067,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); } @@ -1228,21 +1085,24 @@ 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 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_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 row_samples = tile_attrs.row_samples(); + let padding_right_samples = padding_right * self.image().bits_per_sample.len(); self.goto_offset_u64(offset)?; - let line_samples = output_width * self.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, @@ -1275,9 +1135,9 @@ 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 { + if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { Self::invert_colors(&mut buffer.subrange(row_start..row_end), color_type); } } @@ -1320,10 +1180,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, ))); } @@ -1332,20 +1192,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.get_tag_u32(Tag::RowsPerStrip).unwrap_or(self.height); + 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), }; @@ -1356,63 +1216,46 @@ 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.image().chunk_offsets.len())?) } pub fn read_jpeg(&mut self) -> TiffResult { - let offsets = self.get_tag_u32_vec(Tag::StripOffsets)?; - let bytes = self.get_tag_u32_vec(Tag::StripByteCounts)?; + self.check_chunk_type(ChunkType::Strip)?; - 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 { + 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]; - if jpeg_tables.is_some() && length < 2 { + self.goto_offset_u64(offset)?; + + 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, &jpeg_tables)?; + // 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 mut decoder = jpeg::Decoder::new(jpeg_reader); match decoder.decode() { @@ -1439,30 +1282,26 @@ 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() - .unwrap() - .strip_offsets + .image() + .chunk_offsets .get(index) .ok_or(TiffError::FormatError( TiffFormatError::InconsistentSizesEncountered, ))?; let byte_count = *self - .strip_decoder - .as_ref() - .unwrap() - .strip_bytes + .image() + .chunk_bytes .get(index) .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.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) @@ -1474,7 +1313,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 { @@ -1484,29 +1323,19 @@ 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; - } - 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.image().predictor { + Predictor::None => {} + Predictor::Horizontal => { + rev_hpredict( + buffer.copy(), + (self.image().width, u32::try_from(strip_height)?), + usize::try_from(self.image().width)?, + self.colortype()?, + )?; } + Predictor::__NonExhaustive => unreachable!(), } Ok(()) } @@ -1518,26 +1347,23 @@ impl Decoder { output_width: usize, ) -> TiffResult<()> { let file_offset = *self - .tile_decoder - .as_ref() - .unwrap() - .tile_offsets + .image() + .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 + .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; @@ -1551,27 +1377,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.image().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(()) } @@ -1579,14 +1398,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), @@ -1621,17 +1451,17 @@ 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.get_tag_u32(Tag::RowsPerStrip).unwrap_or(self.height))?; + 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) @@ -1641,9 +1471,9 @@ 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 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; @@ -1653,21 +1483,22 @@ 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) } 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)?)?; - 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) { - 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)?; } @@ -1676,18 +1507,20 @@ 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))?; + 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); @@ -1698,7 +1531,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()?, diff --git a/src/decoder/stream.rs b/src/decoder/stream.rs index c1c990d..bd247a4 100644 --- a/src/decoder/stream.rs +++ b/src/decoder/stream.rs @@ -339,6 +339,14 @@ where pub fn wrap(reader: R, byte_order: ByteOrder) -> SmartReader { SmartReader { reader, byte_order } } + pub fn into_inner(self) -> R { + self.reader + } +} +impl SmartReader { + pub fn goto_offset(&mut self, offset: u64) -> io::Result<()> { + self.seek(io::SeekFrom::Start(offset)).map(|_| ()) + } } impl EndianReader for SmartReader diff --git a/src/decoder/tag_reader.rs b/src/decoder/tag_reader.rs new file mode 100644 index 0000000..837da40 --- /dev/null +++ b/src/decoder/tag_reader.rs @@ -0,0 +1,45 @@ +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() + } +} diff --git a/tests/decode_images.rs b/tests/decode_images.rs index 7e33d17..5f32de1 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -344,7 +344,7 @@ fn test_bad_strip_count() { } #[test] -fn test_too_big_buffer_size() { +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, @@ -353,14 +353,7 @@ fn test_too_big_buffer_size() { 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), - } + let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); } #[test] @@ -422,10 +415,7 @@ fn oom_2() { } #[test] -fn invalid_jpeg_tag() { - use tiff::tags; - use tiff::{TiffError, TiffFormatError}; - +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, @@ -437,14 +427,7 @@ fn invalid_jpeg_tag() { 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), - } + let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); } #[test] @@ -474,9 +457,7 @@ fn invalid_jpeg_tag_2() { } #[test] -fn multiple_images_strip_height() { - use tiff::TiffError; - +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, @@ -485,15 +466,7 @@ fn multiple_images_strip_height() { 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), - } + let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); } #[test]