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