Add screenshot functionality.

This commit is contained in:
Patrick Walton 2019-02-25 11:33:26 -08:00
parent aef7dd1353
commit f6af769486
5 changed files with 86 additions and 17 deletions

1
Cargo.lock generated
View File

@ -504,6 +504,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
"gl 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "gl 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"image 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)",
"jemallocator 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "jemallocator 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"nfd 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "nfd 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"pathfinder_geometry 0.3.0", "pathfinder_geometry 0.3.0",

View File

@ -13,6 +13,11 @@ rayon = "1.0"
sdl2 = "0.32" sdl2 = "0.32"
usvg = "0.4" usvg = "0.4"
[dependencies.image]
version = "0.21"
default-features = false
features = ["png_codec"]
[dependencies.pathfinder_geometry] [dependencies.pathfinder_geometry]
path = "../../geometry" path = "../../geometry"

View File

@ -12,7 +12,8 @@
use crate::ui::{DemoUI, UIAction, UIEvent}; use crate::ui::{DemoUI, UIAction, UIEvent};
use clap::{App, Arg}; use clap::{App, Arg};
use gl::types::GLsizei; use gl::types::{GLsizei, GLvoid};
use image::ColorType;
use jemallocator; use jemallocator;
use pathfinder_geometry::basic::point::{Point2DF32, Point2DI32, Point3DF32}; use pathfinder_geometry::basic::point::{Point2DF32, Point2DI32, Point3DF32};
use pathfinder_geometry::basic::rect::RectF32; use pathfinder_geometry::basic::rect::RectF32;
@ -89,6 +90,7 @@ pub struct DemoApp {
camera: Camera, camera: Camera,
frame_counter: u32, frame_counter: u32,
events: Vec<Event>, events: Vec<Event>,
pending_screenshot_path: Option<PathBuf>,
exit: bool, exit: bool,
mouselook_enabled: bool, mouselook_enabled: bool,
dirty: bool, dirty: bool,
@ -164,6 +166,7 @@ impl DemoApp {
camera, camera,
frame_counter: 0, frame_counter: 0,
pending_screenshot_path: None,
events: vec![], events: vec![],
exit: false, exit: false,
mouselook_enabled: false, mouselook_enabled: false,
@ -342,6 +345,10 @@ impl DemoApp {
self.draw_environment(&render_transform); self.draw_environment(&render_transform);
self.render_vector_scene(&built_scene); self.render_vector_scene(&built_scene);
if self.pending_screenshot_path.is_some() {
self.take_screenshot();
}
let rendering_time = self.renderer.shift_timer_query(); let rendering_time = self.renderer.shift_timer_query();
self.renderer.debug_ui.add_sample(tile_time, rendering_time); self.renderer.debug_ui.add_sample(tile_time, rendering_time);
self.renderer.debug_ui.draw(); self.renderer.debug_ui.draw();
@ -499,6 +506,11 @@ impl DemoApp {
self.dirty = true; self.dirty = true;
} }
UIAction::TakeScreenshot(ref path) => {
self.pending_screenshot_path = Some((*path).clone());
self.dirty = true;
}
UIAction::ZoomIn => { UIAction::ZoomIn => {
if let Camera::TwoD(ref mut transform) = self.camera { if let Camera::TwoD(ref mut transform) = self.camera {
let scale = Point2DF32::splat(1.0 + CAMERA_ZOOM_AMOUNT_2D); let scale = Point2DF32::splat(1.0 + CAMERA_ZOOM_AMOUNT_2D);
@ -531,6 +543,17 @@ impl DemoApp {
} }
} }
fn take_screenshot(&mut self) {
let screenshot_path = self.pending_screenshot_path.take().unwrap();
let (drawable_width, drawable_height) = self.window.drawable_size();
let pixels = self.device.readback_pixels(drawable_width, drawable_height);
image::save_buffer(screenshot_path,
&pixels,
drawable_width,
drawable_height,
ColorType::RGBA(8)).unwrap();
}
fn background_color(&self) -> ColorU { fn background_color(&self) -> ColorU {
if self.ui.dark_background_enabled { DARK_BG_COLOR } else { LIGHT_BG_COLOR } if self.ui.dark_background_enabled { DARK_BG_COLOR } else { LIGHT_BG_COLOR }
} }
@ -817,6 +840,29 @@ impl DemoDevice {
gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT | gl::STENCIL_BUFFER_BIT); gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT | gl::STENCIL_BUFFER_BIT);
} }
} }
fn readback_pixels(&self, width: u32, height: u32) -> Vec<u8> {
let mut pixels = vec![0; width as usize * height as usize * 4];
unsafe {
gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
gl::ReadPixels(0, 0,
width as GLsizei, height as GLsizei,
gl::RGBA,
gl::UNSIGNED_BYTE,
pixels.as_mut_ptr() as *mut GLvoid);
}
// Flip right-side-up.
let stride = width as usize * 4;
for y in 0..(height as usize / 2) {
let (index_a, index_b) = (y * stride, (height as usize - y - 1) * stride);
for offset in 0..stride {
pixels.swap(index_a + offset, index_b + offset);
}
}
pixels
}
} }
struct GroundProgram { struct GroundProgram {

View File

@ -30,19 +30,23 @@ const SLIDER_KNOB_HEIGHT: i32 = 48;
const EFFECTS_PANEL_WIDTH: i32 = 550; const EFFECTS_PANEL_WIDTH: i32 = 550;
const EFFECTS_PANEL_HEIGHT: i32 = BUTTON_HEIGHT * 3 + PADDING * 4; const EFFECTS_PANEL_HEIGHT: i32 = BUTTON_HEIGHT * 3 + PADDING * 4;
const BACKGROUND_SWITCH_X: i32 = PADDING + (BUTTON_WIDTH + PADDING) * 2 + PADDING + SWITCH_SIZE; const OPEN_BUTTON_X: i32 = PADDING + (BUTTON_WIDTH + PADDING) * 1;
const SCREENSHOT_BUTTON_X: i32 = PADDING + (BUTTON_WIDTH + PADDING) * 2;
const THREE_D_SWITCH_X: i32 = PADDING + (BUTTON_WIDTH + PADDING) * 3;
const BACKGROUND_SWITCH_X: i32 = PADDING + (BUTTON_WIDTH + PADDING) * 3 + PADDING + SWITCH_SIZE;
const ROTATE_PANEL_X: i32 = PADDING + (BUTTON_WIDTH + PADDING) * 2 + (PADDING + SWITCH_SIZE) * 2; const ROTATE_PANEL_X: i32 = PADDING + (BUTTON_WIDTH + PADDING) * 3 + (PADDING + SWITCH_SIZE) * 2;
const ROTATE_PANEL_WIDTH: i32 = SLIDER_WIDTH + PADDING * 2; const ROTATE_PANEL_WIDTH: i32 = SLIDER_WIDTH + PADDING * 2;
const ROTATE_PANEL_HEIGHT: i32 = PADDING * 2 + SLIDER_HEIGHT; const ROTATE_PANEL_HEIGHT: i32 = PADDING * 2 + SLIDER_HEIGHT;
static EFFECTS_PNG_NAME: &'static str = "demo-effects"; static EFFECTS_PNG_NAME: &'static str = "demo-effects";
static OPEN_PNG_NAME: &'static str = "demo-open"; static OPEN_PNG_NAME: &'static str = "demo-open";
static ROTATE_PNG_NAME: &'static str = "demo-rotate"; static ROTATE_PNG_NAME: &'static str = "demo-rotate";
static ZOOM_IN_PNG_NAME: &'static str = "demo-zoom-in"; static ZOOM_IN_PNG_NAME: &'static str = "demo-zoom-in";
static ZOOM_OUT_PNG_NAME: &'static str = "demo-zoom-out"; static ZOOM_OUT_PNG_NAME: &'static str = "demo-zoom-out";
static BG_LIGHT_PNG_NAME: &'static str = "demo-bg-light"; static BG_LIGHT_PNG_NAME: &'static str = "demo-bg-light";
static BG_DARK_PNG_NAME: &'static str = "demo-bg-dark"; static BG_DARK_PNG_NAME: &'static str = "demo-bg-dark";
static SCREENSHOT_PNG_NAME: &'static str = "demo-screenshot";
pub struct DemoUI { pub struct DemoUI {
effects_texture: Texture, effects_texture: Texture,
@ -52,6 +56,7 @@ pub struct DemoUI {
zoom_out_texture: Texture, zoom_out_texture: Texture,
bg_light_texture: Texture, bg_light_texture: Texture,
bg_dark_texture: Texture, bg_dark_texture: Texture,
screenshot_texture: Texture,
effects_panel_visible: bool, effects_panel_visible: bool,
rotate_panel_visible: bool, rotate_panel_visible: bool,
@ -73,6 +78,7 @@ impl DemoUI {
let zoom_out_texture = device.create_texture_from_png(ZOOM_OUT_PNG_NAME); let zoom_out_texture = device.create_texture_from_png(ZOOM_OUT_PNG_NAME);
let bg_light_texture = device.create_texture_from_png(BG_LIGHT_PNG_NAME); let bg_light_texture = device.create_texture_from_png(BG_LIGHT_PNG_NAME);
let bg_dark_texture = device.create_texture_from_png(BG_DARK_PNG_NAME); let bg_dark_texture = device.create_texture_from_png(BG_DARK_PNG_NAME);
let screenshot_texture = device.create_texture_from_png(SCREENSHOT_PNG_NAME);
DemoUI { DemoUI {
effects_texture, effects_texture,
@ -82,6 +88,7 @@ impl DemoUI {
zoom_out_texture, zoom_out_texture,
bg_light_texture, bg_light_texture,
bg_dark_texture, bg_dark_texture,
screenshot_texture,
three_d_enabled: options.three_d, three_d_enabled: options.three_d,
dark_background_enabled: true, dark_background_enabled: true,
effects_panel_visible: false, effects_panel_visible: false,
@ -107,27 +114,36 @@ impl DemoUI {
} }
// Draw open button. // Draw open button.
let open_button_x = PADDING + BUTTON_WIDTH + PADDING; let lower_button_y = bottom - BUTTON_HEIGHT;
let open_button_y = bottom - BUTTON_HEIGHT; let open_button_position = Point2DI32::new(OPEN_BUTTON_X, lower_button_y);
let open_button_position = Point2DI32::new(open_button_x, open_button_y);
if self.draw_button(debug_ui, event, open_button_position, &self.open_texture) { if self.draw_button(debug_ui, event, open_button_position, &self.open_texture) {
if let Ok(Response::Okay(file)) = nfd::open_file_dialog(Some("svg"), None) { if let Ok(Response::Okay(file)) = nfd::open_file_dialog(Some("svg"), None) {
*action = UIAction::OpenFile(PathBuf::from(file)); *action = UIAction::OpenFile(PathBuf::from(file));
} }
} }
// Draw screenshot button.
let screenshot_button_position = Point2DI32::new(SCREENSHOT_BUTTON_X, lower_button_y);
if self.draw_button(debug_ui,
event,
screenshot_button_position,
&self.screenshot_texture) {
if let Ok(Response::Okay(file)) = nfd::open_save_dialog(Some("png"), None) {
*action = UIAction::TakeScreenshot(PathBuf::from(file));
}
}
// Draw 3D switch. // Draw 3D switch.
let threed_switch_x = PADDING + (BUTTON_WIDTH + PADDING) * 2; let three_d_switch_origin = Point2DI32::new(THREE_D_SWITCH_X, lower_button_y);
let threed_switch_origin = Point2DI32::new(threed_switch_x, open_button_y);
self.three_d_enabled = self.draw_text_switch(debug_ui, self.three_d_enabled = self.draw_text_switch(debug_ui,
event, event,
threed_switch_origin, three_d_switch_origin,
"2D", "2D",
"3D", "3D",
self.three_d_enabled); self.three_d_enabled);
// Draw background switch. // Draw background switch.
let background_switch_origin = Point2DI32::new(BACKGROUND_SWITCH_X, open_button_y); let background_switch_origin = Point2DI32::new(BACKGROUND_SWITCH_X, lower_button_y);
self.dark_background_enabled = self.draw_image_switch(debug_ui, self.dark_background_enabled = self.draw_image_switch(debug_ui,
event, event,
background_switch_origin, background_switch_origin,
@ -354,6 +370,7 @@ impl DemoUI {
pub enum UIAction { pub enum UIAction {
None, None,
OpenFile(PathBuf), OpenFile(PathBuf),
TakeScreenshot(PathBuf),
ZoomIn, ZoomIn,
ZoomOut, ZoomOut,
Rotate(f32), Rotate(f32),

Binary file not shown.

After

Width:  |  Height:  |  Size: 885 B