diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index c9f895f..7b709d0 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -229,12 +229,19 @@ pub struct TiffHeader { /// The representation of a TIFF decoder /// -/// Currently does not support decoding of interlaced images +/// There are three groups of operations: +/// +/// - [Manipulate the directory state](#header-manipulate-directory) to which the decoder currently +/// refers in the file. +/// - [Interact with the underlying stream](#header-stream-access) instead of entries in an IFD. +/// - [Access the encoded image](#header-image). +/// +/// ## Limitations +/// +/// Currently does not support decoding of interlaced images. It also does not support images with +/// downsampling in any direction; except if they are also JPEG or WebP compressed. #[derive(Debug)] -pub struct Decoder -where - R: Read + Seek, -{ +pub struct Decoder { /// There are grouped for borrow checker reasons. This allows us to implement methods that /// borrow the stream access and the other fields mutably at the same time. value_reader: ValueReader, @@ -622,6 +629,7 @@ impl TiffHeader { } } +/// Methods to manipulate the directory state impl Decoder { /// Parse the image header from the underlying file and create a decoder positioned before its /// first directory. @@ -699,29 +707,25 @@ impl Decoder { self } - pub fn dimensions(&mut self) -> TiffResult<(u32, u32)> { - Ok((self.image.width, self.image.height)) - } - - pub fn colortype(&mut self) -> TiffResult { - self.image.colortype() - } - - /// Returns the color map (palette) for the current image, if it uses - /// `PhotometricInterpretation::RGBPalette`. - /// - /// The returned slice contains `3 * 2^BitsPerSample` entries as u16 values, - /// laid out as: all red values, then all green values, then all blue values. - /// Each value is in the range `0..=65535` regardless of the actual bit depth. - pub fn color_map(&self) -> Option<&[u16]> { - self.image.color_map.as_deref() - } - /// The offset of the directory representing the current image. pub fn ifd_pointer(&mut self) -> Option { self.current_ifd_pointer } + /// Get the IFD decoder for our current IFD. + /// + /// This may be used with any directory, valid image or otherwise. When the decoder has not + /// read a directory yet (a state after [`TiffHeader::open`]) the decoder will represent an + /// empty directory with only the next pointer set. + pub fn current_ifd(&mut self) -> IfdDecoder<'_> { + IfdDecoder { + inner: tag_reader::TagReader { + decoder: &mut self.value_reader, + ifd: &self.directory, + }, + } + } + /// Loads the IFD at the specified index in the list, if one exists /// /// Error guarantee: the current directory and [`Self::ifd_pointer`] is not modified when an @@ -835,6 +839,64 @@ impl Decoder { Ok(()) } + /// Read a tag-entry map from a known offset. + /// + /// A TIFF [`Directory`], aka. image file directory aka. IFD, refers to a map from + /// tags–identified by a `u16`–to a typed vector of elements. It is encoded as a list + /// of ascending tag values with the offset and type of their corresponding values. The + /// semantic interpretations of a tag and its type requirements depend on the context of the + /// directory. The main image directories, those iterated over by the `Decoder` after + /// construction, are represented by [`Tag`] and [`ifd::Value`]. Other forms are EXIF and GPS + /// data as well as thumbnail Sub-IFD representations associated with each image file. + /// + /// This method allows the decoding of a directory from an arbitrary offset in the image file + /// with no specific semantic interpretation. Such an offset is usually found as the value of + /// a tag, e.g. [`Tag::SubIfd`], [`Tag::ExifDirectory`], [`Tag::GpsDirectory`] and recovered + /// from the associated value by [`ifd::Value::into_ifd_pointer`]. + /// + /// The library will not verify whether the offset overlaps any other directory or would form a + /// cycle with any other directory when calling this method. This will modify the position of + /// the reader, i.e. continuing with direct reads at a later point will require going back with + /// [`Self::goto_offset`]. + pub fn read_directory(&mut self, ptr: IfdPointer) -> TiffResult { + self.value_reader.read_directory(ptr) + } + + /// Prepare reading values for tags of a given directory. + /// + /// # Examples + /// + /// This method may be used to read the values of tags in directories that have been previously + /// read with [`Decoder::read_directory`]. + /// + /// ```no_run + /// use tiff::decoder::Decoder; + /// use tiff::tags::Tag; + /// + /// # use std::io::Cursor; + /// # let mut data = Cursor::new(vec![0]); + /// let mut decoder = Decoder::open(&mut data)?; + /// + /// decoder.next_directory()?; + /// let sub_ifds = decoder.get_tag(Tag::SubIfd)?.into_ifd_vec()?; + /// + /// for ifd in sub_ifds { + /// let subdir = decoder.read_directory(ifd)?; + /// let subfile = decoder.read_directory_tags(&subdir).find_tag(Tag::SubfileType)?; + /// // omitted: handle the subfiles, e.g. thumbnails + /// } + /// + /// # Ok::<_, tiff::TiffError>(()) + /// ``` + pub fn read_directory_tags<'ifd>(&'ifd mut self, ifd: &'ifd Directory) -> IfdDecoder<'ifd> { + IfdDecoder { + inner: tag_reader::TagReader { + decoder: &mut self.value_reader, + ifd, + }, + } + } + /// Returns `true` if there is at least one more image available. pub fn more_images(&self) -> bool { self.directory.next().is_some() @@ -849,6 +911,14 @@ impl Decoder { pub fn byte_order(&self) -> ByteOrder { self.value_reader.reader.byte_order } +} + +/// Methods that interact with the underlying reader. +impl Decoder { + /// Returns a mutable reference to the stream being decoded. + pub fn inner(&mut self) -> &mut R { + self.value_reader.reader.inner() + } #[inline] pub fn read_ifd_offset(&mut self) -> Result { @@ -859,11 +929,6 @@ impl Decoder { } } - /// Returns a mutable reference to the stream being decoded. - pub fn inner(&mut self) -> &mut R { - self.value_reader.reader.inner() - } - /// Reads a TIFF byte value #[inline] pub fn read_byte(&mut self) -> Result { @@ -953,38 +1018,24 @@ impl Decoder { /// Moves the cursor to the specified offset #[inline] - pub fn goto_offset(&mut self, offset: u32) -> io::Result<()> { + pub fn goto_offset(&mut self, offset: u32) -> io::Result<()> + where + R: Seek, + { self.goto_offset_u64(offset.into()) } #[inline] - pub fn goto_offset_u64(&mut self, offset: u64) -> io::Result<()> { + pub fn goto_offset_u64(&mut self, offset: u64) -> io::Result<()> + where + R: Seek, + { self.value_reader.reader.goto_offset(offset) } +} - /// Read a tag-entry map from a known offset. - /// - /// A TIFF [`Directory`], aka. image file directory aka. IFD, refers to a map from - /// tags–identified by a `u16`–to a typed vector of elements. It is encoded as a list - /// of ascending tag values with the offset and type of their corresponding values. The - /// semantic interpretations of a tag and its type requirements depend on the context of the - /// directory. The main image directories, those iterated over by the `Decoder` after - /// construction, are represented by [`Tag`] and [`ifd::Value`]. Other forms are EXIF and GPS - /// data as well as thumbnail Sub-IFD representations associated with each image file. - /// - /// This method allows the decoding of a directory from an arbitrary offset in the image file - /// with no specific semantic interpretation. Such an offset is usually found as the value of - /// a tag, e.g. [`Tag::SubIfd`], [`Tag::ExifDirectory`], [`Tag::GpsDirectory`] and recovered - /// from the associated value by [`ifd::Value::into_ifd_pointer`]. - /// - /// The library will not verify whether the offset overlaps any other directory or would form a - /// cycle with any other directory when calling this method. This will modify the position of - /// the reader, i.e. continuing with direct reads at a later point will require going back with - /// [`Self::goto_offset`]. - pub fn read_directory(&mut self, ptr: IfdPointer) -> TiffResult { - self.value_reader.read_directory(ptr) - } - +/// Methods that interact with the current directory as an image. +impl Decoder { fn check_chunk_type(&self, expected: ChunkType) -> TiffResult<()> { if expected != self.image.chunk_type { return Err(TiffError::UsageError(UsageError::InvalidChunkType( @@ -996,6 +1047,24 @@ impl Decoder { Ok(()) } + pub fn dimensions(&mut self) -> TiffResult<(u32, u32)> { + Ok((self.image.width, self.image.height)) + } + + pub fn colortype(&mut self) -> TiffResult { + self.image.colortype() + } + + /// Returns the color map (palette) for the current image, if it uses + /// `PhotometricInterpretation::RGBPalette`. + /// + /// The returned slice contains `3 * 2^BitsPerSample` entries as u16 values, + /// laid out as: all red values, then all green values, then all blue values. + /// Each value is in the range `0..=65535` regardless of the actual bit depth. + pub fn color_map(&self) -> Option<&[u16]> { + self.image.color_map.as_deref() + } + /// The chunk type (Strips / Tiles) of the image pub fn get_chunk_type(&self) -> ChunkType { self.image.chunk_type @@ -1409,58 +1478,9 @@ impl Decoder { Ok(()) } - - /// Get the IFD decoder for our current IFD. - /// - /// This may be used with any directory, valid image or otherwise. When the decoder has not - /// read a directory yet (a state after [`TiffHeader::open`]) the decoder will represent an - /// empty directory with only the next pointer set. - pub fn current_ifd(&mut self) -> IfdDecoder<'_> { - IfdDecoder { - inner: tag_reader::TagReader { - decoder: &mut self.value_reader, - ifd: &self.directory, - }, - } - } - - /// Prepare reading values for tags of a given directory. - /// - /// # Examples - /// - /// This method may be used to read the values of tags in directories that have been previously - /// read with [`Decoder::read_directory`]. - /// - /// ```no_run - /// use tiff::decoder::Decoder; - /// use tiff::tags::Tag; - /// - /// # use std::io::Cursor; - /// # let mut data = Cursor::new(vec![0]); - /// let mut decoder = Decoder::open(&mut data)?; - /// - /// decoder.next_directory()?; - /// let sub_ifds = decoder.get_tag(Tag::SubIfd)?.into_ifd_vec()?; - /// - /// for ifd in sub_ifds { - /// let subdir = decoder.read_directory(ifd)?; - /// let subfile = decoder.read_directory_tags(&subdir).find_tag(Tag::SubfileType)?; - /// // omitted: handle the subfiles, e.g. thumbnails - /// } - /// - /// # Ok::<_, tiff::TiffError>(()) - /// ``` - pub fn read_directory_tags<'ifd>(&'ifd mut self, ifd: &'ifd Directory) -> IfdDecoder<'ifd> { - IfdDecoder { - inner: tag_reader::TagReader { - decoder: &mut self.value_reader, - ifd, - }, - } - } } -#[doc(hidden = "Deprecated methods")] +#[doc(hidden)] #[allow(deprecated)] impl Decoder { /// Tries to retrieve a tag from the current image directory. diff --git a/tests/decode_bigtiff_images.rs b/tests/decode_bigtiff_images.rs index 127e18c..3a51009 100644 --- a/tests/decode_bigtiff_images.rs +++ b/tests/decode_bigtiff_images.rs @@ -26,21 +26,20 @@ fn test_big_tiff() { decoder.colortype().expect("Cannot get colortype"), ColorType::RGB(8) ); + + let mut ifd = decoder.current_ifd(); assert_eq!( - decoder - .get_tag_u64(Tag::StripOffsets) + ifd.get_tag_u64(Tag::StripOffsets) .expect("Cannot get StripOffsets"), 16 ); assert_eq!( - decoder - .get_tag_u64(Tag::RowsPerStrip) + ifd.get_tag_u64(Tag::RowsPerStrip) .expect("Cannot get RowsPerStrip"), 64 ); assert_eq!( - decoder - .get_tag_u64(Tag::StripByteCounts) + ifd.get_tag_u64(Tag::StripByteCounts) .expect("Cannot get StripByteCounts"), 12288 ) diff --git a/tests/decode_geotiff_images.rs b/tests/decode_geotiff_images.rs index b40aff2..e582dbd 100644 --- a/tests/decode_geotiff_images.rs +++ b/tests/decode_geotiff_images.rs @@ -30,27 +30,25 @@ fn test_geo_tiff() { num_samples: 5 } ); + + let mut ifd = decoder.current_ifd(); assert_eq!( - decoder - .get_tag_u64(Tag::StripOffsets) + ifd.get_tag_u64(Tag::StripOffsets) .expect("Cannot get StripOffsets"), 418 ); assert_eq!( - decoder - .get_tag_u64(Tag::RowsPerStrip) + ifd.get_tag_u64(Tag::RowsPerStrip) .expect("Cannot get RowsPerStrip"), 10 ); assert_eq!( - decoder - .get_tag_u64(Tag::StripByteCounts) + ifd.get_tag_u64(Tag::StripByteCounts) .expect("Cannot get StripByteCounts"), 1000 ); assert_eq!( - decoder - .get_tag(Tag::ModelPixelScaleTag) + ifd.get_tag(Tag::ModelPixelScaleTag) .expect("Cannot get pixel scale") .into_f64_vec() .expect("Cannot get pixel scale"), diff --git a/tests/decode_images.rs b/tests/decode_images.rs index 213be5a..a68380a 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -404,7 +404,10 @@ fn test_string_tags() { let img_file = File::open(path).expect("Cannot find test image!"); let mut decoder = Decoder::open(img_file).expect("Cannot create decoder"); decoder.next_image().expect("Cannot read image IFD"); - let software = decoder.get_tag(tiff::tags::Tag::Software).unwrap(); + let software = decoder + .current_ifd() + .get_tag(tiff::tags::Tag::Software) + .unwrap(); match software { ifd::Value::Ascii(s) => assert_eq!( &s, @@ -478,7 +481,8 @@ fn test_inner_access() { decoder.next_image().expect("Cannot read image IFD"); assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8)); - let c = decoder + let mut ifd = decoder.current_ifd(); + let c = ifd .get_tag(tiff::tags::Tag::Compression) .unwrap() .into_u16() @@ -487,13 +491,13 @@ fn test_inner_access() { // Because the image is uncompressed, reading the first tile directly with the inner reader // should yield the same result as reading it with the decoder's read_chunk method. - let first_offset = decoder + let first_offset = ifd .get_tag_u32_vec(tiff::tags::Tag::TileOffsets) .unwrap() .into_iter() .next() .unwrap(); - let first_byte_count = decoder + let first_byte_count = ifd .get_tag_u32_vec(tiff::tags::Tag::TileByteCounts) .unwrap() .into_iter() diff --git a/tests/encode_images.rs b/tests/encode_images.rs index 63aae78..3790cb5 100644 --- a/tests/encode_images.rs +++ b/tests/encode_images.rs @@ -40,6 +40,7 @@ fn encode_decode() { assert_eq!(decoder.dimensions().unwrap(), (100, 100)); let mut all_tags = decoder + .current_ifd() .tag_iter() .filter_map(Result::ok) .collect::>(); @@ -80,7 +81,7 @@ fn encode_decode() { ); assert_eq!( - decoder.get_tag(Tag::Artist).unwrap(), + decoder.current_ifd().get_tag(Tag::Artist).unwrap(), ifd::Value::Ascii("Image-tiff".into()) ); @@ -125,6 +126,7 @@ fn encode_decode_big() { assert_eq!(decoder.dimensions().unwrap(), (100, 100)); let mut all_tags = decoder + .current_ifd() .tag_iter() .filter_map(Result::ok) .collect::>(); @@ -165,7 +167,7 @@ fn encode_decode_big() { ); assert_eq!( - decoder.get_tag(Tag::Artist).unwrap(), + decoder.current_ifd().get_tag(Tag::Artist).unwrap(), ifd::Value::Ascii("Image-tiff".into()) ); let mut buffer = tiff::decoder::DecodingSampleBuffer::U8(vec![]); @@ -539,37 +541,57 @@ trait AssertDecode { impl AssertDecode for Decoder { fn assert_tag_u32(&mut self, tag: u16) -> u32 { - self.get_tag(Tag::Unknown(tag)).unwrap().into_u32().unwrap() + self.current_ifd() + .get_tag(Tag::Unknown(tag)) + .unwrap() + .into_u32() + .unwrap() } fn assert_tag_u32_vec(&mut self, tag: u16) -> Vec { - self.get_tag(Tag::Unknown(tag)) + self.current_ifd() + .get_tag(Tag::Unknown(tag)) .unwrap() .into_u32_vec() .unwrap() } fn assert_tag_i32(&mut self, tag: u16) -> i32 { - self.get_tag(Tag::Unknown(tag)).unwrap().into_i32().unwrap() + self.current_ifd() + .get_tag(Tag::Unknown(tag)) + .unwrap() + .into_i32() + .unwrap() } fn assert_tag_i32_vec(&mut self, tag: u16) -> Vec { - self.get_tag(Tag::Unknown(tag)) + self.current_ifd() + .get_tag(Tag::Unknown(tag)) .unwrap() .into_i32_vec() .unwrap() } fn assert_tag_u64(&mut self, tag: u16) -> u64 { - self.get_tag(Tag::Unknown(tag)).unwrap().into_u64().unwrap() + self.current_ifd() + .get_tag(Tag::Unknown(tag)) + .unwrap() + .into_u64() + .unwrap() } fn assert_tag_u64_vec(&mut self, tag: u16) -> Vec { - self.get_tag(Tag::Unknown(tag)) + self.current_ifd() + .get_tag(Tag::Unknown(tag)) .unwrap() .into_u64_vec() .unwrap() } fn assert_tag_i64(&mut self, tag: u16) -> i64 { - self.get_tag(Tag::Unknown(tag)).unwrap().into_i64().unwrap() + self.current_ifd() + .get_tag(Tag::Unknown(tag)) + .unwrap() + .into_i64() + .unwrap() } fn assert_tag_i64_vec(&mut self, tag: u16) -> Vec { - self.get_tag(Tag::Unknown(tag)) + self.current_ifd() + .get_tag(Tag::Unknown(tag)) .unwrap() .into_i64_vec() .unwrap() @@ -776,7 +798,13 @@ fn test_rows_per_strip() { { let mut decoder = Decoder::open(&mut file).unwrap(); decoder.next_image().unwrap(); - assert_eq!(decoder.get_tag_u64(Tag::RowsPerStrip).unwrap(), 2); + assert_eq!( + decoder + .current_ifd() + .get_tag_u64(Tag::RowsPerStrip) + .unwrap(), + 2 + ); assert_eq!(decoder.strip_count().unwrap(), 50); for i in 0..50 { @@ -829,6 +857,7 @@ fn test_auxiliary_directory() { let _ = decoder.read_image().unwrap(); let exif_location = decoder + .current_ifd() .get_tag(Tag::ExifDirectory) .expect("second directory missing Exif") .into_ifd_pointer()