diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 5d3169a..bc073a3 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -662,58 +662,66 @@ 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.photometric_interpretation = self .find_tag_unsigned(Tag::PhotometricInterpretation)? .and_then(PhotometricInterpretation::from_u16) .ok_or(TiffUnsupportedError::UnknownInterpretation)?; - if let Some(val) = self.find_tag_unsigned(Tag::Compression)? { - self.compression_method = CompressionMethod::from_u16(val) - .ok_or(TiffUnsupportedError::UnknownCompressionMethod)?; - } - if let Some(val) = self.find_tag_unsigned(Tag::SamplesPerPixel)? { - self.samples = val; - } - if let Some(vals) = self.find_tag_unsigned_vec(Tag::SampleFormat)? { - self.sample_format = vals - .into_iter() - .map(SampleFormat::from_u16_exhaustive) - .collect(); + // 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, + }; - // TODO: for now, only homogenous formats across samples are supported. - if !self.sample_format.windows(2).all(|s| s[0] == s[1]) { - return Err(TiffUnsupportedError::UnsupportedSampleFormat( - self.sample_format.clone(), - ) - .into()); - } - } - match self.samples { - 1 | 3 | 4 => { - if let Some(val) = self.find_tag_unsigned_vec(Tag::BitsPerSample)? { - self.bits_per_sample = val; + 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()); } - } - _ => return Err(TiffUnsupportedError::UnsupportedSampleDepth(self.samples).into()), - } - self.chunk_type = - match ( - self.get_tag_u32(Tag::RowsPerStrip), - self.get_tag_u32(Tag::TileWidth), - self.get_tag_u32(Tag::TileLength), - ) { - (Ok(_), Err(_), Err(_)) => ChunkType::Strip, - (Err(_), Ok(_), Ok(_)) => ChunkType::Tile, - // TODO: The spec says not to use both strip-oriented fields and tile-oriented fields. - // We can relax this later if it becomes a problem - _ => return Err(TiffError::FormatError(TiffFormatError::Format( - String::from( - "Neither strips nor tiles were found or both were used in the same file", - ), - ))), - }; + sample_format + } + 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(); + self.chunk_type = 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) => ChunkType::Strip, + (false, false, true, true) => ChunkType::Tile, + (_, _, _, _) => { + return Err(TiffError::FormatError( + TiffFormatError::StripTileTagConflict, + )) + } + }; Ok(()) } diff --git a/src/encoder/colortype.rs b/src/encoder/colortype.rs index 4813ba2..f6c775a 100644 --- a/src/encoder/colortype.rs +++ b/src/encoder/colortype.rs @@ -20,6 +20,14 @@ impl ColorType for Gray8 { const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint]; } +pub struct GrayI8; +impl ColorType for GrayI8 { + type Inner = i8; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; + const BITS_PER_SAMPLE: &'static [u16] = &[8]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int]; +} + pub struct Gray16; impl ColorType for Gray16 { type Inner = u16; @@ -28,6 +36,14 @@ impl ColorType for Gray16 { const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint]; } +pub struct GrayI16; +impl ColorType for GrayI16 { + type Inner = i16; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; + const BITS_PER_SAMPLE: &'static [u16] = &[16]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int]; +} + pub struct Gray32; impl ColorType for Gray32 { type Inner = u32; @@ -36,6 +52,14 @@ impl ColorType for Gray32 { const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint]; } +pub struct GrayI32; +impl ColorType for GrayI32 { + type Inner = i32; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; + const BITS_PER_SAMPLE: &'static [u16] = &[32]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int]; +} + pub struct Gray32Float; impl ColorType for Gray32Float { type Inner = f32; @@ -52,6 +76,14 @@ impl ColorType for Gray64 { const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint]; } +pub struct GrayI64; +impl ColorType for GrayI64 { + type Inner = i64; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; + const BITS_PER_SAMPLE: &'static [u16] = &[64]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int]; +} + pub struct Gray64Float; impl ColorType for Gray64Float { type Inner = f64; diff --git a/src/error.rs b/src/error.rs index cd7ad30..67e5f08 100644 --- a/src/error.rs +++ b/src/error.rs @@ -63,6 +63,7 @@ pub enum TiffFormatError { SignedIntegerExpected(Value), Format(String), RequiredTagEmpty(Tag), + StripTileTagConflict, #[doc(hidden)] /// Do not match against this variant. It may get removed. __NonExhaustive, @@ -113,6 +114,7 @@ impl fmt::Display for TiffFormatError { } Format(ref val) => write!(fmt, "Invalid format: {:?}.", val), RequiredTagEmpty(ref val) => write!(fmt, "Required tag {:?} was empty.", val), + StripTileTagConflict => write!(fmt, "File should contain either (StripByteCounts and StripOffsets) or (TileByteCounts and TileOffsets), other combination was found."), __NonExhaustive => unreachable!(), } } diff --git a/tests/decode_images.rs b/tests/decode_images.rs index 3e7f35c..f528601 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -287,7 +287,6 @@ fn test_tiled_incremental() { #[test] fn test_div_zero() { - use tiff::tags; use tiff::{TiffError, TiffFormatError}; let image = [ @@ -298,12 +297,10 @@ fn test_div_zero() { 178, 178, 178, ]; - let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap(); - - let err = decoder.read_image().unwrap_err(); + let err = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); match err { - TiffError::FormatError(TiffFormatError::InvalidTagValueType(tags::Tag::TileWidth)) => {} + TiffError::FormatError(TiffFormatError::StripTileTagConflict) => {} unexpected => panic!("Unexpected error {}", unexpected), } } @@ -530,3 +527,8 @@ fn timeout() { } */ } + +#[test] +fn test_no_rows_per_strip() { + test_image_sum_u8("no_rows_per_strip.tiff", ColorType::RGB(8), 99448840); +} diff --git a/tests/images/no_rows_per_strip.tiff b/tests/images/no_rows_per_strip.tiff new file mode 100644 index 0000000..017ce58 Binary files /dev/null and b/tests/images/no_rows_per_strip.tiff differ