Skip to main content

wasm4fun_graphics/
sprite.rs

1// Copyright Claudio Mattera 2022.
2//
3// Distributed under the MIT License or the Apache 2.0 License at your option.
4// See the accompanying files License-MIT.txt and License-Apache-2.0.txt, or
5// online at
6// https://opensource.org/licenses/MIT
7// https://opensource.org/licenses/Apache-2.0
8
9use wasm4fun_core::{blit, blit_sub, BLIT_FLIP_X, BLIT_FLIP_Y, BLIT_ROTATE};
10
11/// Rotation
12#[derive(Copy, Clone)]
13pub enum Rotation {
14    /// Does not rotate
15    Rotate0,
16
17    /// Rotate by 90 degrees clockwise
18    Rotate90,
19
20    /// Rotate by 180 degrees clockwise
21    Rotate180,
22
23    /// Rotate by 270 degrees clockwise (or 90 degrees counter-clockwise)
24    Rotate270,
25}
26
27impl Rotation {
28    /// Add a rotation on top of the current one
29    pub const fn add(&self, other: Rotation) -> Self {
30        match (self, other) {
31            (Self::Rotate0, other) => other,
32            (self2, Self::Rotate0) => *self2,
33            (Self::Rotate90, Self::Rotate90) => Self::Rotate180,
34            (Self::Rotate90, Self::Rotate180) => Self::Rotate270,
35            (Self::Rotate90, Self::Rotate270) => Self::Rotate0,
36            (Self::Rotate180, Self::Rotate90) => Self::Rotate270,
37            (Self::Rotate180, Self::Rotate180) => Self::Rotate0,
38            (Self::Rotate180, Self::Rotate270) => Self::Rotate90,
39            (Self::Rotate270, Self::Rotate90) => Self::Rotate0,
40            (Self::Rotate270, Self::Rotate180) => Self::Rotate90,
41            (Self::Rotate270, Self::Rotate270) => Self::Rotate180,
42        }
43    }
44}
45
46/// A view of a modified sprite
47///
48/// A sprite view can be used to apply transformations to a sprite before
49/// drawing it to screen.
50///
51/// ```no_run
52/// use wasm4fun_graphics::{Rotation, Sprite};
53///
54/// let sprite: Sprite = unimplemented!();
55/// let clipped = sprite.clip(30, 10, 80, 20);
56/// let rotated = sprite.rotate(Rotation::Rotate90);
57/// let clipped_and_rotated = clipped.rotate(Rotation::Rotate90);
58/// rotated.blit(0, 0);
59/// clipped_and_rotated.blit(50, 50);
60/// ```
61///
62/// # Order of Operations
63///
64/// When applying both flipping and rotation, flipping is performed first.
65#[derive(Clone, Copy)]
66pub struct SpriteViewImpl<'a> {
67    sprite: &'a Sprite<'a>,
68    rotation: Rotation,
69    src_x: u32,
70    src_y: u32,
71    width: u32,
72    height: u32,
73    flip_horizontal: bool,
74    flip_vertical: bool,
75}
76
77impl<'a> SpriteViewImpl<'a> {
78    /// Create a view over a sprite
79    pub const fn new(sprite: &'a Sprite) -> Self {
80        Self {
81            sprite,
82            rotation: Rotation::Rotate0,
83            src_x: 0,
84            src_y: 0,
85            width: sprite.width,
86            height: sprite.height,
87            flip_horizontal: false,
88            flip_vertical: false,
89        }
90    }
91
92    /// Get the sprite width
93    pub const fn width(&self) -> u32 {
94        self.width
95    }
96
97    /// Get the sprite height
98    pub const fn height(&self) -> u32 {
99        self.height
100    }
101
102    /// Apply a rotation to the sprite view
103    pub const fn rotate(&self, rotation: Rotation) -> Self {
104        let mut new = *self;
105        new.rotation = new.rotation.add(rotation);
106        new
107    }
108
109    /// Flip the sprite view horizontally
110    pub const fn flip_horizontally(&self, flip_horizontally: bool) -> Self {
111        let mut new = *self;
112        new.flip_horizontal = flip_horizontally;
113        new
114    }
115
116    /// Flip the sprite view vertically
117    pub const fn flip_vertically(&self, flip_vertically: bool) -> Self {
118        let mut new = *self;
119        new.flip_vertical = flip_vertically;
120        new
121    }
122
123    /// Clip the sprite view to a subregion
124    pub const fn clip(&self, src_x: u32, src_y: u32, width: u32, height: u32) -> Self {
125        let mut new = *self;
126        new.src_x += src_x;
127        new.src_y += src_y;
128        new.width = width;
129        new.height = height;
130        new
131    }
132
133    /// Draw the sprite view to the screen
134    ///
135    /// The sprite is drawn at the point `x`, `y` after applying all the
136    /// transformations in the view.
137    pub fn blit(&self, x: i32, y: i32) {
138        let flags = match (&self.rotation, &self.flip_horizontal, &self.flip_vertical) {
139            (Rotation::Rotate0, false, false) => 0,
140            (Rotation::Rotate90, false, false) => BLIT_FLIP_X | BLIT_FLIP_Y | BLIT_ROTATE,
141            (Rotation::Rotate180, false, false) => BLIT_FLIP_X | BLIT_FLIP_Y,
142            (Rotation::Rotate270, false, false) => BLIT_ROTATE,
143
144            (Rotation::Rotate0, false, true) => BLIT_FLIP_Y,
145            (Rotation::Rotate90, false, true) => BLIT_FLIP_X | BLIT_ROTATE,
146            (Rotation::Rotate180, false, true) => BLIT_FLIP_X,
147            (Rotation::Rotate270, false, true) => BLIT_FLIP_Y | BLIT_ROTATE,
148
149            (Rotation::Rotate0, true, false) => BLIT_FLIP_X,
150            (Rotation::Rotate90, true, false) => BLIT_FLIP_Y | BLIT_ROTATE,
151            (Rotation::Rotate180, true, false) => BLIT_FLIP_Y,
152            (Rotation::Rotate270, true, false) => BLIT_FLIP_X | BLIT_ROTATE,
153
154            (Rotation::Rotate0, true, true) => BLIT_FLIP_X | BLIT_FLIP_Y,
155            (Rotation::Rotate90, true, true) => BLIT_ROTATE,
156            (Rotation::Rotate180, true, true) => 0,
157            (Rotation::Rotate270, true, true) => BLIT_FLIP_X | BLIT_FLIP_Y | BLIT_ROTATE,
158        };
159
160        self.sprite.blit_sub_with_flags(
161            x,
162            y,
163            self.width,
164            self.height,
165            self.src_x,
166            self.src_y,
167            flags,
168        );
169    }
170}
171
172impl<'a> From<&'a Sprite<'a>> for SpriteViewImpl<'a> {
173    fn from(sprite: &'a Sprite) -> Self {
174        Self::new(sprite)
175    }
176}
177
178/// A 1-bps or 2-bps bitmap drawable on screen
179///
180/// Sprites
181///
182/// WASM-4 natively supports sprites with the functions [`wasm4fun_core::blit`]
183/// and [`wasm4fun_core::blit_sub`].
184/// Instead of passing sprite information as separate arguments, this structure
185/// conveniently wraps them in a single place.
186/// It also makes it easier to apply transformations using a [`SpriteViewImpl`].
187///
188/// They can be generated by PNG images with the command `w4 png2src --rust`.
189///
190/// ```no_run
191/// use wasm4fun_graphics::Sprite;
192///
193/// const CAR_WIDTH: u32 = 16;
194/// const CAR_HEIGHT: u32 = 16;
195/// const CAR_FLAGS: u32 = 1; // BLIT_2BPP
196/// const CAR: [u8; 256] = [0; 256];
197///
198/// let car = Sprite::new(CAR_WIDTH, CAR_HEIGHT, CAR_FLAGS, &CAR);
199///
200/// // Draw car to screen
201/// car.blit(10, 40);
202///
203/// // Create a clipped view of the original sprite
204/// let clip = car.clip(0, 0, 8, 8);
205/// clip.blit(10, 40);
206/// ```
207#[derive(Clone, Debug)]
208pub struct Sprite<'a> {
209    width: u32,
210    height: u32,
211    flags: u32,
212    data: &'a [u8],
213}
214
215impl<'a> Sprite<'a> {
216    /// Create a new sprite
217    pub const fn new(width: u32, height: u32, flags: u32, data: &'a [u8]) -> Self {
218        Self {
219            width,
220            height,
221            flags,
222            data,
223        }
224    }
225
226    /// Get the sprite width
227    pub const fn width(&self) -> u32 {
228        self.width
229    }
230
231    /// Get the sprite height
232    pub const fn height(&self) -> u32 {
233        self.height
234    }
235
236    /// Apply a rotation to the sprite
237    pub const fn rotate(&self, rotation: Rotation) -> SpriteViewImpl {
238        let view = SpriteViewImpl::new(self);
239        SpriteViewImpl::rotate(&view, rotation)
240    }
241
242    /// Flip the sprite horizontally
243    pub fn flip_horizontally(&self, flip_horizontally: bool) -> SpriteViewImpl {
244        let view = SpriteViewImpl::new(self);
245        SpriteViewImpl::flip_horizontally(&view, flip_horizontally)
246    }
247
248    /// Flip the sprite vertically
249    pub const fn flip_vertically(&self, flip_vertically: bool) -> SpriteViewImpl {
250        let view = SpriteViewImpl::new(self);
251        SpriteViewImpl::flip_vertically(&view, flip_vertically)
252    }
253
254    /// Clip the sprite to a subregion
255    pub const fn clip(&self, src_x: u32, src_y: u32, width: u32, height: u32) -> SpriteViewImpl {
256        let view = SpriteViewImpl::new(self);
257        SpriteViewImpl::clip(&view, src_x, src_y, width, height)
258    }
259
260    /// Draw the sprite to the screen
261    ///
262    /// The sprite is drawn at the point `x`, `y`.
263    pub fn blit(&self, x: i32, y: i32) {
264        blit(self.data, x, y, self.width, self.height, self.flags);
265    }
266
267    /// Draw a region of the sprite to the screen
268    ///
269    /// The region is drawn at the point `x`, `y`.
270    /// The region starts at the point `src_x`, `src_y` and has width `width`
271    /// and height `height`.
272    #[allow(clippy::too_many_arguments)]
273    fn blit_sub_with_flags(
274        &self,
275        x: i32,
276        y: i32,
277        width: u32,
278        height: u32,
279        src_x: u32,
280        src_y: u32,
281        flags: u32,
282    ) {
283        blit_sub(
284            self.data,
285            x,
286            y,
287            width,
288            height,
289            src_x,
290            src_y,
291            self.width,
292            self.flags | flags,
293        );
294    }
295}
296
297/// A generic view over a sprite or a sprite transformation
298///
299/// Further transformations can be applied to a generic sprite view, or it can
300/// be drawn to the screen.
301///
302/// This trait allows to define functions that accept both "original" sprites
303/// (i.e. of type [`Sprite`]) or their transformations (e.g. of type
304/// [`SpriteViewImpl`]).
305pub trait SpriteView<'a> {
306    /// Get the sprite width
307    fn width(&self) -> u32;
308
309    /// Get the sprite height
310    fn height(&self) -> u32;
311
312    /// Apply a rotation to the sprite view
313    fn rotate(&'a self, rotation: Rotation) -> SpriteViewImpl<'a>;
314
315    /// Flip the sprite view horizontally
316    fn flip_horizontally(&'a self, flip_horizontally: bool) -> SpriteViewImpl<'a>;
317
318    /// Flip the sprite view vertically
319    fn flip_vertically(&'a self, flip_vertically: bool) -> SpriteViewImpl<'a>;
320
321    /// Clip the sprite view to a subregion
322    fn clip(&'a self, src_x: u32, src_y: u32, width: u32, height: u32) -> SpriteViewImpl<'a>;
323
324    /// Draw the sprite view to the screen
325    ///
326    /// The sprite is drawn at the point `x`, `y` after applying all the
327    /// transformations in the view.
328    fn blit(&self, x: i32, y: i32);
329}
330
331impl<'a> SpriteView<'a> for SpriteViewImpl<'a> {
332    fn width(&self) -> u32 {
333        SpriteViewImpl::width(self)
334    }
335
336    fn height(&self) -> u32 {
337        SpriteViewImpl::height(self)
338    }
339
340    fn rotate(&'a self, rotation: Rotation) -> SpriteViewImpl<'a> {
341        SpriteViewImpl::rotate(self, rotation)
342    }
343
344    fn flip_horizontally(&'a self, flip_horizontally: bool) -> SpriteViewImpl<'a> {
345        SpriteViewImpl::flip_horizontally(self, flip_horizontally)
346    }
347
348    fn flip_vertically(&'a self, flip_vertically: bool) -> SpriteViewImpl<'a> {
349        SpriteViewImpl::flip_vertically(self, flip_vertically)
350    }
351
352    fn clip(&'a self, src_x: u32, src_y: u32, width: u32, height: u32) -> SpriteViewImpl<'a> {
353        SpriteViewImpl::clip(self, src_x, src_y, width, height)
354    }
355
356    fn blit(&self, x: i32, y: i32) {
357        SpriteViewImpl::blit(self, x, y)
358    }
359}
360
361impl<'a> SpriteView<'a> for Sprite<'a> {
362    fn width(&self) -> u32 {
363        Sprite::width(self)
364    }
365
366    fn height(&self) -> u32 {
367        Sprite::height(self)
368    }
369
370    fn rotate(&'a self, rotation: Rotation) -> SpriteViewImpl<'a> {
371        Sprite::rotate(self, rotation)
372    }
373
374    fn flip_horizontally(&'a self, flip_horizontally: bool) -> SpriteViewImpl<'a> {
375        Sprite::flip_horizontally(self, flip_horizontally)
376    }
377
378    fn flip_vertically(&'a self, flip_vertically: bool) -> SpriteViewImpl<'a> {
379        Sprite::flip_vertically(self, flip_vertically)
380    }
381
382    fn clip(&'a self, src_x: u32, src_y: u32, width: u32, height: u32) -> SpriteViewImpl<'a> {
383        Sprite::clip(self, src_x, src_y, width, height)
384    }
385
386    fn blit(&self, x: i32, y: i32) {
387        Sprite::blit(self, x, y)
388    }
389}