diff --git a/Cargo.lock b/Cargo.lock index 4f18eeb0..0406dd67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -504,6 +504,7 @@ version = "0.1.0" dependencies = [ "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)", + "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)", "nfd 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "pathfinder_geometry 0.3.0", diff --git a/demo/common/Cargo.toml b/demo/common/Cargo.toml index f9034c01..45916487 100644 --- a/demo/common/Cargo.toml +++ b/demo/common/Cargo.toml @@ -13,6 +13,11 @@ rayon = "1.0" sdl2 = "0.32" usvg = "0.4" +[dependencies.image] +version = "0.21" +default-features = false +features = ["png_codec"] + [dependencies.pathfinder_geometry] path = "../../geometry" diff --git a/demo/common/src/lib.rs b/demo/common/src/lib.rs index bd7ad471..10372630 100644 --- a/demo/common/src/lib.rs +++ b/demo/common/src/lib.rs @@ -12,7 +12,8 @@ use crate::ui::{DemoUI, UIAction, UIEvent}; use clap::{App, Arg}; -use gl::types::GLsizei; +use gl::types::{GLsizei, GLvoid}; +use image::ColorType; use jemallocator; use pathfinder_geometry::basic::point::{Point2DF32, Point2DI32, Point3DF32}; use pathfinder_geometry::basic::rect::RectF32; @@ -89,6 +90,7 @@ pub struct DemoApp { camera: Camera, frame_counter: u32, events: Vec, + pending_screenshot_path: Option, exit: bool, mouselook_enabled: bool, dirty: bool, @@ -164,6 +166,7 @@ impl DemoApp { camera, frame_counter: 0, + pending_screenshot_path: None, events: vec![], exit: false, mouselook_enabled: false, @@ -342,6 +345,10 @@ impl DemoApp { self.draw_environment(&render_transform); self.render_vector_scene(&built_scene); + if self.pending_screenshot_path.is_some() { + self.take_screenshot(); + } + let rendering_time = self.renderer.shift_timer_query(); self.renderer.debug_ui.add_sample(tile_time, rendering_time); self.renderer.debug_ui.draw(); @@ -499,6 +506,11 @@ impl DemoApp { self.dirty = true; } + UIAction::TakeScreenshot(ref path) => { + self.pending_screenshot_path = Some((*path).clone()); + self.dirty = true; + } + UIAction::ZoomIn => { if let Camera::TwoD(ref mut transform) = self.camera { 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 { 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); } } + + fn readback_pixels(&self, width: u32, height: u32) -> Vec { + 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 { diff --git a/demo/common/src/ui.rs b/demo/common/src/ui.rs index d223cb99..bcfd5776 100644 --- a/demo/common/src/ui.rs +++ b/demo/common/src/ui.rs @@ -30,19 +30,23 @@ const SLIDER_KNOB_HEIGHT: i32 = 48; const EFFECTS_PANEL_WIDTH: i32 = 550; 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_HEIGHT: i32 = PADDING * 2 + SLIDER_HEIGHT; -static EFFECTS_PNG_NAME: &'static str = "demo-effects"; -static OPEN_PNG_NAME: &'static str = "demo-open"; -static ROTATE_PNG_NAME: &'static str = "demo-rotate"; -static ZOOM_IN_PNG_NAME: &'static str = "demo-zoom-in"; -static ZOOM_OUT_PNG_NAME: &'static str = "demo-zoom-out"; -static BG_LIGHT_PNG_NAME: &'static str = "demo-bg-light"; -static BG_DARK_PNG_NAME: &'static str = "demo-bg-dark"; +static EFFECTS_PNG_NAME: &'static str = "demo-effects"; +static OPEN_PNG_NAME: &'static str = "demo-open"; +static ROTATE_PNG_NAME: &'static str = "demo-rotate"; +static ZOOM_IN_PNG_NAME: &'static str = "demo-zoom-in"; +static ZOOM_OUT_PNG_NAME: &'static str = "demo-zoom-out"; +static BG_LIGHT_PNG_NAME: &'static str = "demo-bg-light"; +static BG_DARK_PNG_NAME: &'static str = "demo-bg-dark"; +static SCREENSHOT_PNG_NAME: &'static str = "demo-screenshot"; pub struct DemoUI { effects_texture: Texture, @@ -52,6 +56,7 @@ pub struct DemoUI { zoom_out_texture: Texture, bg_light_texture: Texture, bg_dark_texture: Texture, + screenshot_texture: Texture, effects_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 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 screenshot_texture = device.create_texture_from_png(SCREENSHOT_PNG_NAME); DemoUI { effects_texture, @@ -82,6 +88,7 @@ impl DemoUI { zoom_out_texture, bg_light_texture, bg_dark_texture, + screenshot_texture, three_d_enabled: options.three_d, dark_background_enabled: true, effects_panel_visible: false, @@ -107,27 +114,36 @@ impl DemoUI { } // Draw open button. - let open_button_x = PADDING + BUTTON_WIDTH + PADDING; - let open_button_y = bottom - BUTTON_HEIGHT; - let open_button_position = Point2DI32::new(open_button_x, open_button_y); + let lower_button_y = bottom - BUTTON_HEIGHT; + let open_button_position = Point2DI32::new(OPEN_BUTTON_X, lower_button_y); 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) { *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. - let threed_switch_x = PADDING + (BUTTON_WIDTH + PADDING) * 2; - let threed_switch_origin = Point2DI32::new(threed_switch_x, open_button_y); + let three_d_switch_origin = Point2DI32::new(THREE_D_SWITCH_X, lower_button_y); self.three_d_enabled = self.draw_text_switch(debug_ui, event, - threed_switch_origin, + three_d_switch_origin, "2D", "3D", self.three_d_enabled); // 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, event, background_switch_origin, @@ -354,6 +370,7 @@ impl DemoUI { pub enum UIAction { None, OpenFile(PathBuf), + TakeScreenshot(PathBuf), ZoomIn, ZoomOut, Rotate(f32), diff --git a/resources/textures/demo-screenshot.png b/resources/textures/demo-screenshot.png new file mode 100644 index 00000000..d257a515 Binary files /dev/null and b/resources/textures/demo-screenshot.png differ