Skip to content

Commit bf3e09d

Browse files
authored
Merge pull request #10 from firefly-zero/canvas
Canvas
2 parents 68ee980 + fbb3086 commit bf3e09d

File tree

12 files changed

+310
-62
lines changed

12 files changed

+310
-62
lines changed

src/fs.rs

Lines changed: 10 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
//! Access file system: the game ROM files and the data dir.
22
3-
use crate::graphics::{Point, Size};
3+
use crate::graphics::*;
44
#[cfg(feature = "alloc")]
5-
use alloc::vec;
5+
use alloc::boxed::Box;
66
#[cfg(feature = "alloc")]
7-
use alloc::vec::Vec;
7+
use alloc::vec;
88

99
/// Like [File] but owns the buffer.
1010
///
@@ -13,7 +13,7 @@ use alloc::vec::Vec;
1313
/// and [`data::load`] instead.
1414
#[cfg(feature = "alloc")]
1515
pub struct FileBuf {
16-
pub(crate) raw: Vec<u8>,
16+
pub(crate) raw: Box<[u8]>,
1717
}
1818

1919
#[cfg(feature = "alloc")]
@@ -49,17 +49,17 @@ pub struct File<'a> {
4949

5050
impl<'a> File<'a> {
5151
#[must_use]
52-
pub fn data(&self) -> &[u8] {
52+
pub const fn data(&self) -> &[u8] {
5353
self.raw
5454
}
5555

5656
#[must_use]
57-
pub fn as_font(&self) -> Font {
57+
pub const fn as_font(&self) -> Font {
5858
Font { raw: self.raw }
5959
}
6060

6161
#[must_use]
62-
pub fn as_image(&self) -> Image {
62+
pub const fn as_image(&self) -> Image {
6363
Image { raw: self.raw }
6464
}
6565
}
@@ -106,7 +106,9 @@ pub fn load_file_buf(name: &str) -> Option<FileBuf> {
106106
}
107107
let mut buf = vec![0; size];
108108
load_file(name, &mut buf);
109-
Some(FileBuf { raw: buf })
109+
Some(FileBuf {
110+
raw: buf.into_boxed_slice(),
111+
})
110112
}
111113

112114
/// Write the buffer into the given file in the data dir.
@@ -155,46 +157,6 @@ impl<'a> From<&'a FileBuf> for Font<'a> {
155157
}
156158
}
157159

158-
/// A loaded image file.
159-
///
160-
/// Can be loaded as [`FileBuf`] from ROM with [`rom::load_buf`]
161-
/// and then cast using [Into].
162-
pub struct Image<'a> {
163-
pub(crate) raw: &'a [u8],
164-
}
165-
166-
impl<'a> From<File<'a>> for Image<'a> {
167-
fn from(value: File<'a>) -> Self {
168-
Self { raw: value.raw }
169-
}
170-
}
171-
172-
#[cfg(feature = "alloc")]
173-
impl<'a> From<&'a FileBuf> for Image<'a> {
174-
fn from(value: &'a FileBuf) -> Self {
175-
Self { raw: &value.raw }
176-
}
177-
}
178-
179-
impl<'a> Image<'a> {
180-
/// Get a rectangle subregion of the image.
181-
#[must_use]
182-
pub fn sub(&self, p: Point, s: Size) -> SubImage<'a> {
183-
SubImage {
184-
point: p,
185-
size: s,
186-
raw: self.raw,
187-
}
188-
}
189-
}
190-
191-
/// A subregion of an image. Constructed using [`Image::sub`].
192-
pub struct SubImage<'a> {
193-
pub(crate) point: Point,
194-
pub(crate) size: Size,
195-
pub(crate) raw: &'a [u8],
196-
}
197-
198160
mod bindings {
199161
#[link(wasm_import_module = "fs")]
200162
extern {

src/graphics/angle.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ impl Angle {
2626

2727
/// An angle in radians where [TAU] (doubled [PI]) is the full circle.
2828
#[must_use]
29-
pub fn from_radians(r: f32) -> Self {
29+
pub const fn from_radians(r: f32) -> Self {
3030
Self(r)
3131
}
3232

@@ -50,7 +50,7 @@ impl Angle {
5050

5151
/// Get the angle value in radians where [TAU] (doubled [PI]) is the full circle.
5252
#[must_use]
53-
pub fn to_radians(self) -> f32 {
53+
pub const fn to_radians(self) -> f32 {
5454
self.0
5555
}
5656

src/graphics/bindings.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,6 @@ extern {
9999
sub_height: i32,
100100
);
101101
pub(crate) fn draw_image(ptr: u32, len: u32, x: i32, y: i32);
102+
pub(crate) fn set_canvas(ptr: u32, len: u32);
103+
pub(crate) fn unset_canvas();
102104
}

src/graphics/canvas.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
use crate::*;
2+
#[cfg(feature = "alloc")]
3+
use alloc::boxed::Box;
4+
#[cfg(feature = "alloc")]
5+
use 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)]
16+
pub struct CanvasBuf {
17+
pub(crate) raw: Box<[u8]>,
18+
}
19+
20+
#[cfg(feature = "alloc")]
21+
impl 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.
49+
pub struct Canvas<'a> {
50+
pub(crate) raw: &'a [u8],
51+
}
52+
53+
impl<'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")]
81+
impl<'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)]
88+
fn 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+
}

src/graphics/funcs.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::{bindings as b, *};
2-
use crate::fs::{Font, Image, SubImage};
2+
use crate::*;
33

44
/// Fill the whole frame with the given color.
55
pub fn clear_screen(c: Color) {
@@ -191,3 +191,19 @@ pub fn draw_sub_image(i: &SubImage, p: Point) {
191191
);
192192
}
193193
}
194+
195+
/// Set canvas to be used for all subsequent drawing operations.
196+
pub fn set_canvas(c: &Canvas) {
197+
let ptr = c.raw.as_ptr();
198+
let len = c.raw.len();
199+
unsafe {
200+
b::set_canvas(ptr as u32, len as u32);
201+
}
202+
}
203+
204+
/// Unset canvas set by [`set_canvas`]. All subsequent drawing operations will target frame buffer.
205+
pub fn unset_canvas() {
206+
unsafe {
207+
b::unset_canvas();
208+
}
209+
}

