1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![no_std]
//! A safe and ergonomic image API for the playdate
//!
//! ## Feature flags
//!
//! * `playdate-sys-v02`: implementations of the input source traits for the types `&ffi::playdate_graphics` of the crate [`playdate-sys`](https://docs.rs/playdate-sys/0.2) (version `0.2`)
//! * `anyhow`: implementations of `From` error type for `anyhow::Error`
extern crate alloc;
/// Implementations of the the API traits
#[allow(missing_docs)]
pub mod impls {
/// Implementations fpr [playdate-sys](https://docs.rs/playdate-sys) version `0.2`
#[cfg(feature = "playdate-sys-v02")]
pub mod playdate_sys_v02;
}
/// Ability to load an image from path
pub trait LoadImage {
/// Type of image being loaded
type Image;
/// Error type representing failure to load an image
type Error;
/// Load an image from its path
///
/// # Errors
///
/// Returns [`Self::Error`] if the image cannot be loaded (i.e image not found)
fn load_from_path(&self, path: impl AsRef<str>) -> Result<Self::Image, Self::Error>;
}
/// Split the images into `n` columns of the same size
pub trait ToColumns: Sized {
fn to_columns(&self, n: usize) -> impl Iterator<Item = Self>;
}
/// Split the images into `n` rows of the same size
pub trait ToRows: Sized {
fn to_rows(&self, n: usize) -> impl Iterator<Item = Self>;
}
/// Ability to draw an image on screen
pub trait DrawImage<I> {
/// Draw the image on screen with the top-left corner at the given screen coordinates
fn draw(&self, image: &I, top_left: impl Into<[i32; 2]>) {
self.draw_with_flip(image, top_left, [false, false]);
}
/// Draw the image on screen with the top-left corner at the given screen coordinates
fn draw_with_flip(&self, image: &I, top_left: impl Into<[i32; 2]>, flip: impl Into<[bool; 2]>);
}
/// Ability to draw an image from its origin point (instead of from the top-left)
///
/// This trait is automatically implemented for implementations of `DrawImage<I>` where `I: HasSize`
pub trait DrawFromOrigin<I> {
/// Draw the image so that the `origin` is at `position`
///
/// The origin is expressed in ratio of the size. So `[0., 0.]` is the top-left and `[1.,1.]` is the bottom right.
fn draw_from_origin(
&self,
image: &I,
position: impl Into<[i32; 2]>,
origin: impl Into<[f32; 2]>,
) {
self.draw_from_origin_with_flip(image, position, origin, [false, false]);
}
/// Draw the image so that the `origin` is at `position` with given `flip` argument.
///
/// The origin is expressed in ratio of the size. So `[0., 0.]` is the top-left and `[1.,1.]` is the bottom right.
///
/// If flipped, the image is fliped around its origin.
fn draw_from_origin_with_flip(
&self,
image: &I,
position: impl Into<[i32; 2]>,
origin: impl Into<[f32; 2]>,
flip: impl Into<[bool; 2]>,
);
}
impl<T, I> DrawFromOrigin<I> for T
where
T: DrawImage<I>,
I: HasSize,
{
fn draw_from_origin_with_flip(
&self,
image: &I,
position: impl Into<[i32; 2]>,
origin: impl Into<[f32; 2]>,
flip: impl Into<[bool; 2]>,
) {
let flip = flip.into();
let position = position_from_origin(position.into(), origin.into(), image.size(), flip);
self.draw_with_flip(image, position, flip);
}
}
#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
fn position_from_origin(
[x, y]: [i32; 2],
[origin_x, origin_y]: [f32; 2],
[w, h]: [i32; 2],
[flip_x, flip_y]: [bool; 2],
) -> [i32; 2] {
let x = if flip_x {
x - (w as f32 * (1.0 - origin_x)) as i32
} else {
x - (w as f32 * origin_x) as i32
};
let y = if flip_y {
y - (h as f32 * (1.0 - origin_y)) as i32
} else {
y - (h as f32 * origin_y) as i32
};
[x, y]
}
pub trait HasSize {
fn size(&self) -> [i32; 2];
}
#[non_exhaustive]
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)]
pub enum DrawMode {
/// Images are drawn exactly as they are (black pixels are drawn black and white pixels are drawn white)
#[default]
Copy,
/// Any white portions of an image are drawn transparent (black pixels are drawn black and white pixels are drawn transparent)
WhiteTransparent,
/// Any black portions of an image are drawn transparent (black pixels are drawn transparent and white pixels are drawn white)
BlackTransparent,
/// All non-transparent pixels are drawn white (black pixels are drawn white and white pixels are drawn white)
FillWhite,
/// All non-transparent pixels are drawn black (black pixels are drawn black and white pixels are drawn black)
FillBlack,
/// Pixels are drawn inverted on white backgrounds, creating an effect where any white pixels in the original image will always be visible,
/// regardless of the background color, and any black pixels will appear transparent (on a white background, black pixels are drawn white and white pixels are drawn black)
XOR,
/// Pixels are drawn inverted on black backgrounds, creating an effect where any black pixels in the original image will always be visible,
/// regardless of the background color, and any white pixels will appear transparent (on a black background, black pixels are drawn white and white pixels are drawn black)
NXOR,
/// Pixels are drawn inverted (black pixels are drawn white and white pixels are drawn black)
Inverted,
}
#[cfg(test)]
mod test {
use super::*;
use rstest::rstest;
#[rstest]
#[case([0, 0], [0., 0.], [2, 3], [false, false], [0, 0])]
#[case([0, 0], [1., 0.], [2, 3], [false, false], [-2, 0])]
#[case([0, 0], [0., 1.], [2, 3], [false, false], [0, -3])]
#[case([0, 0], [1., 1.], [2, 3], [false, false], [-2, -3])]
#[case([0, 0], [2., 2.], [2, 3], [false, false], [-4, -6])]
#[case([0, 0], [0., 0.], [2, 3], [true, false], [-2, 0])]
#[case([0, 0], [1., 0.], [2, 3], [true, false], [0, 0])]
#[case([0, 0], [0., 1.], [2, 3], [false, true], [0, 0])]
#[case([0, 0], [1., 1.], [2, 3], [true, true], [0, 0])]
#[case([0, 0], [2., 0.], [2, 3], [true, false], [2, 0])]
#[case([0, 0], [0., 2.], [2, 3], [false, true], [0, 3])]
fn test_position_from_origin(
#[case] position: [i32; 2],
#[case] origin: [f32; 2],
#[case] size: [i32; 2],
#[case] flip: [bool; 2],
#[case] expected: [i32; 2],
) {
let actual = position_from_origin(position, origin, size, flip);
assert_eq!(actual, expected);
}
}