wasm4_common/
draw.rs

1#[derive(Clone, Copy)]
2pub struct Sprite<Bytes: ?Sized = [u8]> {
3    shape: [u32; 2],
4    bpp: BitsPerPixel,
5    pub indices: DrawIndices,
6    bytes: Bytes,
7}
8
9impl<const N: usize> Sprite<[u8; N]> {
10    /// Create a sprite from an array of bytes and other metadata.
11    ///
12    /// # Errors
13    ///
14    /// Returns `None` if bytes wont fit the given sprite `shape` and `bpp`.
15    pub const fn from_byte_array(
16        bytes: [u8; N],
17        shape: [u32; 2],
18        bpp: BitsPerPixel,
19        draw_colors: DrawIndices,
20    ) -> Option<Self> {
21        let resolution = shape[0].checked_mul(shape[1]);
22        let capacity = N.checked_mul(1 << (3 - bpp as u32));
23
24        match (resolution, capacity) {
25            // SAFETY: calling unsafe function after the check
26            (Some(resolution), Some(capacity)) if resolution as usize <= capacity => unsafe {
27                Some(Self::from_bytes_unchecked(bytes, shape, bpp, draw_colors))
28            },
29            _ => None,
30        }
31    }
32}
33
34impl<Bytes> Sprite<Bytes> {
35    /// Create a sprite from an array of bytes and other metadata without a
36    /// safety check.
37    ///
38    /// # Safety
39    ///
40    /// Invalidates type's safety invariant [`Sprite::from_byte_array`] would
41    /// return `None`. It wont error if safety requirement is violated during
42    /// const evaluation, instead resulting in undefined behavior when returned
43    /// object's methods are called.
44    pub const unsafe fn from_bytes_unchecked(
45        bytes: Bytes,
46        shape: [u32; 2],
47        bpp: BitsPerPixel,
48        draw_colors: DrawIndices,
49    ) -> Self {
50        Sprite {
51            shape,
52            bpp,
53            indices: draw_colors,
54            bytes,
55        }
56    }
57}
58
59impl<Bytes: AsRef<[u8]>> Sprite<Bytes> {
60    pub fn from_bytes(
61        bytes: Bytes,
62        shape: [u32; 2],
63        bpp: BitsPerPixel,
64        draw_colors: DrawIndices,
65    ) -> Option<Self> {
66        let resolution = shape[0].checked_mul(shape[1]);
67        let capacity = bytes.as_ref().len().checked_mul(1 << (3 - bpp as u32));
68
69        match (resolution, capacity) {
70            // SAFETY: calling unsafe function after the check
71            (Some(resolution), Some(capacity)) if resolution as usize <= capacity => unsafe {
72                Some(Self::from_bytes_unchecked(bytes, shape, bpp, draw_colors))
73            },
74            _ => None,
75        }
76    }
77}
78
79impl<Bytes: ?Sized> Sprite<Bytes> {
80    /// Get a reference to the sprite's bytes.
81    pub const fn bytes(&self) -> &Bytes {
82        &self.bytes
83    }
84
85    /// Get the sprite's shape.
86    pub const fn shape(&self) -> [u32; 2] {
87        self.shape
88    }
89
90    /// Get the sprite's width.
91    pub const fn width(&self) -> u32 {
92        self.shape[0]
93    }
94
95    /// Get the sprite's height.
96    pub const fn height(&self) -> u32 {
97        self.shape[1]
98    }
99
100    /// Get the sprite's bpp (bits per pixel).
101    pub const fn bpp(&self) -> BitsPerPixel {
102        self.bpp
103    }
104}
105
106impl Sprite {
107    /// Create a subview to the sprite. Returns `None` if subview is out of bounds of the sprite.
108    pub const fn view(&self, start: [u32; 2], shape: [u32; 2]) -> Option<SpriteView<'_>> {
109        let dst_bottom_right = match checked_add_pairs(start, shape) {
110            Some(it) => it,
111            None => return None,
112        };
113        if lt_pairs(start, self.shape) && le_pairs(dst_bottom_right, self.shape) {
114            Some(SpriteView {
115                sprite: self,
116                start,
117                shape,
118            })
119        } else {
120            None
121        }
122    }
123
124    /// Create a subview to the sprite. Does not perform in bounds checks.
125    ///
126    /// # Safety
127    ///
128    /// Resulting subview should be inside of the bounds of the `Sprite`
129    pub const unsafe fn view_unchecked(&self, start: [u32; 2], shape: [u32; 2]) -> SpriteView<'_> {
130        SpriteView {
131            sprite: self,
132            start,
133            shape,
134        }
135    }
136}
137
138#[repr(u32)]
139#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
140pub enum BitsPerPixel {
141    One,
142    Two,
143}
144
145#[derive(Clone, Copy)]
146pub struct SpriteView<'a> {
147    sprite: &'a Sprite,
148    start: [u32; 2],
149    shape: [u32; 2],
150}
151
152impl<'a> SpriteView<'a> {
153    /// Get the view's underlying sprite.
154    pub const fn sprite(&self) -> &'a Sprite {
155        self.sprite
156    }
157
158    /// Get the sprite view's start.
159    pub const fn start(&self) -> [u32; 2] {
160        self.start
161    }
162
163    /// Get the sprite view's shape.
164    pub const fn shape(&self) -> [u32; 2] {
165        self.shape
166    }
167}
168
169#[derive(Clone, Copy, Default)]
170pub struct DrawIndices(u16);
171
172impl DrawIndices {
173    pub const TRANSPARENT: Self = DrawIndices(0);
174
175    pub const fn from_array(array: [DrawIndex; 4]) -> Self {
176        DrawIndices(
177            array[0] as u16
178                | (array[1] as u16) << 4
179                | (array[2] as u16) << 8
180                | (array[3] as u16) << 12,
181        )
182    }
183
184    pub const fn into_array(self) -> [DrawIndex; 4] {
185        unsafe {
186            [
187                DrawIndex::new_unchecked(self.0 & 0xf),
188                DrawIndex::new_unchecked(self.0 >> 4 & 0xf),
189                DrawIndex::new_unchecked(self.0 >> 8 & 0xf),
190                DrawIndex::new_unchecked(self.0 >> 12 & 0xf),
191            ]
192        }
193    }
194
195    /// Creates a [`DrawIndices`] value from `u16`.
196    ///
197    /// # Safety
198    ///
199    /// Every four bits of `inner` argument must contain value in range `0..=4`
200    /// to safely create [`DrawIndex`] value using [`DrawIndex::new_unchecked`]
201    pub const unsafe fn from_u16_unchecked(inner: u16) -> Self {
202        DrawIndices(inner)
203    }
204
205    pub const fn into_u16(self) -> u16 {
206        self.0
207    }
208}
209
210impl From<DrawIndices> for u16 {
211    fn from(v: DrawIndices) -> Self {
212        v.0
213    }
214}
215
216#[derive(Clone, Copy, Default)]
217#[repr(u16)]
218pub enum DrawIndex {
219    #[default]
220    Transparent,
221    First,
222    Second,
223    Third,
224    Fourth,
225}
226
227impl DrawIndex {
228    pub const fn new(index: u16) -> Option<Self> {
229        if index <= 4 {
230            Some(unsafe { Self::new_unchecked(index) })
231        } else {
232            None
233        }
234    }
235
236    /// Create [`DrawIndex`] from u16.
237    ///
238    /// # Safety
239    ///
240    /// `index` value must be in range `0..=4`.
241    pub const unsafe fn new_unchecked(index: u16) -> Self {
242        core::mem::transmute(index)
243    }
244}
245
246pub type Palette = [Color; 4];
247
248/// Has `0x__RRGGBB` layout
249#[repr(transparent)]
250#[derive(Clone, Copy, Default)]
251pub struct Color(pub u32);
252
253impl Color {
254    pub const BLACK: Self = Color(0);
255
256    pub const fn blue(self) -> u8 {
257        (self.0 & 0xff) as u8
258    }
259
260    pub const fn green(self) -> u8 {
261        (self.0 >> 8 & 0xff) as u8
262    }
263
264    pub const fn red(self) -> u8 {
265        (self.0 >> 16 & 0xff) as u8
266    }
267
268    pub const fn with_blue(self, channel: u8) -> Self {
269        Color((self.0 & !0x0000ff) | (channel as u32))
270    }
271
272    pub const fn with_green(self, channel: u8) -> Self {
273        Color((self.0 & !0x00ff00) | (channel as u32) << 8)
274    }
275
276    pub const fn with_red(self, channel: u8) -> Self {
277        Color((self.0 & !0xff0000) | (channel as u32) << 16)
278    }
279}
280
281// helpers
282
283const fn checked_add_pairs(lhs: [u32; 2], rhs: [u32; 2]) -> Option<[u32; 2]> {
284    match [lhs[0].checked_add(rhs[0]), lhs[1].checked_add(rhs[1])] {
285        [Some(first), Some(second)] => Some([first, second]),
286        _ => None,
287    }
288}
289
290const fn lt_pairs(lhs: [u32; 2], rhs: [u32; 2]) -> bool {
291    lhs[0] < rhs[0] && lhs[1] < rhs[1]
292}
293
294const fn le_pairs(lhs: [u32; 2], rhs: [u32; 2]) -> bool {
295    lhs[0] <= rhs[0] && lhs[1] <= rhs[1]
296}