src/graphics/image.rs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
use crate::*;
2+
3+
/// A loaded image file.
4+
///
5+
/// Can be loaded as [`FileBuf`] from ROM with [`load_file_buf`]
6+
/// and then cast using [`Into`].
7+
pub struct Image<'a> {
8+
pub(crate) raw: &'a [u8],
9+
}
10+
11+
impl<'a> From<File<'a>> for Image<'a> {
12+
fn from(value: File<'a>) -> Self {
13+
Self { raw: value.raw }
14+
}
15+
}
16+
17+
#[cfg(feature = "alloc")]
18+
impl<'a> From<&'a FileBuf> for Image<'a> {
19+
fn from(value: &'a FileBuf) -> Self {
20+
Self { raw: &value.raw }
21+
}
22+
}
23+
24+
impl<'a> From<Canvas<'a>> for Image<'a> {
25+
fn from(value: Canvas<'a>) -> Self {
26+
Self { raw: value.raw }
27+
}
28+
}
29+
30+
#[cfg(feature = "alloc")]
31+
impl<'a> From<&'a CanvasBuf> for Image<'a> {
32+
fn from(value: &'a CanvasBuf) -> Self {
33+
Self { raw: &value.raw }
34+
}
35+
}
36+
37+
impl<'a> Image<'a> {
38+
/// Get a rectangle subregion of the image.
39+
#[must_use]
40+
pub const fn sub(&self, p: Point, s: Size) -> SubImage<'a> {
41+
SubImage {
42+
point: p,
43+
size: s,
44+
raw: self.raw,
45+
}
46+
}
47+
48+
/// Bits per pixel. One of: 1, 2, or 4.
49+
#[must_use]
50+
pub const fn bpp(&self) -> u8 {
51+
self.raw[1]
52+
}
53+
54+
/// The color used for transparency. If no transparency, returns [`Color::None`].
55+
#[must_use]
56+
pub fn transparency(&self) -> Color {
57+
let c = usize::from(self.raw[4]) + 1;
58+
c.try_into().unwrap_or(Color::None)
59+
}
60+
61+
// pub fn set_transparency(&mut self, c: Color) {
62+
// let c: i32 = c.into();
63+
// if c == 0 {
64+
// self.raw[4] = 16;
65+
// } else {
66+
// self.raw[4] = c as u8;
67+
// }
68+
// }
69+
70+
/// The number of pixels the image has.
71+
#[must_use]
72+
pub const fn pixels(&self) -> usize {
73+
self.raw.len() * 8 / self.bpp() as usize
74+
}
75+
76+
/// The image width in pixels.
77+
#[must_use]
78+
pub fn width(&self) -> u16 {
79+
let big = u16::from(self.raw[2]);
80+
let little = u16::from(self.raw[3]);
81+
big | (little << 8)
82+
}
83+
84+
/// The image height in pixels.
85+
#[must_use]
86+
pub fn height(&self) -> u16 {
87+
let p = self.pixels();
88+
let w = self.width() as usize;
89+
p.checked_div(w).unwrap_or(0) as u16
90+
}
91+
92+
/// The image size in pixels.
93+
#[must_use]
94+
pub fn size(&self) -> Size {
95+
Size {
96+
width: i32::from(self.width()),
97+
height: i32::from(self.height()),
98+
}
99+
}
100+
101+
/// Get the color used to represent the given pixel value.
102+
#[must_use]
103+
pub fn get_color(&self, p: u8) -> Color {
104+
if p > 15 {
105+
return Color::None;
106+
}
107+
let byte_idx = usize::from(5 + p / 2);
108+
let mut byte_val = self.raw[byte_idx];
109+
if p % 2 == 0 {
110+
byte_val >>= 4;
111+
}
112+
byte_val &= 0b1111;
113+
let transp = self.raw[4];
114+
if byte_val == transp {
115+
return Color::None;
116+
}
117+
let color_val = usize::from(byte_val + 1);
118+
color_val.try_into().unwrap_or(Color::None)
119+
}
120+
}
121+
122+
/// A subregion of an image. Constructed using [`Image::sub`].
123+
#[expect(clippy::module_name_repetitions)]
124+
pub struct SubImage<'a> {
125+
pub(crate) point: Point,
126+
pub(crate) size: Size,
127+
pub(crate) raw: &'a [u8],
128+
}

0 commit comments

Comments
 (0)