mirror of
https://github.com/bevyengine/bevy
synced 2024-09-20 06:22:01 +00:00
Screenshots in wasm (#8455)
# 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
This commit is contained in:
parent
670f3f0dce
commit
cb286e5b60
4 changed files with 71 additions and 4 deletions
|
@ -1968,7 +1968,7 @@ path = "examples/window/screenshot.rs"
|
||||||
name = "Screenshot"
|
name = "Screenshot"
|
||||||
description = "Shows how to save screenshots to disk"
|
description = "Shows how to save screenshots to disk"
|
||||||
category = "Window"
|
category = "Window"
|
||||||
wasm = false
|
wasm = true
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "transparent_window"
|
name = "transparent_window"
|
||||||
|
|
|
@ -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.
|
# For wgpu profiling using tracing. Use `RUST_LOG=info` to also capture the wgpu spans.
|
||||||
profiling = { version = "1", features = ["profile-with-tracing"], optional = true }
|
profiling = { version = "1", features = ["profile-with-tracing"], optional = true }
|
||||||
async-channel = "1.8"
|
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"
|
||||||
|
|
|
@ -71,10 +71,47 @@ impl ScreenshotManager {
|
||||||
// discard the alpha channel which stores brightness values when HDR is enabled to make sure
|
// discard the alpha channel which stores brightness values when HDR is enabled to make sure
|
||||||
// the screenshot looks right
|
// the screenshot looks right
|
||||||
let img = dyn_img.to_rgb8();
|
let img = dyn_img.to_rgb8();
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
match img.save_with_format(&path, format) {
|
match img.save_with_format(&path, format) {
|
||||||
Ok(_) => info!("Screenshot saved to {}", path.display()),
|
Ok(_) => info!("Screenshot saved to {}", path.display()),
|
||||||
Err(e) => error!("Cannot save screenshot, IO error: {e}"),
|
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}"),
|
Err(e) => error!("Cannot save screenshot, requested format not recognized: {e}"),
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,17 +8,17 @@ fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins(DefaultPlugins)
|
.add_plugins(DefaultPlugins)
|
||||||
.add_systems(Startup, setup)
|
.add_systems(Startup, setup)
|
||||||
.add_systems(Update, screenshot_on_f12)
|
.add_systems(Update, screenshot_on_spacebar)
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn screenshot_on_f12(
|
fn screenshot_on_spacebar(
|
||||||
input: Res<Input<KeyCode>>,
|
input: Res<Input<KeyCode>>,
|
||||||
main_window: Query<Entity, With<PrimaryWindow>>,
|
main_window: Query<Entity, With<PrimaryWindow>>,
|
||||||
mut screenshot_manager: ResMut<ScreenshotManager>,
|
mut screenshot_manager: ResMut<ScreenshotManager>,
|
||||||
mut counter: Local<u32>,
|
mut counter: Local<u32>,
|
||||||
) {
|
) {
|
||||||
if input.just_pressed(KeyCode::F12) {
|
if input.just_pressed(KeyCode::Space) {
|
||||||
let path = format!("./screenshot-{}.png", *counter);
|
let path = format!("./screenshot-{}.png", *counter);
|
||||||
*counter += 1;
|
*counter += 1;
|
||||||
screenshot_manager
|
screenshot_manager
|
||||||
|
@ -61,4 +61,21 @@ fn setup(
|
||||||
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
|
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||||
..default()
|
..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()
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue