Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 10 additions & 48 deletions src/fs.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
//! Access file system: the game ROM files and the data dir.

use crate::graphics::{Point, Size};
use crate::graphics::*;
#[cfg(feature = "alloc")]
use alloc::vec;
use alloc::boxed::Box;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use alloc::vec;

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

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

impl<'a> File<'a> {
#[must_use]
pub fn data(&self) -> &[u8] {
pub const fn data(&self) -> &[u8] {
self.raw
}

#[must_use]
pub fn as_font(&self) -> Font {
pub const fn as_font(&self) -> Font {
Font { raw: self.raw }
}

#[must_use]
pub fn as_image(&self) -> Image {
pub const fn as_image(&self) -> Image {
Image { raw: self.raw }
}
}
Expand Down Expand Up @@ -106,7 +106,9 @@ pub fn load_file_buf(name: &str) -> Option<FileBuf> {
}
let mut buf = vec![0; size];
load_file(name, &mut buf);
Some(FileBuf { raw: buf })
Some(FileBuf {
raw: buf.into_boxed_slice(),
})
}

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

/// A loaded image file.
///
/// Can be loaded as [`FileBuf`] from ROM with [`rom::load_buf`]
/// and then cast using [Into].
pub struct Image<'a> {
pub(crate) raw: &'a [u8],
}

impl<'a> From<File<'a>> for Image<'a> {
fn from(value: File<'a>) -> Self {
Self { raw: value.raw }
}
}

#[cfg(feature = "alloc")]
impl<'a> From<&'a FileBuf> for Image<'a> {
fn from(value: &'a FileBuf) -> Self {
Self { raw: &value.raw }
}
}

impl<'a> Image<'a> {
/// Get a rectangle subregion of the image.
#[must_use]
pub fn sub(&self, p: Point, s: Size) -> SubImage<'a> {
SubImage {
point: p,
size: s,
raw: self.raw,
}
}
}

/// A subregion of an image. Constructed using [`Image::sub`].
pub struct SubImage<'a> {
pub(crate) point: Point,
pub(crate) size: Size,
pub(crate) raw: &'a [u8],
}

mod bindings {
#[link(wasm_import_module = "fs")]
extern {
Expand Down
4 changes: 2 additions & 2 deletions src/graphics/angle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ impl Angle {

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

Expand All @@ -50,7 +50,7 @@ impl Angle {

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

Expand Down
2 changes: 2 additions & 0 deletions src/graphics/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,6 @@ extern {
sub_height: i32,
);
pub(crate) fn draw_image(ptr: u32, len: u32, x: i32, y: i32);
pub(crate) fn set_canvas(ptr: u32, len: u32);
pub(crate) fn unset_canvas();
}
99 changes: 99 additions & 0 deletions src/graphics/canvas.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use crate::*;
#[cfg(feature = "alloc")]
use alloc::boxed::Box;
#[cfg(feature = "alloc")]
use alloc::vec;

// TODO: add statically-allocated version when prepare_slice can be turned into static fn.
// It is blocked by this feature going into stable:
// https://github.com/rust-lang/rust/issues/57349

/// Canvas is an [`Image`] that can be drawn upon.
///
/// [`CanvasBuf`] is the same as [`Canvas`] but holds the ownership of the underlying slice.
#[cfg(feature = "alloc")]
#[expect(clippy::module_name_repetitions)]
pub struct CanvasBuf {
pub(crate) raw: Box<[u8]>,
}

#[cfg(feature = "alloc")]
impl CanvasBuf {
/// Create new empty canvas.
#[must_use]
#[expect(clippy::cast_sign_loss)]
pub fn new(s: Size) -> Self {
const HEADER_SIZE: usize = 5 + 8;
let body_size = s.width * s.height / 2;
let mut raw = vec![0; HEADER_SIZE + body_size as usize];
prepare_slice(&mut raw, s.width);
Self {
raw: raw.into_boxed_slice(),
}
}

/// Represent the canvas as an [`Image`].
#[must_use]
pub const fn as_image(&self) -> Image<'_> {
Image { raw: &self.raw }
}

/// Represent the buffered canvas as [`Canvas`].
#[must_use]
pub const fn as_canvas(&self) -> Canvas<'_> {
Canvas { raw: &self.raw }
}
}

/// Canvas is an [`Image`] that can be drawn upon.
pub struct Canvas<'a> {
pub(crate) raw: &'a [u8],
}

impl<'a> Canvas<'a> {
/// Create new empty canvas using the given slice.
///
/// Returns [`None`] if the slice is too small for the given image size.
/// A bigger slice than needed is fine.
#[must_use]
#[expect(clippy::cast_sign_loss)]
pub fn new(s: Size, raw: &'a mut [u8]) -> Option<Self> {
const HEADER_SIZE: usize = 5 + 8;
let body_size = s.width * s.height / 2;
let exp_size = HEADER_SIZE + body_size as usize;
if raw.len() < exp_size {
return None;
}
prepare_slice(raw, s.width);
Some(Self {
raw: &raw[..exp_size],
})
}

/// Represent the canvas as an [`Image`].
#[must_use]
pub const fn as_image(&self) -> Image<'a> {
Image { raw: self.raw }
}
}

