From 233196eb14b40f8bd5201ea0262571f82136ad53 Mon Sep 17 00:00:00 2001 From: Bingus Date: Sat, 25 Mar 2023 10:45:39 -0700 Subject: Added offscreen rendering support for wgpu & tiny-skia exposed with the window::screenshot command. --- runtime/src/lib.rs | 2 ++ runtime/src/screenshot.rs | 80 ++++++++++++++++++++++++++++++++++++++++++++ runtime/src/window.rs | 8 +++++ runtime/src/window/action.rs | 9 +++++ 4 files changed, 99 insertions(+) create mode 100644 runtime/src/screenshot.rs (limited to 'runtime') diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 50abf7b2..32ed14d8 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -60,6 +60,7 @@ mod debug; #[cfg(not(feature = "debug"))] #[path = "debug/null.rs"] mod debug; +mod screenshot; pub use iced_core as core; pub use iced_futures as futures; @@ -68,4 +69,5 @@ pub use command::Command; pub use debug::Debug; pub use font::Font; pub use program::Program; +pub use screenshot::{CropError, Screenshot}; pub use user_interface::UserInterface; diff --git a/runtime/src/screenshot.rs b/runtime/src/screenshot.rs new file mode 100644 index 00000000..527e400f --- /dev/null +++ b/runtime/src/screenshot.rs @@ -0,0 +1,80 @@ +use iced_core::{Rectangle, Size}; +use std::fmt::{Debug, Formatter}; + +/// Data of a screenshot, captured with `window::screenshot()`. +/// +/// The `bytes` of this screenshot will always be ordered as `RGBA` in the sRGB color space. +#[derive(Clone)] +pub struct Screenshot { + /// The bytes of the [`Screenshot`]. + pub bytes: Vec, + /// The size of the [`Screenshot`]. + pub size: Size, +} + +impl Debug for Screenshot { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Screenshot: {{ \n bytes: {}\n size: {:?} }}", + self.bytes.len(), + self.size + ) + } +} + +impl Screenshot { + /// Creates a new [`Screenshot`]. + pub fn new(bytes: Vec, size: Size) -> Self { + Self { bytes, size } + } + + /// Crops a [`Screenshot`] to the provided `region`. This will always be relative to the + /// top-left corner of the [`Screenshot`]. + pub fn crop(&self, region: Rectangle) -> Result { + if region.width == 0 || region.height == 0 { + return Err(CropError::Zero); + } + + if region.x + region.width > self.size.width + || region.y + region.height > self.size.height + { + return Err(CropError::OutOfBounds); + } + + // Image is always RGBA8 = 4 bytes per pixel + const PIXEL_SIZE: usize = 4; + + let bytes_per_row = self.size.width as usize * PIXEL_SIZE; + let row_range = region.y as usize..(region.y + region.height) as usize; + let column_range = region.x as usize * PIXEL_SIZE + ..(region.x + region.width) as usize * PIXEL_SIZE; + + let chopped = self.bytes.chunks(bytes_per_row).enumerate().fold( + vec![], + |mut acc, (row, bytes)| { + if row_range.contains(&row) { + acc.extend(&bytes[column_range.clone()]); + } + + acc + }, + ); + + Ok(Self { + bytes: chopped, + size: Size::new(region.width, region.height), + }) + } +} + +#[derive(Debug, thiserror::Error)] +/// Errors that can occur when cropping a [`Screenshot`]. +pub enum CropError { + #[error("The cropped region is out of bounds.")] + /// The cropped region's size is out of bounds. + OutOfBounds, + #[error("The cropped region is not visible.")] + /// The cropped region's size is zero. + Zero, +} diff --git a/runtime/src/window.rs b/runtime/src/window.rs index d4111293..9b66cb0e 100644 --- a/runtime/src/window.rs +++ b/runtime/src/window.rs @@ -7,6 +7,7 @@ use crate::command::{self, Command}; use crate::core::time::Instant; use crate::core::window::{Event, Icon, Level, Mode, UserAttention}; use crate::futures::subscription::{self, Subscription}; +use crate::screenshot::Screenshot; /// Subscribes to the frames of the window of the running application. /// @@ -115,3 +116,10 @@ pub fn fetch_id( pub fn change_icon(icon: Icon) -> Command { Command::single(command::Action::Window(Action::ChangeIcon(icon))) } + +/// Captures a [`Screenshot`] from the window. +pub fn screenshot( + f: impl FnOnce(Screenshot) -> Message + Send + 'static, +) -> Command { + Command::single(command::Action::Window(Action::Screenshot(Box::new(f)))) +} diff --git a/runtime/src/window/action.rs b/runtime/src/window/action.rs index a9d2a3d0..cb430681 100644 --- a/runtime/src/window/action.rs +++ b/runtime/src/window/action.rs @@ -1,6 +1,7 @@ use crate::core::window::{Icon, Level, Mode, UserAttention}; use crate::futures::MaybeSend; +use crate::screenshot::Screenshot; use std::fmt; /// An operation to be performed on some window. @@ -89,6 +90,8 @@ pub enum Action { /// - **X11:** Has no universal guidelines for icon sizes, so you're at the whims of the WM. That /// said, it's usually in the same ballpark as on Windows. ChangeIcon(Icon), + /// Screenshot the viewport of the window. + Screenshot(Box T + 'static>), } impl Action { @@ -118,6 +121,11 @@ impl Action { Self::ChangeLevel(level) => Action::ChangeLevel(level), Self::FetchId(o) => Action::FetchId(Box::new(move |s| f(o(s)))), Self::ChangeIcon(icon) => Action::ChangeIcon(icon), + Self::Screenshot(tag) => { + Action::Screenshot(Box::new(move |screenshot| { + f(tag(screenshot)) + })) + } } } } @@ -155,6 +163,7 @@ impl fmt::Debug for Action { Self::ChangeIcon(_icon) => { write!(f, "Action::ChangeIcon(icon)") } + Self::Screenshot(_) => write!(f, "Action::Screenshot"), } } } -- cgit From 5324928044cba800454b1861eb9999038bc28c2e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 6 Jun 2023 16:14:42 +0200 Subject: Wrap `Screenshot::bytes` in an `Arc` and implement `AsRef<[u8]>` --- runtime/src/screenshot.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) (limited to 'runtime') diff --git a/runtime/src/screenshot.rs b/runtime/src/screenshot.rs index 527e400f..b88f2c20 100644 --- a/runtime/src/screenshot.rs +++ b/runtime/src/screenshot.rs @@ -1,5 +1,7 @@ -use iced_core::{Rectangle, Size}; +use crate::core::{Rectangle, Size}; + use std::fmt::{Debug, Formatter}; +use std::sync::Arc; /// Data of a screenshot, captured with `window::screenshot()`. /// @@ -7,7 +9,7 @@ use std::fmt::{Debug, Formatter}; #[derive(Clone)] pub struct Screenshot { /// The bytes of the [`Screenshot`]. - pub bytes: Vec, + pub bytes: Arc>, /// The size of the [`Screenshot`]. pub size: Size, } @@ -26,7 +28,10 @@ impl Debug for Screenshot { impl Screenshot { /// Creates a new [`Screenshot`]. pub fn new(bytes: Vec, size: Size) -> Self { - Self { bytes, size } + Self { + bytes: Arc::new(bytes), + size, + } } /// Crops a [`Screenshot`] to the provided `region`. This will always be relative to the @@ -62,12 +67,18 @@ impl Screenshot { ); Ok(Self { - bytes: chopped, + bytes: Arc::new(chopped), size: Size::new(region.width, region.height), }) } } +impl AsRef<[u8]> for Screenshot { + fn as_ref(&self) -> &[u8] { + &self.bytes + } +} + #[derive(Debug, thiserror::Error)] /// Errors that can occur when cropping a [`Screenshot`]. pub enum CropError { -- cgit From 5ae726e02c4d6c9889ef7335d9bc80ef1992e34f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 27 Jun 2023 19:41:03 +0200 Subject: Move `Screenshot` inside `window` module --- runtime/src/lib.rs | 2 - runtime/src/screenshot.rs | 91 --------------------------------------- runtime/src/window.rs | 4 +- runtime/src/window/action.rs | 2 +- runtime/src/window/screenshot.rs | 92 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 96 insertions(+), 95 deletions(-) delete mode 100644 runtime/src/screenshot.rs create mode 100644 runtime/src/window/screenshot.rs (limited to 'runtime') diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 32ed14d8..50abf7b2 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -60,7 +60,6 @@ mod debug; #[cfg(not(feature = "debug"))] #[path = "debug/null.rs"] mod debug; -mod screenshot; pub use iced_core as core; pub use iced_futures as futures; @@ -69,5 +68,4 @@ pub use command::Command; pub use debug::Debug; pub use font::Font; pub use program::Program; -pub use screenshot::{CropError, Screenshot}; pub use user_interface::UserInterface; diff --git a/runtime/src/screenshot.rs b/runtime/src/screenshot.rs deleted file mode 100644 index b88f2c20..00000000 --- a/runtime/src/screenshot.rs +++ /dev/null @@ -1,91 +0,0 @@ -use crate::core::{Rectangle, Size}; - -use std::fmt::{Debug, Formatter}; -use std::sync::Arc; - -/// Data of a screenshot, captured with `window::screenshot()`. -/// -/// The `bytes` of this screenshot will always be ordered as `RGBA` in the sRGB color space. -#[derive(Clone)] -pub struct Screenshot { - /// The bytes of the [`Screenshot`]. - pub bytes: Arc>, - /// The size of the [`Screenshot`]. - pub size: Size, -} - -impl Debug for Screenshot { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!( - f, - "Screenshot: {{ \n bytes: {}\n size: {:?} }}", - self.bytes.len(), - self.size - ) - } -} - -impl Screenshot { - /// Creates a new [`Screenshot`]. - pub fn new(bytes: Vec, size: Size) -> Self { - Self { - bytes: Arc::new(bytes), - size, - } - } - - /// Crops a [`Screenshot`] to the provided `region`. This will always be relative to the - /// top-left corner of the [`Screenshot`]. - pub fn crop(&self, region: Rectangle) -> Result { - if region.width == 0 || region.height == 0 { - return Err(CropError::Zero); - } - - if region.x + region.width > self.size.width - || region.y + region.height > self.size.height - { - return Err(CropError::OutOfBounds); - } - - // Image is always RGBA8 = 4 bytes per pixel - const PIXEL_SIZE: usize = 4; - - let bytes_per_row = self.size.width as usize * PIXEL_SIZE; - let row_range = region.y as usize..(region.y + region.height) as usize; - let column_range = region.x as usize * PIXEL_SIZE - ..(region.x + region.width) as usize * PIXEL_SIZE; - - let chopped = self.bytes.chunks(bytes_per_row).enumerate().fold( - vec![], - |mut acc, (row, bytes)| { - if row_range.contains(&row) { - acc.extend(&bytes[column_range.clone()]); - } - - acc - }, - ); - - Ok(Self { - bytes: Arc::new(chopped), - size: Size::new(region.width, region.height), - }) - } -} - -impl AsRef<[u8]> for Screenshot { - fn as_ref(&self) -> &[u8] { - &self.bytes - } -} - -#[derive(Debug, thiserror::Error)] -/// Errors that can occur when cropping a [`Screenshot`]. -pub enum CropError { - #[error("The cropped region is out of bounds.")] - /// The cropped region's size is out of bounds. - OutOfBounds, - #[error("The cropped region is not visible.")] - /// The cropped region's size is zero. - Zero, -} diff --git a/runtime/src/window.rs b/runtime/src/window.rs index 9b66cb0e..e448edef 100644 --- a/runtime/src/window.rs +++ b/runtime/src/window.rs @@ -1,13 +1,15 @@ //! Build window-based GUI applications. mod action; +pub mod screenshot; + pub use action::Action; +pub use screenshot::Screenshot; use crate::command::{self, Command}; use crate::core::time::Instant; use crate::core::window::{Event, Icon, Level, Mode, UserAttention}; use crate::futures::subscription::{self, Subscription}; -use crate::screenshot::Screenshot; /// Subscribes to the frames of the window of the running application. /// diff --git a/runtime/src/window/action.rs b/runtime/src/window/action.rs index cb430681..09be1810 100644 --- a/runtime/src/window/action.rs +++ b/runtime/src/window/action.rs @@ -1,7 +1,7 @@ use crate::core::window::{Icon, Level, Mode, UserAttention}; use crate::futures::MaybeSend; +use crate::window::Screenshot; -use crate::screenshot::Screenshot; use std::fmt; /// An operation to be performed on some window. diff --git a/runtime/src/window/screenshot.rs b/runtime/src/window/screenshot.rs new file mode 100644 index 00000000..c84286b6 --- /dev/null +++ b/runtime/src/window/screenshot.rs @@ -0,0 +1,92 @@ +//! Take screenshots of a window. +use crate::core::{Rectangle, Size}; + +use std::fmt::{Debug, Formatter}; +use std::sync::Arc; + +/// Data of a screenshot, captured with `window::screenshot()`. +/// +/// The `bytes` of this screenshot will always be ordered as `RGBA` in the sRGB color space. +#[derive(Clone)] +pub struct Screenshot { + /// The bytes of the [`Screenshot`]. + pub bytes: Arc>, + /// The size of the [`Screenshot`]. + pub size: Size, +} + +impl Debug for Screenshot { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Screenshot: {{ \n bytes: {}\n size: {:?} }}", + self.bytes.len(), + self.size + ) + } +} + +impl Screenshot { + /// Creates a new [`Screenshot`]. + pub fn new(bytes: Vec, size: Size) -> Self { + Self { + bytes: Arc::new(bytes), + size, + } + } + + /// Crops a [`Screenshot`] to the provided `region`. This will always be relative to the + /// top-left corner of the [`Screenshot`]. + pub fn crop(&self, region: Rectangle) -> Result { + if region.width == 0 || region.height == 0 { + return Err(CropError::Zero); + } + + if region.x + region.width > self.size.width + || region.y + region.height > self.size.height + { + return Err(CropError::OutOfBounds); + } + + // Image is always RGBA8 = 4 bytes per pixel + const PIXEL_SIZE: usize = 4; + + let bytes_per_row = self.size.width as usize * PIXEL_SIZE; + let row_range = region.y as usize..(region.y + region.height) as usize; + let column_range = region.x as usize * PIXEL_SIZE + ..(region.x + region.width) as usize * PIXEL_SIZE; + + let chopped = self.bytes.chunks(bytes_per_row).enumerate().fold( + vec![], + |mut acc, (row, bytes)| { + if row_range.contains(&row) { + acc.extend(&bytes[column_range.clone()]); + } + + acc + }, + ); + + Ok(Self { + bytes: Arc::new(chopped), + size: Size::new(region.width, region.height), + }) + } +} + +impl AsRef<[u8]> for Screenshot { + fn as_ref(&self) -> &[u8] { + &self.bytes + } +} + +#[derive(Debug, thiserror::Error)] +/// Errors that can occur when cropping a [`Screenshot`]. +pub enum CropError { + #[error("The cropped region is out of bounds.")] + /// The cropped region's size is out of bounds. + OutOfBounds, + #[error("The cropped region is not visible.")] + /// The cropped region's size is zero. + Zero, +} -- cgit