From 840f9b6673785c29012cd597c9a258ae7753d0c3 Mon Sep 17 00:00:00 2001 From: Gunnar Schulze Date: Wed, 20 Nov 2024 16:44:03 +0100 Subject: [PATCH 1/4] Add support for iterating over all tags --- Cargo.toml | 1 + src/decoder/mod.rs | 25 ++++++++++--- src/decoder/tag_iter.rs | 52 ++++++++++++++++++++++++++ tests/encode_images.rs | 82 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 src/decoder/tag_iter.rs diff --git a/Cargo.toml b/Cargo.toml index baf432fd..ab14f0f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ categories = ["multimedia::images", "multimedia::encoding"] exclude = ["tests/images/*", "tests/fuzz_images/*"] [dependencies] +either = "1.13.0" weezl = "0.1.0" jpeg = { package = "jpeg-decoder", version = "0.3.0", default-features = false } flate2 = "1.0.20" diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 8b5ffbcf..6524f309 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -1,22 +1,25 @@ use std::collections::{HashMap, HashSet}; use std::io::{self, Read, Seek}; -use crate::{ - bytecast, ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError, -}; +use either::Either; -use self::ifd::Directory; -use self::image::Image; +use crate::decoder::tag_iter::TagIter; use crate::tags::{ CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, Tag, Type, }; +use crate::{ + bytecast, ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError, +}; +use self::ifd::Directory; +use self::image::Image; use self::stream::{ByteOrder, EndianReader, SmartReader}; pub mod ifd; mod image; mod stream; +mod tag_iter; mod tag_reader; /// Result of a decoding process @@ -895,6 +898,18 @@ impl Decoder { self.get_tag(tag)?.into_string() } + pub fn tag_iter(&mut self) -> impl Iterator> + '_ { + match self.image().ifd.as_ref() { + None => Either::Left(std::iter::empty()), + Some(ifd) => Either::Right(TagIter::new( + ifd.clone(), + &self.limits, + self.bigtiff, + &mut self.reader, + )), + } + } + fn check_chunk_type(&self, expected: ChunkType) -> TiffResult<()> { if expected != self.image().chunk_type { return Err(TiffError::UsageError(UsageError::InvalidChunkType( diff --git a/src/decoder/tag_iter.rs b/src/decoder/tag_iter.rs new file mode 100644 index 00000000..687c5ff1 --- /dev/null +++ b/src/decoder/tag_iter.rs @@ -0,0 +1,52 @@ +use std::collections::hash_map::IntoIter; +use std::io::{Read, Seek}; + +use crate::decoder::ifd::{Directory, Value}; +use crate::decoder::stream::SmartReader; +use crate::decoder::{ifd, Limits}; +use crate::tags::Tag; +use crate::TiffResult; + +pub(crate) struct TagIter<'a, R> +where + R: Read + Seek, +{ + iter: IntoIter, + limits: &'a Limits, + bigtiff: bool, + reader: &'a mut SmartReader, +} + +impl<'a, R> TagIter<'a, R> +where + R: Read + Seek, +{ + pub fn new( + directory: Directory, + limits: &'a Limits, + bigtiff: bool, + reader: &'a mut SmartReader, + ) -> Self { + Self { + iter: directory.into_iter(), + limits, + bigtiff, + reader, + } + } +} + +impl<'a, R> Iterator for TagIter<'a, R> +where + R: Read + Seek, +{ + type Item = TiffResult<(Tag, Value)>; + + fn next(&mut self) -> Option { + self.iter.next().map(|(tag, entry)| { + entry + .val(self.limits, self.bigtiff, self.reader) + .map(|value| (tag, value)) + }) + } +} diff --git a/tests/encode_images.rs b/tests/encode_images.rs index 532ed21c..a585b3de 100644 --- a/tests/encode_images.rs +++ b/tests/encode_images.rs @@ -36,6 +36,47 @@ fn encode_decode() { let mut decoder = Decoder::new(&mut file).unwrap(); assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8)); assert_eq!(decoder.dimensions().unwrap(), (100, 100)); + + let mut all_tags = decoder + .tag_iter() + .filter_map(Result::ok) + .collect::>(); + all_tags.sort_by(|(t1, _), (t2, _)| t1.to_u16().cmp(&t2.to_u16())); + assert_eq!( + all_tags, + vec![ + (Tag::ImageWidth, ifd::Value::Unsigned(100)), + (Tag::ImageLength, ifd::Value::Unsigned(100)), + ( + Tag::BitsPerSample, + ifd::Value::List(vec![ + ifd::Value::UnsignedBig(8), + ifd::Value::UnsignedBig(8), + ifd::Value::UnsignedBig(8) + ]) + ), + (Tag::Compression, ifd::Value::Unsigned(1)), + (Tag::PhotometricInterpretation, ifd::Value::Unsigned(2)), + (Tag::StripOffsets, ifd::Value::Unsigned(8)), + (Tag::SamplesPerPixel, ifd::Value::Unsigned(3)), + (Tag::RowsPerStrip, ifd::Value::Unsigned(3334)), + (Tag::StripByteCounts, ifd::Value::Unsigned(30000)), + (Tag::XResolution, ifd::Value::Rational(1, 1)), + (Tag::YResolution, ifd::Value::Rational(1, 1)), + (Tag::ResolutionUnit, ifd::Value::Unsigned(1)), + (Tag::Artist, ifd::Value::Ascii("Image-tiff".into())), + (Tag::Predictor, ifd::Value::Unsigned(1)), + ( + Tag::SampleFormat, + ifd::Value::List(vec![ + ifd::Value::UnsignedBig(1), + ifd::Value::UnsignedBig(1), + ifd::Value::UnsignedBig(1) + ]) + ), + ] + ); + assert_eq!( decoder.get_tag(Tag::Artist).unwrap(), ifd::Value::Ascii("Image-tiff".into()) @@ -75,6 +116,47 @@ fn encode_decode_big() { let mut decoder = Decoder::new(&mut file).unwrap(); assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8)); assert_eq!(decoder.dimensions().unwrap(), (100, 100)); + + let mut all_tags = decoder + .tag_iter() + .filter_map(Result::ok) + .collect::>(); + all_tags.sort_by(|(t1, _), (t2, _)| t1.to_u16().cmp(&t2.to_u16())); + assert_eq!( + all_tags, + vec![ + (Tag::ImageWidth, ifd::Value::Unsigned(100)), + (Tag::ImageLength, ifd::Value::Unsigned(100)), + ( + Tag::BitsPerSample, + ifd::Value::List(vec![ + ifd::Value::Short(8), + ifd::Value::Short(8), + ifd::Value::Short(8) + ]) + ), + (Tag::Compression, ifd::Value::Unsigned(1)), + (Tag::PhotometricInterpretation, ifd::Value::Unsigned(2)), + (Tag::StripOffsets, ifd::Value::UnsignedBig(16)), + (Tag::SamplesPerPixel, ifd::Value::Unsigned(3)), + (Tag::RowsPerStrip, ifd::Value::Unsigned(3334)), + (Tag::StripByteCounts, ifd::Value::UnsignedBig(30000)), + (Tag::XResolution, ifd::Value::Rational(1, 1)), + (Tag::YResolution, ifd::Value::Rational(1, 1)), + (Tag::ResolutionUnit, ifd::Value::Unsigned(1)), + (Tag::Artist, ifd::Value::Ascii("Image-tiff".into())), + (Tag::Predictor, ifd::Value::Unsigned(1)), + ( + Tag::SampleFormat, + ifd::Value::List(vec![ + ifd::Value::Short(1), + ifd::Value::Short(1), + ifd::Value::Short(1) + ]) + ), + ] + ); + assert_eq!( decoder.get_tag(Tag::Artist).unwrap(), ifd::Value::Ascii("Image-tiff".into()) From 395a755b74381ea403278161abce77deb68d998d Mon Sep 17 00:00:00 2001 From: Gunnar Schulze Date: Sun, 24 Nov 2024 20:09:09 +0100 Subject: [PATCH 2/4] Use 'sort_by_key' for sorting tags in tests --- tests/encode_images.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/encode_images.rs b/tests/encode_images.rs index a585b3de..ee1e3c0d 100644 --- a/tests/encode_images.rs +++ b/tests/encode_images.rs @@ -41,7 +41,7 @@ fn encode_decode() { .tag_iter() .filter_map(Result::ok) .collect::>(); - all_tags.sort_by(|(t1, _), (t2, _)| t1.to_u16().cmp(&t2.to_u16())); + all_tags.sort_by_key(|(t, _)| t.to_u16()); assert_eq!( all_tags, vec![ @@ -121,7 +121,7 @@ fn encode_decode_big() { .tag_iter() .filter_map(Result::ok) .collect::>(); - all_tags.sort_by(|(t1, _), (t2, _)| t1.to_u16().cmp(&t2.to_u16())); + all_tags.sort_by_key(|(t, _)| t.to_u16()); assert_eq!( all_tags, vec![ From 0670fbba973c57bef088feb1409010f8eb1617be Mon Sep 17 00:00:00 2001 From: Gunnar Schulze Date: Sun, 24 Nov 2024 22:16:55 +0100 Subject: [PATCH 3/4] Simplify implementation of 'tag_iter()' --- Cargo.toml | 1 - src/decoder/mod.rs | 18 ++++---------- src/decoder/tag_iter.rs | 52 ----------------------------------------- 3 files changed, 5 insertions(+), 66 deletions(-) delete mode 100644 src/decoder/tag_iter.rs diff --git a/Cargo.toml b/Cargo.toml index ab14f0f3..baf432fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,6 @@ categories = ["multimedia::images", "multimedia::encoding"] exclude = ["tests/images/*", "tests/fuzz_images/*"] [dependencies] -either = "1.13.0" weezl = "0.1.0" jpeg = { package = "jpeg-decoder", version = "0.3.0", default-features = false } flate2 = "1.0.20" diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 6524f309..90c2b80a 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -1,9 +1,6 @@ use std::collections::{HashMap, HashSet}; use std::io::{self, Read, Seek}; -use either::Either; - -use crate::decoder::tag_iter::TagIter; use crate::tags::{ CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, Tag, Type, @@ -19,7 +16,6 @@ use self::stream::{ByteOrder, EndianReader, SmartReader}; pub mod ifd; mod image; mod stream; -mod tag_iter; mod tag_reader; /// Result of a decoding process @@ -899,15 +895,11 @@ impl Decoder { } pub fn tag_iter(&mut self) -> impl Iterator> + '_ { - match self.image().ifd.as_ref() { - None => Either::Left(std::iter::empty()), - Some(ifd) => Either::Right(TagIter::new( - ifd.clone(), - &self.limits, - self.bigtiff, - &mut self.reader, - )), - } + self.image.ifd.as_ref().unwrap().iter().map(|(tag, entry)| { + entry + .val(&self.limits, self.bigtiff, &mut self.reader) + .map(|value| (*tag, value)) + }) } fn check_chunk_type(&self, expected: ChunkType) -> TiffResult<()> { diff --git a/src/decoder/tag_iter.rs b/src/decoder/tag_iter.rs deleted file mode 100644 index 687c5ff1..00000000 --- a/src/decoder/tag_iter.rs +++ /dev/null @@ -1,52 +0,0 @@ -use std::collections::hash_map::IntoIter; -use std::io::{Read, Seek}; - -use crate::decoder::ifd::{Directory, Value}; -use crate::decoder::stream::SmartReader; -use crate::decoder::{ifd, Limits}; -use crate::tags::Tag; -use crate::TiffResult; - -pub(crate) struct TagIter<'a, R> -where - R: Read + Seek, -{ - iter: IntoIter, - limits: &'a Limits, - bigtiff: bool, - reader: &'a mut SmartReader, -} - -impl<'a, R> TagIter<'a, R> -where - R: Read + Seek, -{ - pub fn new( - directory: Directory, - limits: &'a Limits, - bigtiff: bool, - reader: &'a mut SmartReader, - ) -> Self { - Self { - iter: directory.into_iter(), - limits, - bigtiff, - reader, - } - } -} - -impl<'a, R> Iterator for TagIter<'a, R> -where - R: Read + Seek, -{ - type Item = TiffResult<(Tag, Value)>; - - fn next(&mut self) -> Option { - self.iter.next().map(|(tag, entry)| { - entry - .val(self.limits, self.bigtiff, self.reader) - .map(|value| (tag, value)) - }) - } -} From fd2bd310228bab364e0786886621957a83d51f1e Mon Sep 17 00:00:00 2001 From: Gunnar Schulze Date: Mon, 25 Nov 2024 08:38:07 +0100 Subject: [PATCH 4/4] Document 'tag_iter()' method --- src/decoder/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 90c2b80a..2b8b0dd2 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -894,6 +894,7 @@ impl Decoder { self.get_tag(tag)?.into_string() } + /// Returns an iterator over all tags in the current image, along with their values. pub fn tag_iter(&mut self) -> impl Iterator> + '_ { self.image.ifd.as_ref().unwrap().iter().map(|(tag, entry)| { entry