#[cfg(feature = "alloc")]
impl<'a> From<&'a CanvasBuf> for Canvas<'a> {
fn from(value: &'a CanvasBuf) -> Self {
Self { raw: &value.raw }
}
}

#[expect(clippy::cast_sign_loss)]
fn prepare_slice(raw: &mut [u8], width: i32) {
raw[0] = 0x21; // magic number
raw[1] = 4; // BPP
raw[2] = width as u8; // width
raw[3] = (width >> 8) as u8; // width
raw[4] = 255; // transparency

// color swaps
for i in 0u8..8u8 {
raw[5 + i as usize] = ((i * 2) << 4) | (i * 2 + 1);
}
}
18 changes: 17 additions & 1 deletion src/graphics/funcs.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::{bindings as b, *};
use crate::fs::{Font, Image, SubImage};
use crate::*;

/// Fill the whole frame with the given color.
pub fn clear_screen(c: Color) {
Expand Down Expand Up @@ -191,3 +191,19 @@ pub fn draw_sub_image(i: &SubImage, p: Point) {
);
}
}

/// Set canvas to be used for all subsequent drawing operations.
pub fn set_canvas(c: &Canvas) {
let ptr = c.raw.as_ptr();
let len = c.raw.len();
unsafe {
b::set_canvas(ptr as u32, len as u32);
}
}

/// Unset canvas set by [`set_canvas`]. All subsequent drawing operations will target frame buffer.
pub fn unset_canvas() {
unsafe {
b::unset_canvas();
}
}
128 changes: 128 additions & 0 deletions src/graphics/image.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
use crate::*;

/// A loaded image file.
///
/// Can be loaded as [`FileBuf`] from ROM with [`load_file_buf`]
/// and then cast using [`Into`].
pub struct Image<'a> {
pub(crate) raw: &'a [u8],
}

impl<'a> From<File<'a>> for Image<'a> {
fn from(value: File<'a>) -> Self {
Self { raw: value.raw }
}
}

#[cfg(feature = "alloc")]
impl<'a> From<&'a FileBuf> for Image<'a> {
fn from(value: &'a FileBuf) -> Self {
Self { raw: &value.raw }
}
}

impl<'a> From<Canvas<'a>> for Image<'a> {
fn from(value: Canvas<'a>) -> Self {
Self { raw: value.raw }
}
}

#[cfg(feature = "alloc")]
impl<'a> From<&'a CanvasBuf> for Image<'a> {
fn from(value: &'a CanvasBuf) -> Self {
Self { raw: &value.raw }
}
}

impl<'a> Image<'a> {
/// Get a rectangle subregion of the image.
#[must_use]
pub const fn sub(&self, p: Point, s: Size) -> SubImage<'a> {
SubImage {
point: p,
size: s,
raw: self.raw,
}
}

/// Bits per pixel. One of: 1, 2, or 4.
#[must_use]
pub const fn bpp(&self) -> u8 {
self.raw[1]
}

/// The color used for transparency. If no transparency, returns [`Color::None`].
#[must_use]
pub fn transparency(&self) -> Color {
let c = usize::from(self.raw[4]) + 1;
c.try_into().unwrap_or(Color::None)
}

// pub fn set_transparency(&mut self, c: Color) {
// let c: i32 = c.into();
// if c == 0 {
// self.raw[4] = 16;
// } else {
// self.raw[4] = c as u8;
// }
// }

/// The number of pixels the image has.
#[must_use]
pub const fn pixels(&self) -> usize {
self.raw.len() * 8 / self.bpp() as usize
}

/// The image width in pixels.
#[must_use]
pub fn width(&self) -> u16 {
let big = u16::from(self.raw[2]);
let little = u16::from(self.raw[3]);
big | (little << 8)
}

/// The image height in pixels.
#[must_use]
pub fn height(&self) -> u16 {
let p = self.pixels();
let w = self.width() as usize;
p.checked_div(w).unwrap_or(0) as u16
}

/// The image size in pixels.
#[must_use]
pub fn size(&self) -> Size {
Size {
width: i32::from(self.width()),
height: i32::from(self.height()),
}
}

/// Get the color used to represent the given pixel value.
#[must_use]
pub fn get_color(&self, p: u8) -> Color {
if p > 15 {
return Color::None;
}
let byte_idx = usize::from(5 + p / 2);
let mut byte_val = self.raw[byte_idx];
if p % 2 == 0 {
byte_val >>= 4;
}
byte_val &= 0b1111;
let transp = self.raw[4];
if byte_val == transp {
return Color::None;
}
let color_val = usize::from(byte_val + 1);
color_val.try_into().unwrap_or(Color::None)
}
}

/// A subregion of an image. Constructed using [`Image::sub`].
#[expect(clippy::module_name_repetitions)]
pub struct SubImage<'a> {
pub(crate) point: Point,
pub(crate) size: Size,
pub(crate) raw: &'a [u8],
}
Loading