iced_core/window/
screenshot.rs

1//! Take screenshots of a window.
2use crate::{Bytes, Rectangle, Size};
3
4use std::fmt::{Debug, Formatter};
5
6/// Data of a screenshot, captured with `window::screenshot()`.
7///
8/// The `bytes` of this screenshot will always be ordered as `RGBA` in the `sRGB` color space.
9#[derive(Clone)]
10pub struct Screenshot {
11    /// The RGBA bytes of the [`Screenshot`].
12    pub rgba: Bytes,
13    /// The size of the [`Screenshot`] in physical pixels.
14    pub size: Size<u32>,
15    /// The scale factor of the [`Screenshot`]. This can be useful when converting between widget
16    /// bounds (which are in logical pixels) to crop screenshots.
17    pub scale_factor: f32,
18}
19
20impl Debug for Screenshot {
21    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
22        write!(
23            f,
24            "Screenshot: {{ \n bytes: {}\n scale: {}\n size: {:?} }}",
25            self.rgba.len(),
26            self.scale_factor,
27            self.size
28        )
29    }
30}
31
32impl Screenshot {
33    /// Creates a new [`Screenshot`].
34    pub fn new(rgba: impl Into<Bytes>, size: Size<u32>, scale_factor: f32) -> Self {
35        Self {
36            rgba: rgba.into(),
37            size,
38            scale_factor,
39        }
40    }
41
42    /// Crops a [`Screenshot`] to the provided `region`. This will always be relative to the
43    /// top-left corner of the [`Screenshot`].
44    pub fn crop(&self, region: Rectangle<u32>) -> Result<Self, CropError> {
45        if region.width == 0 || region.height == 0 {
46            return Err(CropError::Zero);
47        }
48
49        if region.x + region.width > self.size.width || region.y + region.height > self.size.height
50        {
51            return Err(CropError::OutOfBounds);
52        }
53
54        // Image is always RGBA8 = 4 bytes per pixel
55        const PIXEL_SIZE: usize = 4;
56
57        let bytes_per_row = self.size.width as usize * PIXEL_SIZE;
58        let row_range = region.y as usize..(region.y + region.height) as usize;
59        let column_range =
60            region.x as usize * PIXEL_SIZE..(region.x + region.width) as usize * PIXEL_SIZE;
61
62        let chopped =
63            self.rgba
64                .chunks(bytes_per_row)
65                .enumerate()
66                .fold(vec![], |mut acc, (row, bytes)| {
67                    if row_range.contains(&row) {
68                        acc.extend(&bytes[column_range.clone()]);
69                    }
70
71                    acc
72                });
73
74        Ok(Self {
75            rgba: Bytes::from(chopped),
76            size: Size::new(region.width, region.height),
77            scale_factor: self.scale_factor,
78        })
79    }
80}
81
82impl AsRef<[u8]> for Screenshot {
83    fn as_ref(&self) -> &[u8] {
84        &self.rgba
85    }
86}
87
88impl From<Screenshot> for Bytes {
89    fn from(screenshot: Screenshot) -> Self {
90        screenshot.rgba
91    }
92}
93
94#[derive(Debug, thiserror::Error)]
95/// Errors that can occur when cropping a [`Screenshot`].
96pub enum CropError {
97    #[error("The cropped region is out of bounds.")]
98    /// The cropped region's size is out of bounds.
99    OutOfBounds,
100    #[error("The cropped region is not visible.")]
101    /// The cropped region's size is zero.
102    Zero,
103}