Skip to content

Commit 82a3d4d

Browse files
authored
Show EXIF data for JPEG/TIFF images (#9153)
When selecting an JPEG or TIFF image, we will show its EXIF contents in the selection panel <img width="341" alt="Screenshot 2025-02-27 at 11 15 46" src="https://github.com/user-attachments/assets/23840ec6-e653-490a-a5de-8d7ec7df2d8d" />
1 parent 74ad285 commit 82a3d4d

File tree

4 files changed

+60
-2
lines changed

4 files changed

+60
-2
lines changed

Cargo.lock

+7
Original file line numberDiff line numberDiff line change
@@ -6037,6 +6037,7 @@ dependencies = [
60376037
"re_ui",
60386038
"re_video",
60396039
"re_viewer_context",
6040+
"rexif",
60406041
"unindent",
60416042
]
60426043

@@ -7644,6 +7645,12 @@ dependencies = [
76447645
"usvg",
76457646
]
76467647

7648+
[[package]]
7649+
name = "rexif"
7650+
version = "0.7.5"
7651+
source = "registry+https://github.com/rust-lang/crates.io-index"
7652+
checksum = "be932047c168919c8d5af065b16fa7d4bd24835ffa256bf0cf1ff463f91c15df"
7653+
76477654
[[package]]
76487655
name = "rfd"
76497656
version = "0.15.0"

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ quote = "1.0"
258258
rand = { version = "0.8", default-features = false }
259259
rand_distr = { version = "0.4", default-features = false }
260260
rayon = "1.7"
261+
rexif = "0.7.5"
261262
rfd = { version = "0.15", default-features = false, features = [
262263
"async-std",
263264
"xdg-portal",

crates/viewer/re_data_ui/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,5 @@ egui.workspace = true
4646
image.workspace = true
4747
itertools.workspace = true
4848
nohash-hasher.workspace = true
49+
rexif.workspace = true
4950
unindent.workspace = true

crates/viewer/re_data_ui/src/blob.rs

+51-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1-
use re_types::components::{Blob, MediaType, VideoTimestamp};
2-
use re_ui::{list_item::PropertyContent, UiExt};
1+
use std::sync::Arc;
2+
3+
use re_types::{
4+
components::{Blob, MediaType, VideoTimestamp},
5+
RowId,
6+
};
7+
use re_ui::{
8+
list_item::{self, PropertyContent},
9+
UiExt,
10+
};
311
use re_viewer_context::UiLayout;
412

513
use crate::{
@@ -108,6 +116,10 @@ pub fn blob_preview_and_save_ui(
108116
let mut video_result_for_frame_preview = None;
109117

110118
if let Some(blob_row_id) = blob_row_id {
119+
if !ui_layout.is_single_line() && ui_layout != UiLayout::Tooltip {
120+
exif_ui(ui, blob_row_id, blob);
121+
}
122+
111123
// Try to treat it as an image:
112124
image = ctx
113125
.store_context
@@ -197,3 +209,40 @@ pub fn blob_preview_and_save_ui(
197209
}
198210
}
199211
}
212+
213+
/// Show EXIF data about the given blob (image), if possible.
214+
fn exif_ui(ui: &mut egui::Ui, blob_row_id: RowId, blob: &re_types::datatypes::Blob) {
215+
let exif_result = ui.ctx().memory_mut(|mem| {
216+
// Cache EXIF parsing to avoid re-parsing every frame.
217+
// The parsing is really fast, so this is not really needed.
218+
let key = blob_row_id;
219+
let cache = mem
220+
.caches
221+
.cache::<egui::cache::FramePublisher<RowId, Arc<rexif::ExifResult>>>();
222+
cache.get(&key).cloned().unwrap_or_else(|| {
223+
re_tracing::profile_scope!("exif-parse");
224+
let (result, _warnings) = rexif::parse_buffer_quiet(blob);
225+
let result = Arc::new(result);
226+
cache.set(key, result.clone());
227+
result
228+
})
229+
});
230+
231+
if let Ok(exif) = &*exif_result {
232+
ui.list_item_collapsible_noninteractive_label("EXIF", false, |ui| {
233+
list_item::list_item_scope(ui, "exif", |ui| {
234+
for entry in &exif.entries {
235+
let tag_string = if entry.tag == rexif::ExifTag::UnknownToMe {
236+
"<Unknown tag>".to_owned()
237+
} else {
238+
entry.tag.to_string()
239+
};
240+
ui.list_item_flat_noninteractive(
241+
list_item::PropertyContent::new(tag_string)
242+
.value_text(entry.value_more_readable.to_string()),
243+
);
244+
}
245+
});
246+
});
247+
}
248+
}

0 commit comments

Comments
 (0)