iced_core/window/
screenshot.rs

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