Skip to content

Commit

Permalink
Screenshots in wasm (#8455)
Browse files Browse the repository at this point in the history
# Objective

- Enable taking a screenshot in wasm
- Followup on #7163 

## Solution

- Create a blob from the image data, generate a url to that blob, add an
`a` element to the document linking to that url, click on that element,
then revoke the url
- This will automatically trigger a download of the screenshot file in
the browser
  • Loading branch information
mockersf authored Apr 28, 2023
1 parent 670f3f0 commit cb286e5
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 4 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1968,7 +1968,7 @@ path = "examples/window/screenshot.rs"
name = "Screenshot"
description = "Shows how to save screenshots to disk"
category = "Window"
wasm = false
wasm = true

[[example]]
name = "transparent_window"
Expand Down
13 changes: 13 additions & 0 deletions crates/bevy_render/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,16 @@ encase = { version = "0.5", features = ["glam"] }
# For wgpu profiling using tracing. Use `RUST_LOG=info` to also capture the wgpu spans.
profiling = { version = "1", features = ["profile-with-tracing"], optional = true }
async-channel = "1.8"

[target.'cfg(target_arch = "wasm32")'.dependencies]
js-sys = "0.3"
web-sys = { version = "0.3", features = [
'Blob',
'Document',
'Element',
'HtmlElement',
'Node',
'Url',
'Window',
] }
wasm-bindgen = "0.2"
37 changes: 37 additions & 0 deletions crates/bevy_render/src/view/window/screenshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,47 @@ impl ScreenshotManager {
// discard the alpha channel which stores brightness values when HDR is enabled to make sure
// the screenshot looks right
let img = dyn_img.to_rgb8();
#[cfg(not(target_arch = "wasm32"))]
match img.save_with_format(&path, format) {
Ok(_) => info!("Screenshot saved to {}", path.display()),
Err(e) => error!("Cannot save screenshot, IO error: {e}"),
}

#[cfg(target_arch = "wasm32")]
{
match (|| {
use image::EncodableLayout;
use wasm_bindgen::{JsCast, JsValue};

let mut image_buffer = std::io::Cursor::new(Vec::new());
img.write_to(&mut image_buffer, format)
.map_err(|e| JsValue::from_str(&format!("{e}")))?;
// SAFETY: `image_buffer` only exist in this closure, and is not used after this line
let parts = js_sys::Array::of1(&unsafe {
js_sys::Uint8Array::view(image_buffer.into_inner().as_bytes())
.into()
});
let blob = web_sys::Blob::new_with_u8_array_sequence(&parts)?;
let url = web_sys::Url::create_object_url_with_blob(&blob)?;
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let link = document.create_element("a")?;
link.set_attribute("href", &url)?;
link.set_attribute(
"download",
path.file_name()
.and_then(|filename| filename.to_str())
.ok_or_else(|| JsValue::from_str("Invalid filename"))?,
)?;
let html_element = link.dyn_into::<web_sys::HtmlElement>()?;
html_element.click();
web_sys::Url::revoke_object_url(&url)?;
Ok::<(), JsValue>(())
})() {
Ok(_) => info!("Screenshot saved to {}", path.display()),
Err(e) => error!("Cannot save screenshot, error: {e:?}"),
};
}
}
Err(e) => error!("Cannot save screenshot, requested format not recognized: {e}"),
},
Expand Down
23 changes: 20 additions & 3 deletions examples/window/screenshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(Update, screenshot_on_f12)
.add_systems(Update, screenshot_on_spacebar)
.run();
}

fn screenshot_on_f12(
fn screenshot_on_spacebar(
input: Res<Input<KeyCode>>,
main_window: Query<Entity, With<PrimaryWindow>>,
mut screenshot_manager: ResMut<ScreenshotManager>,
mut counter: Local<u32>,
) {
if input.just_pressed(KeyCode::F12) {
if input.just_pressed(KeyCode::Space) {
let path = format!("./screenshot-{}.png", *counter);
*counter += 1;
screenshot_manager
Expand Down Expand Up @@ -61,4 +61,21 @@ fn setup(
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
..default()
});

commands.spawn(
TextBundle::from_section(
"Press <spacebar> to save a screenshot to disk",
TextStyle {
font_size: 25.0,
color: Color::WHITE,
..default()
},
)
.with_style(Style {
position_type: PositionType::Absolute,
top: Val::Px(10.0),
left: Val::Px(10.0),
..default()
}),
);
}

0 comments on commit cb286e5

Please sign in to comment.