1
- use std:: sync:: Arc ;
2
-
1
+ use crate :: image:: ImageUi ;
2
+ use crate :: video:: VideoUi ;
3
+ use crate :: { EntityDataUi , find_and_deserialize_archetype_mono_component} ;
4
+ use re_chunk_store:: UnitChunkShared ;
3
5
use re_log_types:: EntityPath ;
4
6
use re_types:: {
5
- ComponentDescriptor , RowId ,
7
+ ComponentDescriptor , RowId , archetypes , components ,
6
8
components:: { Blob , MediaType , VideoTimestamp } ,
7
9
} ;
10
+ use re_types_core:: Component as _;
11
+ use re_ui:: list_item:: ListItemContentButtonsExt as _;
8
12
use re_ui:: {
9
13
UiExt as _, icons,
10
14
list_item:: { self , PropertyContent } ,
11
15
} ;
12
16
use re_viewer_context:: { StoredBlobCacheKey , UiLayout , ViewerContext } ;
13
-
14
- use crate :: {
15
- EntityDataUi ,
16
- image:: image_preview_ui,
17
- video:: { show_decoded_frame_info, video_asset_result_ui} ,
18
- } ;
17
+ use std:: sync:: Arc ;
19
18
20
19
impl EntityDataUi for Blob {
21
20
fn entity_data_ui (
@@ -39,20 +38,20 @@ impl EntityDataUi for Blob {
39
38
// This can also help a user debug if they log the contents of `.png` file with a `image/jpeg` `MediaType`.
40
39
let media_type = MediaType :: guess_from_data ( self ) ;
41
40
41
+ let blob_ui = BlobUi :: new (
42
+ ctx,
43
+ entity_path,
44
+ component_descriptor,
45
+ row_id,
46
+ self . 0 . clone ( ) ,
47
+ media_type. as_ref ( ) ,
48
+ None ,
49
+ ) ;
50
+
42
51
if ui_layout. is_single_line ( ) {
43
52
ui. horizontal ( |ui| {
44
- blob_preview_and_save_ui (
45
- ctx,
46
- ui,
47
- ui_layout,
48
- query,
49
- entity_path,
50
- component_descriptor,
51
- row_id,
52
- self ,
53
- media_type. as_ref ( ) ,
54
- None ,
55
- ) ;
53
+ ui. set_truncate_style ( ) ;
54
+ blob_ui. data_ui ( ctx, ui, ui_layout, query, entity_path) ;
56
55
57
56
ui. label ( compact_size_string) ;
58
57
@@ -85,167 +84,12 @@ impl EntityDataUi for Blob {
85
84
)
86
85
. on_hover_text ( "Failed to detect media type (Mime) from magic header bytes" ) ;
87
86
}
88
-
89
- blob_preview_and_save_ui (
90
- ctx,
91
- ui,
92
- ui_layout,
93
- query,
94
- entity_path,
95
- component_descriptor,
96
- row_id,
97
- self ,
98
- media_type. as_ref ( ) ,
99
- None ,
100
- ) ;
87
+ blob_ui. data_ui ( ctx, ui, ui_layout, query, entity_path) ;
101
88
} ) ;
102
89
}
103
90
}
104
91
}
105
92
106
- #[ allow( clippy:: too_many_arguments) ]
107
- pub fn blob_preview_and_save_ui (
108
- ctx : & re_viewer_context:: ViewerContext < ' _ > ,
109
- ui : & mut egui:: Ui ,
110
- ui_layout : UiLayout ,
111
- query : & re_chunk_store:: LatestAtQuery ,
112
- entity_path : & re_log_types:: EntityPath ,
113
- blob_component_descriptor : & ComponentDescriptor ,
114
- blob_row_id : Option < RowId > ,
115
- blob : & re_types:: datatypes:: Blob ,
116
- media_type : Option < & MediaType > ,
117
- video_timestamp : Option < VideoTimestamp > ,
118
- ) {
119
- #[ allow( unused_assignments) ] // Not used when targeting web.
120
- let mut image = None ;
121
- let mut video_result_for_frame_preview = None ;
122
-
123
- if let Some ( blob_row_id) = blob_row_id {
124
- if !ui_layout. is_single_line ( ) && ui_layout != UiLayout :: Tooltip {
125
- exif_ui (
126
- ui,
127
- StoredBlobCacheKey :: new ( blob_row_id, blob_component_descriptor) ,
128
- blob,
129
- ) ;
130
- }
131
-
132
- // Try to treat it as an image:
133
- image = ctx
134
- . store_context
135
- . caches
136
- . entry ( |c : & mut re_viewer_context:: ImageDecodeCache | {
137
- c. entry ( blob_row_id, blob_component_descriptor, blob, media_type)
138
- } )
139
- . ok ( ) ;
140
-
141
- if let Some ( image) = & image {
142
- if !ui_layout. is_single_line ( ) {
143
- ui. list_item_flat_noninteractive (
144
- PropertyContent :: new ( "Image format" ) . value_text ( image. format . to_string ( ) ) ,
145
- ) ;
146
- }
147
-
148
- let colormap = None ; // TODO(andreas): Rely on default here for now.
149
- image_preview_ui ( ctx, ui, ui_layout, query, entity_path, image, colormap) ;
150
- } else {
151
- // Try to treat it as a video.
152
- let video_result =
153
- ctx. store_context
154
- . caches
155
- . entry ( |c : & mut re_viewer_context:: VideoAssetCache | {
156
- let debug_name = entity_path. to_string ( ) ;
157
- c. entry (
158
- debug_name,
159
- blob_row_id,
160
- blob_component_descriptor,
161
- blob,
162
- media_type,
163
- ctx. app_options ( ) . video_decoder_settings ( ) ,
164
- )
165
- } ) ;
166
- video_asset_result_ui ( ui, ui_layout, & video_result) ;
167
- video_result_for_frame_preview = Some ( video_result) ;
168
- }
169
- }
170
-
171
- if !ui_layout. is_single_line ( ) && ui_layout != UiLayout :: Tooltip {
172
- ui. horizontal ( |ui| {
173
- let text = if cfg ! ( target_arch = "wasm32" ) {
174
- "Download blob…"
175
- } else {
176
- "Save blob…"
177
- } ;
178
- if ui
179
- . add ( egui:: Button :: image_and_text (
180
- icons:: DOWNLOAD . as_image ( ) ,
181
- text,
182
- ) )
183
- . clicked ( )
184
- {
185
- let mut file_name = entity_path
186
- . last ( )
187
- . map_or ( "blob" , |name| name. unescaped_str ( ) )
188
- . to_owned ( ) ;
189
-
190
- if let Some ( file_extension) = media_type. as_ref ( ) . and_then ( |mt| mt. file_extension ( ) )
191
- {
192
- file_name. push ( '.' ) ;
193
- file_name. push_str ( file_extension) ;
194
- }
195
-
196
- ctx. command_sender ( ) . save_file_dialog (
197
- re_capabilities:: MainThreadToken :: from_egui_ui ( ui) ,
198
- & file_name,
199
- "Save blob" . to_owned ( ) ,
200
- blob. to_vec ( ) ,
201
- ) ;
202
- }
203
-
204
- if let Some ( image) = image {
205
- let image_stats = ctx
206
- . store_context
207
- . caches
208
- . entry ( |c : & mut re_viewer_context:: ImageStatsCache | c. entry ( & image) ) ;
209
- let data_range = re_viewer_context:: gpu_bridge:: image_data_range_heuristic (
210
- & image_stats,
211
- & image. format ,
212
- ) ;
213
- crate :: image:: copy_image_button_ui ( ui, & image, data_range) ;
214
- }
215
- } ) ;
216
-
217
- // Show a mini video player for video blobs:
218
- if let Some ( video_result) = & video_result_for_frame_preview
219
- && let Ok ( video) = video_result. as_ref ( )
220
- {
221
- ui. separator ( ) ;
222
-
223
- let video_timestamp = video_timestamp. unwrap_or_else ( || {
224
- // TODO(emilk): Some time controls would be nice,
225
- // but the point here is not to have a nice viewer,
226
- // but to show the user what they have selected
227
- ui. ctx ( ) . request_repaint ( ) ; // TODO(emilk): schedule a repaint just in time for the next frame of video
228
- let time = ui. input ( |i| i. time ) ;
229
-
230
- if let Some ( duration) = video. data_descr ( ) . duration ( ) {
231
- VideoTimestamp :: from_secs ( time % duration. as_secs_f64 ( ) )
232
- } else {
233
- // Invalid video or unknown timescale.
234
- VideoTimestamp :: from_nanos ( 0 )
235
- }
236
- } ) ;
237
- let video_time = re_viewer_context:: video_timestamp_component_to_video_time (
238
- ctx,
239
- video_timestamp,
240
- video. data_descr ( ) . timescale ,
241
- ) ;
242
- let video_buffers = std:: iter:: once ( blob. as_ref ( ) ) . collect ( ) ;
243
-
244
- show_decoded_frame_info ( ctx, ui, ui_layout, video, video_time, & video_buffers) ;
245
- }
246
- }
247
- }
248
-
249
93
/// Show EXIF data about the given blob (image), if possible.
250
94
fn exif_ui ( ui : & mut egui:: Ui , key : StoredBlobCacheKey , blob : & re_types:: datatypes:: Blob ) {
251
95
let exif_result = ui. ctx ( ) . memory_mut ( |mem| {
@@ -281,3 +125,167 @@ fn exif_ui(ui: &mut egui::Ui, key: StoredBlobCacheKey, blob: &re_types::datatype
281
125
} ) ;
282
126
}
283
127
}
128
+
129
+ /// Utility for displaying additional UI for blobs.
130
+ pub struct BlobUi {
131
+ descr : ComponentDescriptor ,
132
+ blob : re_types:: datatypes:: Blob ,
133
+
134
+ /// Additional image ui if any.
135
+ image : Option < ImageUi > ,
136
+
137
+ /// Additional video ui if the blob is a video.
138
+ video : Option < VideoUi > ,
139
+
140
+ /// The row id of the blob.
141
+ row_id : Option < RowId > ,
142
+
143
+ /// The media type of the blob if known (used to inform image and video uis).
144
+ media_type : Option < MediaType > ,
145
+ }
146
+
147
+ impl BlobUi {
148
+ pub fn from_components (
149
+ ctx : & ViewerContext < ' _ > ,
150
+ entity_path : & re_log_types:: EntityPath ,
151
+ blob_descr : & ComponentDescriptor ,
152
+ blob_chunk : & UnitChunkShared ,
153
+ components : & [ ( ComponentDescriptor , UnitChunkShared ) ] ,
154
+ ) -> Option < Self > {
155
+ if blob_descr. component_type != Some ( components:: Blob :: name ( ) ) {
156
+ return None ;
157
+ }
158
+
159
+ let blob = blob_chunk
160
+ . component_mono :: < components:: Blob > ( blob_descr) ?
161
+ . ok ( ) ?;
162
+
163
+ // Media type comes typically alongside the blob in various different archetypes.
164
+ // Look for the one that matches the blob's archetype.
165
+ let media_type = find_and_deserialize_archetype_mono_component :: < components:: MediaType > (
166
+ components,
167
+ blob_descr. archetype ,
168
+ )
169
+ . or_else ( || components:: MediaType :: guess_from_data ( & blob) ) ;
170
+
171
+ // Video timestamp is only relevant here if it comes from a VideoFrameReference archetype.
172
+ // It doesn't show up in the blob's archetype.
173
+ let video_timestamp_descr = archetypes:: VideoFrameReference :: descriptor_timestamp ( ) ;
174
+ let video_timestamp = components
175
+ . iter ( )
176
+ . find_map ( |( descr, chunk) | {
177
+ ( descr == & video_timestamp_descr) . then ( || {
178
+ chunk
179
+ . component_mono :: < components:: VideoTimestamp > ( & video_timestamp_descr) ?
180
+ . ok ( )
181
+ } )
182
+ } )
183
+ . flatten ( ) ;
184
+
185
+ Some ( Self :: new (
186
+ ctx,
187
+ entity_path,
188
+ blob_descr,
189
+ blob_chunk. row_id ( ) ,
190
+ blob. 0 ,
191
+ media_type. as_ref ( ) ,
192
+ video_timestamp,
193
+ ) )
194
+ }
195
+
196
+ pub fn new (
197
+ ctx : & re_viewer_context:: ViewerContext < ' _ > ,
198
+ entity_path : & re_log_types:: EntityPath ,
199
+ blob_component_descriptor : & ComponentDescriptor ,
200
+ blob_row_id : Option < RowId > ,
201
+ blob : re_types:: datatypes:: Blob ,
202
+ media_type : Option < & MediaType > ,
203
+ video_timestamp : Option < VideoTimestamp > ,
204
+ ) -> Self {
205
+ let mut image = None ;
206
+ let mut video = None ;
207
+
208
+ if let Some ( blob_row_id) = blob_row_id {
209
+ image = ImageUi :: from_blob (
210
+ ctx,
211
+ blob_row_id,
212
+ blob_component_descriptor,
213
+ & blob,
214
+ media_type,
215
+ ) ;
216
+
217
+ video = VideoUi :: from_blob (
218
+ ctx,
219
+ entity_path,
220
+ blob_row_id,
221
+ blob_component_descriptor,
222
+ & blob,
223
+ media_type,
224
+ video_timestamp,
225
+ ) ;
226
+ }
227
+ Self {
228
+ image,
229
+ video,
230
+ row_id : blob_row_id,
231
+ descr : blob_component_descriptor. clone ( ) ,
232
+ blob,
233
+ media_type : media_type. cloned ( ) ,
234
+ }
235
+ }
236
+
237
+ pub fn inline_download_button < ' a > (
238
+ & ' a self ,
239
+ ctx : & ' a ViewerContext < ' _ > ,
240
+ entity_path : & ' a EntityPath ,
241
+ mut property_content : list_item:: PropertyContent < ' a > ,
242
+ ) -> list_item:: PropertyContent < ' a > {
243
+ if let Some ( image) = & self . image {
244
+ property_content = image. inline_copy_button ( ctx, property_content) ;
245
+ }
246
+ property_content. with_action_button ( & icons:: DOWNLOAD , "Save blob…" , || {
247
+ let mut file_name = entity_path
248
+ . last ( )
249
+ . map_or ( "blob" , |name| name. unescaped_str ( ) )
250
+ . to_owned ( ) ;
251
+
252
+ if let Some ( file_extension) =
253
+ self . media_type . as_ref ( ) . and_then ( |mt| mt. file_extension ( ) )
254
+ {
255
+ file_name. push ( '.' ) ;
256
+ file_name. push_str ( file_extension) ;
257
+ }
258
+
259
+ ctx. command_sender ( ) . save_file_dialog (
260
+ re_capabilities:: MainThreadToken :: i_promise_i_am_on_the_main_thread ( ) ,
261
+ & file_name,
262
+ "Save blob" . to_owned ( ) ,
263
+ self . blob . to_vec ( ) ,
264
+ ) ;
265
+ } )
266
+ }
267
+
268
+ pub fn data_ui (
269
+ & self ,
270
+ ctx : & ViewerContext < ' _ > ,
271
+ ui : & mut egui:: Ui ,
272
+ ui_layout : UiLayout ,
273
+ query : & re_chunk_store:: LatestAtQuery ,
274
+ entity_path : & EntityPath ,
275
+ ) {
276
+ if let Some ( row_id) = self . row_id
277
+ && !ui_layout. is_single_line ( )
278
+ && ui_layout != UiLayout :: Tooltip
279
+ {
280
+ exif_ui ( ui, StoredBlobCacheKey :: new ( row_id, & self . descr ) , & self . blob ) ;
281
+ }
282
+
283
+ if let Some ( image) = & self . image {
284
+ image. data_ui ( ctx, ui, ui_layout, query, entity_path) ;
285
+ }
286
+
287
+ if let Some ( video) = & self . video {
288
+ video. data_ui ( ctx, ui, ui_layout, query) ;
289
+ }
290
+ }
291
+ }
0 commit comments