use crate::geometry::Rect;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ImageFit {
Stretch,
Center,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ImageRef<'a> {
pub width: u32,
pub height: u32,
pub pixels: &'a [u16],
}
impl<'a> ImageRef<'a> {
pub const fn new(width: u32, height: u32, pixels: &'a [u16]) -> Self {
Self {
width,
height,
pixels,
}
}
pub fn bounds_at(&self, rect: Rect, fit: ImageFit) -> Rect {
match fit {
ImageFit::Stretch => rect,
ImageFit::Center => {
let x = rect.x + rect.w.saturating_sub(self.width) as i32 / 2;
let y = rect.y + rect.h.saturating_sub(self.height) as i32 / 2;
Rect::new(x, y, self.width.min(rect.w), self.height.min(rect.h))
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TileMode {
None,
Repeat,
Mirror,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TileRef<'a> {
pub width: u32,
pub height: u32,
pub pixels: &'a [u16],
pub mode: TileMode,
}
impl<'a> TileRef<'a> {
pub const fn new(width: u32, height: u32, pixels: &'a [u16], mode: TileMode) -> Self {
Self {
width,
height,
pixels,
mode,
}
}
pub const fn from_image(image: ImageRef<'a>, mode: TileMode) -> Self {
Self {
width: image.width,
height: image.height,
pixels: image.pixels,
mode,
}
}
pub fn get_pixel(&self, u: i32, v: i32) -> Option<embedded_graphics_core::pixelcolor::Rgb565> {
let w = self.width as i32;
let h = self.height as i32;
if w <= 0 || h <= 0 {
return None;
}
let (u_final, v_final) = match self.mode {
TileMode::None => {
if u < 0 || v < 0 || u >= w || v >= h {
return None;
}
(u as usize, v as usize)
}
TileMode::Repeat => (u.rem_euclid(w) as usize, v.rem_euclid(h) as usize),
TileMode::Mirror => {
let m_u = u.rem_euclid(2 * w);
let m_v = v.rem_euclid(2 * h);
let final_u = if m_u >= w { 2 * w - 1 - m_u } else { m_u };
let final_v = if m_v >= h { 2 * h - 1 - m_v } else { m_v };
(final_u as usize, final_v as usize)
}
};
let idx = v_final * self.width as usize + u_final;
self.pixels.get(idx).map(|&raw| {
embedded_graphics_core::pixelcolor::Rgb565::new(
((raw >> 11) & 0x1F) as u8,
((raw >> 5) & 0x3F) as u8,
(raw & 0x1F) as u8,
)
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SpriteSheet<'a> {
pub image: ImageRef<'a>,
pub sprite_w: u32,
pub sprite_h: u32,
pub columns: u32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ReelFrame {
pub sprite_index: u16,
pub duration_ms: u16,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ReelPlayer<'a> {
pub sheet: SpriteSheet<'a>,
pub frames: &'a [ReelFrame],
pub repeat: bool,
current: usize,
elapsed_in_frame_ms: u32,
finished: bool,
}
impl<'a> ReelPlayer<'a> {
pub const fn new(sheet: SpriteSheet<'a>, frames: &'a [ReelFrame], repeat: bool) -> Self {
Self {
sheet,
frames,
repeat,
current: 0,
elapsed_in_frame_ms: 0,
finished: false,
}
}
pub fn tick(&mut self, dt_ms: u32) {
if self.frames.is_empty() || self.finished {
return;
}
self.elapsed_in_frame_ms = self.elapsed_in_frame_ms.saturating_add(dt_ms);
loop {
let frame = self.frames[self.current];
let frame_ms = u32::from(frame.duration_ms).max(1);
if self.elapsed_in_frame_ms < frame_ms {
break;
}
self.elapsed_in_frame_ms -= frame_ms;
if self.current + 1 < self.frames.len() {
self.current += 1;
continue;
}
if self.repeat {
self.current = 0;
} else {
self.finished = true;
}
break;
}
}
pub const fn is_finished(&self) -> bool {
self.finished
}
pub fn restart(&mut self) {
self.current = 0;
self.elapsed_in_frame_ms = 0;
self.finished = false;
}
pub fn current_sprite_rect(&self) -> Option<Rect> {
let frame = self.frames.get(self.current)?;
Some(self.sheet.sprite_rect(frame.sprite_index as u32))
}
}
impl<'a> SpriteSheet<'a> {
pub const fn new(image: ImageRef<'a>, sprite_w: u32, sprite_h: u32) -> Self {
let columns = match image.width.checked_div(sprite_w) {
Some(c) => c,
None => 1,
};
Self {
image,
sprite_w,
sprite_h,
columns,
}
}
pub fn sprite_rect(&self, index: u32) -> Rect {
let columns = self.columns.max(1);
let col = index % columns;
let row = index / columns;
Rect::new(
(col * self.sprite_w) as i32,
(row * self.sprite_h) as i32,
self.sprite_w,
self.sprite_h,
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ImageAtlasEntry {
pub id: u16,
pub rect: Rect,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ImageAtlas<'a> {
pub image: ImageRef<'a>,
pub entries: &'a [ImageAtlasEntry],
}
impl<'a> ImageAtlas<'a> {
pub const fn new(image: ImageRef<'a>, entries: &'a [ImageAtlasEntry]) -> Self {
Self { image, entries }
}
pub fn rect_for(&self, id: u16) -> Option<Rect> {
self.entries
.iter()
.find(|entry| entry.id == id)
.map(|e| e.rect)
}
}
#[cfg(all(feature = "std", feature = "image-decode"))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ImageDecodeError {
InvalidHeader,
Unsupported,
InvalidData,
Capacity,
}
#[cfg(all(feature = "std", feature = "image-decode"))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum EncodedImageFormat {
PpmAscii,
}
#[cfg(all(feature = "std", feature = "image-decode"))]
pub trait ImageDecoder {
fn decode<const N: usize>(
&self,
format: EncodedImageFormat,
data: &str,
out_pixels: &mut heapless::Vec<u16, N>,
) -> Result<(u32, u32), ImageDecodeError>;
}
#[cfg(all(feature = "std", feature = "image-decode"))]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct BasicImageDecoder;
#[cfg(all(feature = "std", feature = "image-decode"))]
impl ImageDecoder for BasicImageDecoder {
fn decode<const N: usize>(
&self,
format: EncodedImageFormat,
data: &str,
out_pixels: &mut heapless::Vec<u16, N>,
) -> Result<(u32, u32), ImageDecodeError> {
match format {
EncodedImageFormat::PpmAscii => decode_ppm_ascii(data, out_pixels),
}
}
}
#[cfg(all(feature = "std", feature = "image-decode"))]
pub fn decode_image_with<const N: usize>(
decoder: &impl ImageDecoder,
format: EncodedImageFormat,
data: &str,
out_pixels: &mut heapless::Vec<u16, N>,
) -> Result<(u32, u32), ImageDecodeError> {
decoder.decode(format, data, out_pixels)
}
#[cfg(all(feature = "std", feature = "image-decode"))]
pub fn decode_image_auto<const N: usize>(
data: &str,
out_pixels: &mut heapless::Vec<u16, N>,
) -> Result<(u32, u32), ImageDecodeError> {
let format = if data.trim_start().starts_with("P3") {
EncodedImageFormat::PpmAscii
} else {
return Err(ImageDecodeError::Unsupported);
};
decode_image_with(&BasicImageDecoder, format, data, out_pixels)
}
#[cfg(all(feature = "std", feature = "image-decode"))]
pub fn decode_ppm_ascii<const N: usize>(
data: &str,
out_pixels: &mut heapless::Vec<u16, N>,
) -> Result<(u32, u32), ImageDecodeError> {
let mut parts = data.split_whitespace();
if parts.next() != Some("P3") {
return Err(ImageDecodeError::InvalidHeader);
}
let width: u32 = parts
.next()
.ok_or(ImageDecodeError::InvalidHeader)?
.parse()
.map_err(|_| ImageDecodeError::InvalidHeader)?;
let height: u32 = parts
.next()
.ok_or(ImageDecodeError::InvalidHeader)?
.parse()
.map_err(|_| ImageDecodeError::InvalidHeader)?;
let maxv: u32 = parts
.next()
.ok_or(ImageDecodeError::InvalidHeader)?
.parse()
.map_err(|_| ImageDecodeError::InvalidHeader)?;
if maxv == 0 {
return Err(ImageDecodeError::InvalidData);
}
out_pixels.clear();
let count = width.saturating_mul(height);
for _ in 0..count {
let r: u32 = parts
.next()
.ok_or(ImageDecodeError::InvalidData)?
.parse()
.map_err(|_| ImageDecodeError::InvalidData)?;
let g: u32 = parts
.next()
.ok_or(ImageDecodeError::InvalidData)?
.parse()
.map_err(|_| ImageDecodeError::InvalidData)?;
let b: u32 = parts
.next()
.ok_or(ImageDecodeError::InvalidData)?
.parse()
.map_err(|_| ImageDecodeError::InvalidData)?;
let r5 = ((r.saturating_mul(31)) / maxv) as u16;
let g6 = ((g.saturating_mul(63)) / maxv) as u16;
let b5 = ((b.saturating_mul(31)) / maxv) as u16;
out_pixels
.push((r5 << 11) | (g6 << 5) | b5)
.map_err(|_| ImageDecodeError::Capacity)?;
}
Ok((width, height))
}