Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Screenshots in wasm #8455

Merged
merged 4 commits into from
Apr 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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',
Comment on lines +89 to +95
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will including more web-sys features meaningfully affect our wasm build sizes?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just tried a build, it's 14kB smaller with this PR. Build duration were similar

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing that it got smaller by adding features. Works for me

] }
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>(())
})() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The syntax for "try/catch" in rust is very unusual 🙂

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you... get used to it. and it provides nice scoping

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()
}),
);
}