Skip to content

Commit 870264b

Browse files
amfaberemilk
andauthored
eframe: capture a screenshot using Frame::request_screenshot
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
1 parent 74d43bf commit 870264b

File tree

11 files changed

+384
-73
lines changed

11 files changed

+384
-73
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/eframe/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ NOTE: [`egui-winit`](../egui-winit/CHANGELOG.md), [`egui_glium`](../egui_glium/C
55

66

77
## Unreleased
8+
* Add `Frame::request_screenshot` and `Frame::screenshot` to communicate to the backend that a screenshot of the current frame should be exposed by `Frame` during `App::post_rendering` ([#2676](https://github.com/emilk/egui/pull/2676))
89

910

1011
## 0.21.3 - 2023-02-15

crates/eframe/src/epi.rs

+69-1
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ pub trait App {
197197

198198
/// Called each time after the rendering the UI.
199199
///
200-
/// Can be used to access pixel data with `get_pixels`
200+
/// Can be used to access pixel data with [`Frame::screenshot`]
201201
fn post_rendering(&mut self, _window_size_px: [u32; 2], _frame: &Frame) {}
202202
}
203203

@@ -674,6 +674,11 @@ pub struct Frame {
674674
/// Can be used to manage GPU resources for custom rendering with WGPU using [`egui::PaintCallback`]s.
675675
#[cfg(feature = "wgpu")]
676676
pub(crate) wgpu_render_state: Option<egui_wgpu::RenderState>,
677+
678+
/// If [`Frame::request_screenshot`] was called during a frame, this field will store the screenshot
679+
/// such that it can be retrieved during [`App::post_rendering`] with [`Frame::screenshot`]
680+
#[cfg(not(target_arch = "wasm32"))]
681+
pub(crate) screenshot: std::cell::Cell<Option<egui::ColorImage>>,
677682
}
678683

679684
impl Frame {
@@ -695,6 +700,66 @@ impl Frame {
695700
self.storage.as_deref()
696701
}
697702

703+
/// Request the current frame's pixel data. Needs to be retrieved by calling [`Frame::screenshot`]
704+
/// during [`App::post_rendering`].
705+
#[cfg(not(target_arch = "wasm32"))]
706+
pub fn request_screenshot(&mut self) {
707+
self.output.screenshot_requested = true;
708+
}
709+
710+
/// Cancel a request made with [`Frame::request_screenshot`].
711+
#[cfg(not(target_arch = "wasm32"))]
712+
pub fn cancel_screenshot_request(&mut self) {
713+
self.output.screenshot_requested = false;
714+
}
715+
716+
/// During [`App::post_rendering`], use this to retrieve the pixel data that was requested during
717+
/// [`App::update`] via [`Frame::request_screenshot`].
718+
///
719+
/// Returns None if:
720+
/// * Called in [`App::update`]
721+
/// * [`Frame::request_screenshot`] wasn't called on this frame during [`App::update`]
722+
/// * The rendering backend doesn't support this feature (yet). Currently implemented for wgpu and glow, but not with wasm as target.
723+
/// * Retrieving the data was unsuccessful in some way.
724+
///
725+
/// See also [`egui::ColorImage::region`]
726+
///
727+
/// ## Example generating a capture of everything within a square of 100 pixels located at the top left of the app and saving it with the [`image`](crates.io/crates/image) crate:
728+
/// ```
729+
/// struct MyApp;
730+
///
731+
/// impl eframe::App for MyApp {
732+
/// fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
733+
/// // In real code the app would render something here
734+
/// frame.request_screenshot();
735+
/// // Things that are added to the frame after the call to
736+
/// // request_screenshot() will still be included.
737+
/// }
738+
///
739+
/// fn post_rendering(&mut self, _window_size: [u32; 2], frame: &eframe::Frame) {
740+
/// if let Some(screenshot) = frame.screenshot() {
741+
/// let pixels_per_point = frame.info().native_pixels_per_point;
742+
/// let region = egui::Rect::from_two_pos(
743+
/// egui::Pos2::ZERO,
744+
/// egui::Pos2{ x: 100., y: 100. },
745+
/// );
746+
/// let top_left_corner = screenshot.region(&region, pixels_per_point);
747+
/// image::save_buffer(
748+
/// "top_left.png",
749+
/// top_left_corner.as_raw(),
750+
/// top_left_corner.width() as u32,
751+
/// top_left_corner.height() as u32,
752+
/// image::ColorType::Rgba8,
753+
/// ).unwrap();
754+
/// }
755+
/// }
756+
/// }
757+
/// ```
758+
#[cfg(not(target_arch = "wasm32"))]
759+
pub fn screenshot(&self) -> Option<egui::ColorImage> {
760+
self.screenshot.take()
761+
}
762+
698763
/// A place where you can store custom data in a way that persists when you restart the app.
699764
pub fn storage_mut(&mut self) -> Option<&mut (dyn Storage + 'static)> {
700765
self.storage.as_deref_mut()
@@ -1061,5 +1126,8 @@ pub(crate) mod backend {
10611126
/// Set to some bool to maximize or unmaximize window.
10621127
#[cfg(not(target_arch = "wasm32"))]
10631128
pub maximized: Option<bool>,
1129+
1130+
#[cfg(not(target_arch = "wasm32"))]
1131+
pub screenshot_requested: bool,
10641132
}
10651133
}

crates/eframe/src/native/epi_integration.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ pub fn window_builder<E>(
128128
// Restore pos/size from previous session
129129
window_settings.clamp_to_sane_values(largest_monitor_point_size(event_loop));
130130
#[cfg(windows)]
131-
window_settings.clamp_window_to_sane_position(&event_loop);
131+
window_settings.clamp_window_to_sane_position(event_loop);
132132
window_builder = window_settings.initialize_window(window_builder);
133133
window_settings.inner_size_points()
134134
} else {
@@ -228,6 +228,7 @@ pub fn handle_app_output(
228228
window_pos,
229229
visible: _, // handled in post_present
230230
always_on_top,
231+
screenshot_requested: _, // handled by the rendering backend,
231232
minimized,
232233
maximized,
233234
} = app_output;
@@ -349,6 +350,7 @@ impl EpiIntegration {
349350
gl,
350351
#[cfg(feature = "wgpu")]
351352
wgpu_render_state,
353+
screenshot: std::cell::Cell::new(None),
352354
};
353355

354356
let mut egui_winit = egui_winit::State::new(event_loop);
@@ -467,6 +469,7 @@ impl EpiIntegration {
467469
tracing::debug!("App::on_close_event returned {}", self.close);
468470
}
469471
self.frame.output.visible = app_output.visible; // this is handled by post_present
472+
self.frame.output.screenshot_requested = app_output.screenshot_requested;
470473
handle_app_output(
471474
window,
472475
self.egui_ctx.pixels_per_point(),

crates/eframe/src/native/run.rs

+23-6
Original file line numberDiff line numberDiff line change
@@ -803,6 +803,14 @@ mod glow_integration {
803803
&textures_delta,
804804
);
805805

806+
let screenshot_requested = &mut integration.frame.output.screenshot_requested;
807+
808+
if *screenshot_requested {
809+
*screenshot_requested = false;
810+
let screenshot = painter.read_screen_rgba(screen_size_in_pixels);
811+
integration.frame.screenshot.set(Some(screenshot));
812+
}
813+
806814
integration.post_rendering(app.as_mut(), window);
807815

808816
{
@@ -820,11 +828,15 @@ mod glow_integration {
820828
path.ends_with(".png"),
821829
"Expected EFRAME_SCREENSHOT_TO to end with '.png', got {path:?}"
822830
);
823-
let [w, h] = screen_size_in_pixels;
824-
let pixels = painter.read_screen_rgba(screen_size_in_pixels);
825-
let image = image::RgbaImage::from_vec(w, h, pixels).unwrap();
826-
let image = image::imageops::flip_vertical(&image);
827-
image.save(&path).unwrap_or_else(|err| {
831+
let screenshot = painter.read_screen_rgba(screen_size_in_pixels);
832+
image::save_buffer(
833+
&path,
834+
screenshot.as_raw(),
835+
screenshot.width() as u32,
836+
screenshot.height() as u32,
837+
image::ColorType::Rgba8,
838+
)
839+
.unwrap_or_else(|err| {
828840
panic!("Failed to save screenshot to {path:?}: {err}");
829841
});
830842
eprintln!("Screenshot saved to {path:?}.");
@@ -1229,12 +1241,17 @@ mod wgpu_integration {
12291241
integration.egui_ctx.tessellate(shapes)
12301242
};
12311243

1232-
painter.paint_and_update_textures(
1244+
let screenshot_requested = &mut integration.frame.output.screenshot_requested;
1245+
1246+
let screenshot = painter.paint_and_update_textures(
12331247
integration.egui_ctx.pixels_per_point(),
12341248
app.clear_color(&integration.egui_ctx.style().visuals),
12351249
&clipped_primitives,
12361250
&textures_delta,
1251+
*screenshot_requested,
12371252
);
1253+
*screenshot_requested = false;
1254+
integration.frame.screenshot.set(screenshot);
12381255

12391256
integration.post_rendering(app.as_mut(), window);
12401257
integration.post_present(window);

crates/egui-wgpu/CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ All notable changes to the `egui-wgpu` integration will be noted in this file.
33

44

55
## Unreleased
6+
* Add `read_screan_rgba` to the egui-wgpu `Painter`, to allow for capturing the current frame when using wgpu. Used in conjuction with `Frame::request_screenshot`. ([#2676](https://github.com/emilk/egui/pull/2676))
67

78

89
## 0.21.0 - 2023-02-08
@@ -12,7 +13,6 @@ All notable changes to the `egui-wgpu` integration will be noted in this file.
1213
* `egui-wgpu` now only depends on `epaint` instead of the entire `egui` ([#2438](https://github.com/emilk/egui/pull/2438)).
1314
* `winit::Painter` now supports transparent backbuffer ([#2684](https://github.com/emilk/egui/pull/2684)).
1415

15-
1616
## 0.20.0 - 2022-12-08 - web support
1717
* Renamed `RenderPass` to `Renderer`.
1818
* Renamed `RenderPass::execute` to `RenderPass::render`.

0 commit comments

Comments
 (0)