diff options
author | 2023-06-27 20:37:19 +0200 | |
---|---|---|
committer | 2023-06-27 20:37:19 +0200 | |
commit | f6966268bb6d58b4b03ba61fc5732e1bf016e2a1 (patch) | |
tree | c8e79670b59fdd19103cb1a3a6d18783136e2294 /runtime/src | |
parent | ef18ecf4ef0fe654578380059b467fe2fb34aa0a (diff) | |
parent | 5b6e205e998cbb20b3c8aaff8b515d78315d6703 (diff) | |
download | iced-f6966268bb6d58b4b03ba61fc5732e1bf016e2a1.tar.gz iced-f6966268bb6d58b4b03ba61fc5732e1bf016e2a1.tar.bz2 iced-f6966268bb6d58b4b03ba61fc5732e1bf016e2a1.zip |
Merge pull request #1845 from bungoboingo/feat/offscreen-rendering
Feat: Offscreen Rendering & Screenshots
Diffstat (limited to 'runtime/src')
-rw-r--r-- | runtime/src/window.rs | 10 | ||||
-rw-r--r-- | runtime/src/window/action.rs | 9 | ||||
-rw-r--r-- | runtime/src/window/screenshot.rs | 92 |
3 files changed, 111 insertions, 0 deletions
diff --git a/runtime/src/window.rs b/runtime/src/window.rs index d4111293..e448edef 100644 --- a/runtime/src/window.rs +++ b/runtime/src/window.rs @@ -1,7 +1,10 @@ //! 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; @@ -115,3 +118,10 @@ pub fn fetch_id<Message>( pub fn change_icon<Message>(icon: Icon) -> Command<Message> { Command::single(command::Action::Window(Action::ChangeIcon(icon))) } + +/// Captures a [`Screenshot`] from the window. +pub fn screenshot<Message>( + f: impl FnOnce(Screenshot) -> Message + Send + 'static, +) -> Command<Message> { + 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..09be1810 100644 --- a/runtime/src/window/action.rs +++ b/runtime/src/window/action.rs @@ -1,5 +1,6 @@ use crate::core::window::{Icon, Level, Mode, UserAttention}; use crate::futures::MaybeSend; +use crate::window::Screenshot; use std::fmt; @@ -89,6 +90,8 @@ pub enum Action<T> { /// - **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<dyn FnOnce(Screenshot) -> T + 'static>), } impl<T> Action<T> { @@ -118,6 +121,11 @@ impl<T> Action<T> { 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<T> fmt::Debug for Action<T> { Self::ChangeIcon(_icon) => { write!(f, "Action::ChangeIcon(icon)") } + Self::Screenshot(_) => write!(f, "Action::Screenshot"), } } } 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<Vec<u8>>, + /// The size of the [`Screenshot`]. + pub size: Size<u32>, +} + +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<u8>, size: Size<u32>) -> 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<u32>) -> Result<Self, CropError> { + 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, +} |