Skip to content

Commit

Permalink
Space view screenshotting in native viewer (#8258)
Browse files Browse the repository at this point in the history
### Related
* Closes #3941
* Uses emilk/egui#5416
* Uses emilk/egui#5426
* Follow-up: #8264

### What
Right-click the name of any space view to get the option to copy its
contents as a screenshot, or save it to disk:


![image](https://github.com/user-attachments/assets/0a0c5215-9042-4989-9e4a-62f76823588a)

-> 


![image](https://github.com/user-attachments/assets/ec222323-4692-4fd2-ae7b-2342996829a3)


#### Details
The implementation simply takes a full-screen screenshot and crops the
result.

The previous attempt (behind a feature-flag) only captured things that
were rendered with `re_renderer`, i.e. did not include any egui
elements, like labels. So it also only worked on spatial views. This PR
work on _all_ space views, and could also be extended to work on
containers etc.

### Limitations
Not yet implemented on web. See
#8264

### TODO
* [x] Point commit hash to egui `master`

---------

Co-authored-by: Andreas Reich <[email protected]>
  • Loading branch information
emilk and Wumpf authored Dec 3, 2024
1 parent 99cc6c0 commit 16f0309
Show file tree
Hide file tree
Showing 24 changed files with 336 additions and 245 deletions.
24 changes: 11 additions & 13 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1923,7 +1923,7 @@ checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
[[package]]
name = "ecolor"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=eac7ba01fa37bad35f71bc303561761952361b7c#eac7ba01fa37bad35f71bc303561761952361b7c"
dependencies = [
"bytemuck",
"color-hex",
Expand All @@ -1940,7 +1940,7 @@ checksum = "18aade80d5e09429040243ce1143ddc08a92d7a22820ac512610410a4dd5214f"
[[package]]
name = "eframe"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=eac7ba01fa37bad35f71bc303561761952361b7c#eac7ba01fa37bad35f71bc303561761952361b7c"
dependencies = [
"ahash",
"bytemuck",
Expand Down Expand Up @@ -1979,7 +1979,7 @@ dependencies = [
[[package]]
name = "egui"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=eac7ba01fa37bad35f71bc303561761952361b7c#eac7ba01fa37bad35f71bc303561761952361b7c"
dependencies = [
"accesskit",
"ahash",
Expand All @@ -1996,7 +1996,7 @@ dependencies = [
[[package]]
name = "egui-wgpu"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=eac7ba01fa37bad35f71bc303561761952361b7c#eac7ba01fa37bad35f71bc303561761952361b7c"
dependencies = [
"ahash",
"bytemuck",
Expand All @@ -2015,7 +2015,7 @@ dependencies = [
[[package]]
name = "egui-winit"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=eac7ba01fa37bad35f71bc303561761952361b7c#eac7ba01fa37bad35f71bc303561761952361b7c"
dependencies = [
"accesskit_winit",
"ahash",
Expand Down Expand Up @@ -2057,7 +2057,7 @@ dependencies = [
[[package]]
name = "egui_extras"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=eac7ba01fa37bad35f71bc303561761952361b7c#eac7ba01fa37bad35f71bc303561761952361b7c"
dependencies = [
"ahash",
"egui",
Expand All @@ -2074,7 +2074,7 @@ dependencies = [
[[package]]
name = "egui_glow"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=eac7ba01fa37bad35f71bc303561761952361b7c#eac7ba01fa37bad35f71bc303561761952361b7c"
dependencies = [
"ahash",
"bytemuck",
Expand All @@ -2092,7 +2092,7 @@ dependencies = [
[[package]]
name = "egui_kittest"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=eac7ba01fa37bad35f71bc303561761952361b7c#eac7ba01fa37bad35f71bc303561761952361b7c"
dependencies = [
"dify",
"egui",
Expand Down Expand Up @@ -2161,7 +2161,7 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "emath"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=eac7ba01fa37bad35f71bc303561761952361b7c#eac7ba01fa37bad35f71bc303561761952361b7c"
dependencies = [
"bytemuck",
"serde",
Expand Down Expand Up @@ -2277,7 +2277,7 @@ dependencies = [
[[package]]
name = "epaint"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=eac7ba01fa37bad35f71bc303561761952361b7c#eac7ba01fa37bad35f71bc303561761952361b7c"
dependencies = [
"ab_glyph",
"ahash",
Expand All @@ -2296,7 +2296,7 @@ dependencies = [
[[package]]
name = "epaint_default_fonts"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=eac7ba01fa37bad35f71bc303561761952361b7c#eac7ba01fa37bad35f71bc303561761952361b7c"

[[package]]
name = "equivalent"
Expand Down Expand Up @@ -6765,8 +6765,6 @@ dependencies = [
"ahash",
"egui",
"egui_tiles",
"glam",
"image",
"itertools 0.13.0",
"nohash-hasher",
"rayon",
Expand Down
16 changes: 8 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -559,20 +559,20 @@ significant_drop_tightening = "allow" # An update of parking_lot made this trigg
# As a last resport, patch with a commit to our own repository.
# ALWAYS document what PR the commit hash is part of, or when it was merged into the upstream trunk.

ecolor = { git = "https://github.com/emilk/egui.git", rev = "84cc1572b175d49a64f1b323a6d7e56b1f1fba66" } # egui master 2024-11-27
eframe = { git = "https://github.com/emilk/egui.git", rev = "84cc1572b175d49a64f1b323a6d7e56b1f1fba66" } # egui master 2024-11-27
egui = { git = "https://github.com/emilk/egui.git", rev = "84cc1572b175d49a64f1b323a6d7e56b1f1fba66" } # egui master 2024-11-27
egui_extras = { git = "https://github.com/emilk/egui.git", rev = "84cc1572b175d49a64f1b323a6d7e56b1f1fba66" } # egui master 2024-11-27
egui-wgpu = { git = "https://github.com/emilk/egui.git", rev = "84cc1572b175d49a64f1b323a6d7e56b1f1fba66" } # egui master 2024-11-27
emath = { git = "https://github.com/emilk/egui.git", rev = "84cc1572b175d49a64f1b323a6d7e56b1f1fba66" } # egui master 2024-11-27

egui_kittest = { git = "https://github.com/emilk/egui.git", rev = "84cc1572b175d49a64f1b323a6d7e56b1f1fba66" } # egui master 2024-11-27
ecolor = { git = "https://github.com/emilk/egui.git", rev = "eac7ba01fa37bad35f71bc303561761952361b7c" } # egui master 2024-12-03
eframe = { git = "https://github.com/emilk/egui.git", rev = "eac7ba01fa37bad35f71bc303561761952361b7c" } # egui master 2024-12-03
egui = { git = "https://github.com/emilk/egui.git", rev = "eac7ba01fa37bad35f71bc303561761952361b7c" } # egui master 2024-12-03
egui_extras = { git = "https://github.com/emilk/egui.git", rev = "eac7ba01fa37bad35f71bc303561761952361b7c" } # egui master 2024-12-03
egui_kittest = { git = "https://github.com/emilk/egui.git", rev = "eac7ba01fa37bad35f71bc303561761952361b7c" } # egui master 2024-12-03
egui-wgpu = { git = "https://github.com/emilk/egui.git", rev = "eac7ba01fa37bad35f71bc303561761952361b7c" } # egui master 2024-12-03
emath = { git = "https://github.com/emilk/egui.git", rev = "eac7ba01fa37bad35f71bc303561761952361b7c" } # egui master 2024-12-03

# Useful while developing:
# ecolor = { path = "../../egui/crates/ecolor" }
# eframe = { path = "../../egui/crates/eframe" }
# egui = { path = "../../egui/crates/egui" }
# egui_extras = { path = "../../egui/crates/egui_extras" }
# egui_kittest = { path = "../../egui/crates/egui_kittest" }
# egui-wgpu = { path = "../../egui/crates/egui-wgpu" }
# emath = { path = "../../egui/crates/emath" }

Expand Down
6 changes: 6 additions & 0 deletions crates/viewer/re_context_menu/src/actions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,9 @@ pub(super) mod collapse_expand_all;
pub(super) mod move_contents_to_new_container;
pub(super) mod remove;
pub(super) mod show_hide;

#[cfg(not(target_arch = "wasm32"))] // TODO(#8264): screenshotting on web
mod screenshot_action;

#[cfg(not(target_arch = "wasm32"))] // TODO(#8264): screenshotting on web
pub use screenshot_action::ScreenshotAction;
78 changes: 78 additions & 0 deletions crates/viewer/re_context_menu/src/actions/screenshot_action.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use re_viewer_context::{
Item, PublishedSpaceViewInfo, ScreenshotTarget, SpaceViewId, SpaceViewRectPublisher,
};

use crate::{ContextMenuAction, ContextMenuContext};

/// Space view screenshot action.
#[cfg(not(target_arch = "wasm32"))]
pub enum ScreenshotAction {
/// Screenshot the space view, and copy the results to clipboard.
CopyScreenshot,

/// Screenshot the space view, and save the results to disk.
SaveScreenshot,
}

impl ContextMenuAction for ScreenshotAction {
/// Do we have a context menu for this selection?
fn supports_selection(&self, ctx: &ContextMenuContext<'_>) -> bool {
// Allow if there is a single space view selected.
ctx.selection.len() == 1
&& ctx
.selection
.iter()
.all(|(item, _)| self.supports_item(ctx, item))
}

/// Do we have a context menu for this item?
fn supports_item(&self, ctx: &ContextMenuContext<'_>, item: &Item) -> bool {
let Item::SpaceView(space_view_id) = item else {
return false;
};

ctx.egui_context.memory_mut(|mem| {
mem.caches
.cache::<SpaceViewRectPublisher>()
.get(space_view_id)
.is_some()
})
}

fn label(&self, _ctx: &ContextMenuContext<'_>) -> String {
match self {
Self::CopyScreenshot => "Copy screenshot".to_owned(),
Self::SaveScreenshot => "Save screenshot…".to_owned(),
}
}

fn process_space_view(&self, ctx: &ContextMenuContext<'_>, space_view_id: &SpaceViewId) {
let Some(space_view_info) = ctx.egui_context.memory_mut(|mem| {
mem.caches
.cache::<SpaceViewRectPublisher>()
.get(space_view_id)
.cloned()
}) else {
return;
};

let PublishedSpaceViewInfo { name, rect } = space_view_info;

let rect = rect.shrink(1.75); // Hacky: Shrink so we don't accidentally include the border of the space-view.

let target = match self {
Self::CopyScreenshot => ScreenshotTarget::CopyToClipboard,
Self::SaveScreenshot => ScreenshotTarget::SaveToDisk,
};

ctx.egui_context
.send_viewport_cmd(egui::ViewportCommand::Screenshot(egui::UserData::new(
re_viewer_context::ScreenshotInfo {
ui_rect: Some(rect),
pixels_per_point: ctx.egui_context.pixels_per_point(),
name,
target,
},
)));
}
}
5 changes: 5 additions & 0 deletions crates/viewer/re_context_menu/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ fn action_list(
Box::new(HideAction),
Box::new(RemoveAction),
],
#[cfg(not(target_arch = "wasm32"))] // TODO(#8264): screenshotting on web
vec![
Box::new(actions::ScreenshotAction::CopyScreenshot),
Box::new(actions::ScreenshotAction::SaveScreenshot),
],
vec![
Box::new(CollapseExpandAllAction::ExpandAll),
Box::new(CollapseExpandAllAction::CollapseAll),
Expand Down
6 changes: 5 additions & 1 deletion crates/viewer/re_data_ui/src/blob.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,11 @@ pub fn blob_preview_and_save_ui(
file_name.push_str(file_extension);
}

ctx.save_file_dialog(file_name, "Save blob".to_owned(), blob.to_vec());
ctx.command_sender.save_file_dialog(
&file_name,
"Save blob".to_owned(),
blob.to_vec(),
);
}

#[cfg(not(target_arch = "wasm32"))]
Expand Down
3 changes: 2 additions & 1 deletion crates/viewer/re_data_ui/src/instance_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,8 @@ fn image_download_button_ui(
.map_or("image", |name| name.unescaped_str())
.to_owned()
);
ctx.save_file_dialog(file_name, "Save image".to_owned(), png_bytes);
ctx.command_sender
.save_file_dialog(&file_name, "Save image".to_owned(), png_bytes);
}
Err(err) => {
re_log::error!("{err}");
Expand Down
2 changes: 0 additions & 2 deletions crates/viewer/re_space_view/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ mod instance_hash_conversions;
mod outlines;
mod query;
mod results_ext;
mod screenshot;
mod view_property_ui;

pub use annotation_context_utils::{
Expand All @@ -31,7 +30,6 @@ pub use query::{
pub use results_ext::{
HybridLatestAtResults, HybridResults, HybridResultsChunkIter, RangeResultsExt,
};
pub use screenshot::ScreenshotMode;
pub use view_property_ui::view_property_ui;

pub mod external {
Expand Down
9 changes: 0 additions & 9 deletions crates/viewer/re_space_view/src/screenshot.rs

This file was deleted.

34 changes: 1 addition & 33 deletions crates/viewer/re_space_view_spatial/src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,12 @@ use egui::{epaint::util::OrderedFloat, text::TextWrapping, NumExt as _, WidgetTe

use re_format::format_f32;
use re_math::BoundingBox;
use re_space_view::ScreenshotMode;
use re_types::{
archetypes::Pinhole, blueprint::components::VisualBounds2D, components::ViewCoordinates,
image::ImageKind,
};
use re_ui::UiExt as _;
use re_viewer_context::{
HoverHighlight, SelectionHighlight, SpaceViewHighlights, SpaceViewState, ViewerContext,
};
use re_viewer_context::{HoverHighlight, SelectionHighlight, SpaceViewHighlights, SpaceViewState};

use crate::{
eye::EyeMode,
Expand Down Expand Up @@ -341,35 +338,6 @@ pub fn paint_loading_spinners(
}
}

pub fn screenshot_context_menu(
_ctx: &ViewerContext<'_>,
_response: &egui::Response,
) -> Option<ScreenshotMode> {
#[cfg(not(target_arch = "wasm32"))]
{
if _ctx.app_options.experimental_space_view_screenshots {
let mut take_screenshot = None;
_response.context_menu(|ui| {
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
if ui.button("Save screenshot to disk").clicked() {
take_screenshot = Some(ScreenshotMode::SaveAndCopyToClipboard);
ui.close_menu();
} else if ui.button("Copy screenshot to clipboard").clicked() {
take_screenshot = Some(ScreenshotMode::CopyToClipboard);
ui.close_menu();
}
});
take_screenshot
} else {
None
}
}
#[cfg(target_arch = "wasm32")]
{
None
}
}

pub fn format_vector(v: glam::Vec3) -> String {
use glam::Vec3;

Expand Down
15 changes: 3 additions & 12 deletions crates/viewer/re_space_view_spatial/src/ui_2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@ use re_viewer_context::{
};
use re_viewport_blueprint::ViewProperty;

use super::{
eye::Eye,
ui::{create_labels, screenshot_context_menu},
};
use super::{eye::Eye, ui::create_labels};
use crate::{
query_pinhole_legacy, ui::SpatialSpaceViewState, view_kind::SpatialSpaceViewKind,
visualizers::collect_ui_labels, SpatialSpaceView2D,
Expand Down Expand Up @@ -169,7 +166,7 @@ impl SpatialSpaceView2D {
state.pinhole_at_origin =
query_pinhole_legacy(ctx, &ctx.current_query(), query.space_origin);

let (mut response, painter) =
let (response, painter) =
ui.allocate_painter(ui.available_size(), egui::Sense::click_and_drag());

// Convert ui coordinates to/from scene coordinates.
Expand Down Expand Up @@ -216,7 +213,7 @@ impl SpatialSpaceView2D {
ui.ctx().pixels_per_point(),
&eye,
);
response = crate::picking_ui::picking(
crate::picking_ui::picking(
ctx,
&picking_context,
ui,
Expand Down Expand Up @@ -249,12 +246,6 @@ impl SpatialSpaceView2D {

// ------------------------------------------------------------------------

if let Some(mode) = screenshot_context_menu(ctx, &response) {
view_builder
.schedule_screenshot(render_ctx, query.space_view_id.gpu_readback_id(), mode)
.ok();
}

// Draw a re_renderer driven view.
// Camera & projection are configured to ingest space coordinates directly.
painter.add(gpu_bridge::new_renderer_callback(
Expand Down
Loading

0 comments on commit 16f0309

Please sign in to comment.