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(
35        rgba: impl Into<Bytes>,
36        size: Size<u32>,
37        scale_factor: f32,
38    ) -> Self {
39        Self {
40            rgba: rgba.into(),
41            size,
42            scale_factor,
43        }
44    }
45
46    /// Crops a [`Screenshot`] to the provided `region`. This will always be relative to the
47    /// top-left corner of the [`Screenshot`].
48    pub fn crop(&self, region: Rectangle<u32>) -> Result<Self, CropError> {
49        if region.width == 0 || region.height == 0 {
50            return Err(CropError::Zero);
51        }
52
53        if region.x + region.width > self.size.width
54            || region.y + region.height > self.size.height
55        {
56            return Err(CropError::OutOfBounds);
57        }
58
59        // Image is always RGBA8 = 4 bytes per pixel
60        const PIXEL_SIZE: usize = 4;
61
62        let bytes_per_row = self.size.width as usize * PIXEL_SIZE;
63        let row_range = region.y as usize..(region.y + region.height) as usize;
64        let column_range = region.x as usize * PIXEL_SIZE
65            ..(region.x + region.width) as usize * PIXEL_SIZE;
66
67        let chopped = self.rgba.chunks(bytes_per_row).enumerate().fold(
68            vec![],
69            |mut acc, (row, bytes)| {
70                if row_range.contains(&row) {
71                    acc.extend(&bytes[column_range.clone()]);
72                }
73
74                acc
75            },
76        );
77
78        Ok(Self {
79            rgba: Bytes::from(chopped),
80            size: Size::new(region.width, region.height),
81            scale_factor: self.scale_factor,
82        })
83    }
84}
85
86impl AsRef<[u8]> for Screenshot {
87    fn as_ref(&self) -> &[u8] {
88        &self.rgba
89    }
90}
91
92impl From<Screenshot> for Bytes {
93    fn from(screenshot: Screenshot) -> Self {
94        screenshot.rgba
95    }
96}
97
98#[derive(Debug, thiserror::Error)]
99/// Errors that can occur when cropping a [`Screenshot`].
100pub enum CropError {
101    #[error("The cropped region is out of bounds.")]
102    /// The cropped region's size is out of bounds.
103    OutOfBounds,
104    #[error("The cropped region is not visible.")]
105    /// The cropped region's size is zero.
106    Zero,
107}