firefly_rust/graphics/
canvas.rs

1use crate::*;
2#[cfg(feature = "alloc")]
3use alloc::boxed::Box;
4#[cfg(feature = "alloc")]
5use alloc::vec;
6
7// TODO: add statically-allocated version when prepare_slice can be turned into static fn.
8// It is blocked by this feature going into stable:
9// https://github.com/rust-lang/rust/issues/57349
10
11/// Canvas is an [`Image`] that can be drawn upon.
12///
13/// [`CanvasBuf`] is the same as [`Canvas`] but holds the ownership of the underlying slice.
14#[cfg(feature = "alloc")]
15pub struct CanvasBuf {
16    pub(crate) raw: Box<[u8]>,
17}
18
19#[cfg(feature = "alloc")]
20impl CanvasBuf {
21    /// Create new empty canvas.
22    #[must_use]
23    #[expect(clippy::cast_sign_loss)]
24    pub fn new(s: Size) -> Self {
25        const HEADER_SIZE: usize = 5 + 8;
26        let body_size = s.width * s.height / 2;
27        let mut raw = vec![0; HEADER_SIZE + body_size as usize];
28        prepare_slice(&mut raw, s.width);
29        Self {
30            raw: raw.into_boxed_slice(),
31        }
32    }
33
34    /// Represent the canvas as an [`Image`].
35    #[must_use]
36    pub const fn as_image(&self) -> Image<'_> {
37        Image { raw: &self.raw }
38    }
39
40    /// Represent the buffered canvas as [`Canvas`].
41    #[must_use]
42    pub const fn as_canvas(&self) -> Canvas<'_> {
43        Canvas { raw: &self.raw }
44    }
45}
46
47/// Canvas is an [`Image`] that can be drawn upon.
48pub struct Canvas<'a> {
49    pub(crate) raw: &'a [u8],
50}
51
52impl<'a> Canvas<'a> {
53    /// Create new empty canvas using the given slice.
54    ///
55    /// Returns [`None`] if the slice is too small for the given image size.
56    /// A bigger slice than needed is fine.
57    #[must_use]
58    #[expect(clippy::cast_sign_loss)]
59    pub fn new(s: Size, raw: &'a mut [u8]) -> Option<Self> {
60        const HEADER_SIZE: usize = 5 + 8;
61        let body_size = s.width * s.height / 2;
62        let exp_size = HEADER_SIZE + body_size as usize;
63        if raw.len() < exp_size {
64            return None;
65        }
66        prepare_slice(raw, s.width);
67        Some(Self {
68            raw: &raw[..exp_size],
69        })
70    }
71
72    /// Represent the canvas as an [`Image`].
73    #[must_use]
74    pub const fn as_image(&self) -> Image<'a> {
75        Image { raw: self.raw }
76    }
77}
78
79#[cfg(feature = "alloc")]
80impl<'a> From<&'a CanvasBuf> for Canvas<'a> {
81    fn from(value: &'a CanvasBuf) -> Self {
82        Self { raw: &value.raw }
83    }
84}
85
86#[expect(clippy::cast_sign_loss)]
87fn prepare_slice(raw: &mut [u8], width: i32) {
88    raw[0] = 0x21; // magic number
89    raw[1] = 4; // BPP
90    raw[2] = width as u8; // width
91    raw[3] = (width >> 8) as u8; // width
92    raw[4] = 255; // transparency
93
94    // color swaps
95    for i in 0u8..8u8 {
96        raw[5 + i as usize] = ((i * 2) << 4) | (i * 2 + 1);
97    }
98}