Skip to content

Commit 194160c

Browse files
committed
added doc example for exif, exif sample tiff and some sad error handling in ifd::from_tags
1 parent 06ebb91 commit 194160c

File tree

6 files changed

+214
-11
lines changed

6 files changed

+214
-11
lines changed

src/ifd.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use num_enum::TryFromPrimitive;
77

88
use crate::error::{AsyncTiffError, AsyncTiffResult};
99
use crate::geo::{GeoKeyDirectory, GeoKeyTag};
10-
use crate::metadata::ExtraTagsRegistry;
10+
use crate::metadata::extra_tags::ExtraTagsRegistry;
1111
use crate::predictor::PredictorInfo;
1212
use crate::reader::{AsyncFileReader, Endianness};
1313
use crate::tiff::tags::{
@@ -269,11 +269,17 @@ impl ImageFileDirectory {
269269
Tag::Unknown(DOCUMENT_NAME) => document_name = Some(value.into_string()?),
270270
t => {
271271
if extra_tags_registry.contains(&t) {
272-
extra_tags_registry[&t].process_tag(t, value);
272+
extra_tags_registry[&t].process_tag(t, value).map_err(|e| {
273+
if let AsyncTiffError::InternalTIFFError(err) = e {
274+
err
275+
} else {
276+
// TODO fix error handling. This is bad
277+
TiffError::IntSizeError
278+
}
279+
})?;
273280
} else {
274281
other_tags.insert(tag, value);
275282
}
276-
277283
}
278284
};
279285
Ok::<_, TiffError>(())
@@ -645,6 +651,11 @@ impl ImageFileDirectory {
645651
self.model_tiepoint.as_deref()
646652
}
647653

654+
/// the registry holding extra tags
655+
pub fn extra_tags(&self) -> &ExtraTagsRegistry {
656+
&self.extra_tags
657+
}
658+
648659
/// Tags for which the tiff crate doesn't have a hard-coded enum variant.
649660
pub fn other_tags(&self) -> &HashMap<Tag, Value> {
650661
&self.other_tags

src/metadata/extra_tags.rs

Lines changed: 196 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,81 @@
1+
//! # Register parsers for additional tags
2+
//!
3+
//! Simplified example for exif tags parser
4+
//!
5+
//! ```
6+
//! # use std::sync::{LazyLock, OnceLock, Arc};
7+
//! # use std::env::current_dir;
8+
//! # use async_tiff::tiff::{Value, tags::Tag};
9+
//! # use async_tiff::error::AsyncTiffResult;
10+
//! # use async_tiff::reader::{ObjectReader, AsyncFileReader};
11+
//! # use async_tiff::metadata::TiffMetadataReader;
12+
//! use async_tiff::metadata::extra_tags::{ExtraTags, ExtraTagsRegistry};
13+
//! # use object_store::local::LocalFileSystem;
14+
//! // see https://www.media.mit.edu/pia/Research/deepview/exif.html#ExifTags
15+
//! // or exif spec: https://www.cipa.jp/std/documents/download_e.html?DC-008-Translation-2023-E
16+
//! // / all tags processed by your extension
17+
//! pub const EXIF_TAGS: [Tag; 3] = [
18+
//! Tag::Unknown(34665), // Exif IFD pointer
19+
//! Tag::Unknown(34853), // GPS IFD pointer
20+
//! Tag::Unknown(40965), // Interoperability IFD pointer
21+
//! ];
22+
//!
23+
//! // / the struct that stores the data (using interior mutability)
24+
//! #[derive(Debug, Clone, Default)]
25+
//! pub struct ExifTags {
26+
//! pub exif: OnceLock<u32>,
27+
//! pub gps: OnceLock<u32>,
28+
//! pub interop: OnceLock<u32>,
29+
//! // would also hold e.g. a TiffMetadataReader to read exif IFDs
30+
//! }
31+
//!
32+
//! impl ExtraTags for ExifTags {
33+
//! fn tags(&self) -> &'static [Tag] {
34+
//! &EXIF_TAGS
35+
//! }
36+
//!
37+
//! fn process_tag(&self, tag:Tag, value: Value) -> AsyncTiffResult<()> {
38+
//! match tag {
39+
//! Tag::Unknown(34665) => self.exif.set(value.into_u32()?).unwrap(),
40+
//! Tag::Unknown(34853) => self.gps.set(value.into_u32()?).unwrap(),
41+
//! Tag::Unknown(40965) => self.interop.set(value.into_u32()?).unwrap(),
42+
//! _ => {}
43+
//! }
44+
//! Ok(())
45+
//! }
46+
//! }
47+
//!
48+
//! #[tokio::main]
49+
//! async fn main() {
50+
//! // create an empty registry
51+
//! let mut registry = ExtraTagsRegistry::new();
52+
//! // register our custom extra tags
53+
//! registry.register(Arc::new(ExifTags::default()));
54+
//!
55+
//! let store = Arc::new(LocalFileSystem::new_with_prefix(current_dir().unwrap()).unwrap());
56+
//! let path = "tests/sample-exif.tiff";
57+
//! let reader =
58+
//! Arc::new(ObjectReader::new(store.clone(), path.into())) as Arc<dyn AsyncFileReader>;
59+
//! let mut metadata_reader = TiffMetadataReader::try_open(&reader).await.unwrap();
60+
//! // get the first ifd
61+
//! let ifd = &metadata_reader
62+
//! .read_all_ifds(&reader, registry)
63+
//! .await
64+
//! .unwrap()[0];
65+
//!
66+
//! // access by any of our registered tags
67+
//! let exif = ifd.extra_tags()[&EXIF_TAGS[0]]
68+
//! .clone()
69+
//! .as_any_arc()
70+
//! .downcast::<ExifTags>()
71+
//! .unwrap();
72+
//! assert!(exif.exif.get().is_some());
73+
//! assert!(exif.gps.get().is_some());
74+
//! // our image doesn't have interop info
75+
//! assert!(exif.interop.get().is_none());
76+
//! }
77+
//! ```
78+
179
use crate::error::{AsyncTiffError, AsyncTiffResult};
280
use crate::tiff::tags::Tag;
381
use crate::tiff::Value;
@@ -8,21 +86,105 @@ use std::ops::Index;
886
use std::sync::Arc;
987

1088
/// Trait to implement for custom tags, such as Geo, EXIF, OME, etc
11-
/// your type should also implement `Clone`
89+
///
90+
/// your type should also implement `Clone` for blanket implementations of [`ExtraTagsBlankets`]
91+
///
92+
/// ```
93+
/// # use async_tiff::tiff::{Value, tags::Tag};
94+
/// # use async_tiff::error::AsyncTiffResult;
95+
/// use async_tiff::metadata::extra_tags::ExtraTags;
96+
/// # use std::sync::OnceLock;
97+
///
98+
/// pub const EXIF_TAGS: [Tag; 3] = [
99+
/// Tag::Unknown(34665), // Exif IFD pointer
100+
/// Tag::Unknown(34853), // GPS IFD pointer
101+
/// Tag::Unknown(40965), // Interoperability IFD pointer
102+
/// ];
103+
///
104+
/// // / the struct that stores the data (using interior mutability)
105+
/// #[derive(Debug, Clone, Default)]
106+
/// pub struct ExifTags {
107+
/// pub exif: OnceLock<u32>,
108+
/// pub gps: OnceLock<u32>,
109+
/// pub interop: OnceLock<u32>,
110+
/// // would also hold e.g. a TiffMetadataReader to read exif IFDs
111+
/// }
112+
///
113+
/// impl ExtraTags for ExifTags {
114+
/// fn tags(&self) -> &'static [Tag] {
115+
/// &EXIF_TAGS
116+
/// }
117+
///
118+
/// fn process_tag(&self, tag:Tag, value: Value) -> AsyncTiffResult<()> {
119+
/// match tag {
120+
/// Tag::Unknown(34665) => self.exif.set(value.into_u32()?).unwrap(),
121+
/// Tag::Unknown(34853) => self.gps.set(value.into_u32()?).unwrap(),
122+
/// Tag::Unknown(40965) => self.interop.set(value.into_u32()?).unwrap(),
123+
/// _ => {}
124+
/// }
125+
/// Ok(())
126+
/// }
127+
/// }
128+
/// ```
12129
// Send + Sync are required for Python, where `dyn ExtraTags` needs `Send` and `Sync`
13130
pub trait ExtraTags: ExtraTagsBlankets + Any + Debug + Send + Sync {
14131
/// a list of tags this entry processes
132+
///
15133
/// e.g. for Geo this would be [34735, 34736, 34737]
16134
fn tags(&self) -> &'static [Tag];
17135
/// process a single tag, using internal mutability if needed
18136
fn process_tag(&self, tag: Tag, value: Value) -> AsyncTiffResult<()>;
19137
}
20138

21-
// we need to do a little dance to do an object-safe deep clone
22-
// https://stackoverflow.com/a/30353928/14681457
23-
// also object-safe type conversions for downcasting
139+
/// Extra trait with blanket implementations for object-safe cloning and casting
140+
///
141+
/// Automatically implemented if your type implements [`ExtraTags`] and [`Clone`]
142+
///
143+
/// ```
144+
/// # use std::sync::Arc;
145+
/// # use async_tiff::tiff::{Value, tags::Tag};
146+
/// # use async_tiff::error::AsyncTiffResult;
147+
/// use async_tiff::metadata::extra_tags::ExtraTags;
148+
/// // derive these
149+
/// #[derive(Debug, Clone)]
150+
/// pub struct MyTags;
151+
///
152+
/// // custom functionality
153+
/// impl MyTags {
154+
/// fn forty_two(&self) -> u32 {42}
155+
/// }
156+
///
157+
/// // implement ExtraTags
158+
/// impl ExtraTags for MyTags {
159+
/// fn tags(&self) -> &'static [Tag] {
160+
/// &[]
161+
/// }
162+
///
163+
/// fn process_tag(&self, _tag:Tag, _value:Value) -> AsyncTiffResult<()> {
164+
/// Ok(())
165+
/// }
166+
/// }
167+
///
168+
/// fn main() {
169+
/// // allows for deep cloning
170+
/// let my_tags = Arc::new(MyTags) as Arc<dyn ExtraTags>;
171+
/// let other_my_tags = my_tags.clone_arc();
172+
/// assert!(Arc::ptr_eq(&my_tags, &my_tags.clone()));
173+
/// assert!(!Arc::ptr_eq(&my_tags, &other_my_tags));
174+
///
175+
/// // and downcasting
176+
/// let my_tags_concrete = my_tags.as_any_arc().downcast::<MyTags>().unwrap();
177+
/// assert_eq!(my_tags_concrete.forty_two(), 42);
178+
/// }
179+
/// ```
180+
///
181+
/// This works since blanket implementations are done on concrete types and only
182+
/// their signatures (function pointer) will end up in the vtable
183+
/// <https://stackoverflow.com/a/30353928/14681457>
24184
pub trait ExtraTagsBlankets {
185+
/// deep clone
25186
fn clone_arc(&self) -> Arc<dyn ExtraTags>;
187+
/// convert to any for downcasting
26188
fn as_any_arc(self: Arc<Self>) -> Arc<dyn Any + Send + Sync>;
27189
}
28190

@@ -40,7 +202,36 @@ where
40202
}
41203

42204
/// The registry in which extra tags (parsers) are registered
43-
/// This is passed to TODO
205+
///
206+
/// Pass this to [`crate::metadata::TiffMetadataReader`] when reading.
207+
///
208+
/// ```
209+
/// # use async_tiff::reader::{AsyncFileReader, ObjectReader};
210+
/// # use async_tiff::metadata::TiffMetadataReader;
211+
/// use async_tiff::metadata::extra_tags::ExtraTagsRegistry;
212+
/// # use std::sync::Arc;
213+
/// # use std::env::current_dir;
214+
/// # use object_store::local::LocalFileSystem;
215+
///
216+
/// #[tokio::main]
217+
/// async fn main() {
218+
/// let registry = ExtraTagsRegistry::default();
219+
///
220+
/// let store = Arc::new(LocalFileSystem::new_with_prefix(current_dir().unwrap()).unwrap());
221+
/// # let path = "tests/sample-exif.tiff";
222+
/// let reader =
223+
/// Arc::new(ObjectReader::new(store.clone(), path.into())) as Arc<dyn AsyncFileReader>;
224+
/// let mut metadata_reader = TiffMetadataReader::try_open(&reader).await.unwrap();
225+
/// // get first ifd
226+
/// let ifd = &metadata_reader
227+
/// .read_all_ifds(&reader, registry)
228+
/// .await
229+
/// .unwrap()[0];
230+
/// // retrieve the registry
231+
/// println!("{:?}",ifd.extra_tags());
232+
/// }
233+
/// ```
234+
///
44235
#[derive(Debug, Clone)]
45236
pub struct ExtraTagsRegistry(HashMap<Tag, Arc<dyn ExtraTags>>);
46237

src/metadata/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,10 @@
5858
//! fetches the first `N` bytes out of a file.
5959
//!
6060
61-
mod extra_tags;
61+
pub mod extra_tags;
6262
mod fetch;
6363
mod reader;
6464

65-
pub use extra_tags::{ExtraTags, ExtraTagsRegistry};
65+
// pub use extra_tags::{ExtraTags, ExtraTagsRegistry};
6666
pub use fetch::{MetadataFetch, PrefetchBuffer};
6767
pub use reader::{ImageFileDirectoryReader, TiffMetadataReader};

src/metadata/reader.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use bytes::Bytes;
55

66
use crate::error::{AsyncTiffError, AsyncTiffResult};
77
use crate::metadata::fetch::MetadataCursor;
8-
use crate::metadata::ExtraTagsRegistry;
8+
use crate::metadata::extra_tags::ExtraTagsRegistry;
99
use crate::metadata::MetadataFetch;
1010
use crate::reader::Endianness;
1111
use crate::tiff::tags::{Tag, Type};

src/predictor.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ pub(crate) fn unpredict_hdiff(
168168
// From image-tiff
169169
///
170170
/// This should be used _after_ endianness fixing
171+
///
171172
pub fn rev_hpredict_nsamp(buf: &mut [u8], bit_depth: u16, samples: usize) {
172173
match bit_depth {
173174
0..=8 => {

tests/sample-exif.tiff

257 KB
Binary file not shown.

0 commit comments

Comments
 (0)