1use crate::geometry::Rect;
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq)]
4pub enum ImageFit {
5 Stretch,
6 Center,
7}
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub struct ImageRef<'a> {
11 pub width: u32,
12 pub height: u32,
13 pub pixels: &'a [u16],
14}
15
16impl<'a> ImageRef<'a> {
17 pub const fn new(width: u32, height: u32, pixels: &'a [u16]) -> Self {
18 Self {
19 width,
20 height,
21 pixels,
22 }
23 }
24
25 pub fn bounds_at(&self, rect: Rect, fit: ImageFit) -> Rect {
26 match fit {
27 ImageFit::Stretch => rect,
28 ImageFit::Center => {
29 let x = rect.x + rect.w.saturating_sub(self.width) as i32 / 2;
30 let y = rect.y + rect.h.saturating_sub(self.height) as i32 / 2;
31 Rect::new(x, y, self.width.min(rect.w), self.height.min(rect.h))
32 }
33 }
34 }
35}
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38pub struct SpriteSheet<'a> {
39 pub image: ImageRef<'a>,
40 pub sprite_w: u32,
41 pub sprite_h: u32,
42 pub columns: u32,
43}
44
45#[derive(Clone, Copy, Debug, PartialEq, Eq)]
46pub struct ReelFrame {
47 pub sprite_index: u16,
48 pub duration_ms: u16,
49}
50
51#[derive(Clone, Copy, Debug, PartialEq, Eq)]
52pub struct ReelPlayer<'a> {
53 pub sheet: SpriteSheet<'a>,
54 pub frames: &'a [ReelFrame],
55 pub repeat: bool,
56 current: usize,
57 elapsed_in_frame_ms: u32,
58 finished: bool,
59}
60
61impl<'a> ReelPlayer<'a> {
62 pub const fn new(sheet: SpriteSheet<'a>, frames: &'a [ReelFrame], repeat: bool) -> Self {
63 Self {
64 sheet,
65 frames,
66 repeat,
67 current: 0,
68 elapsed_in_frame_ms: 0,
69 finished: false,
70 }
71 }
72
73 pub fn tick(&mut self, dt_ms: u32) {
74 if self.frames.is_empty() || self.finished {
75 return;
76 }
77 self.elapsed_in_frame_ms = self.elapsed_in_frame_ms.saturating_add(dt_ms);
78 loop {
79 let frame = self.frames[self.current];
80 let frame_ms = u32::from(frame.duration_ms).max(1);
81 if self.elapsed_in_frame_ms < frame_ms {
82 break;
83 }
84 self.elapsed_in_frame_ms -= frame_ms;
85 if self.current + 1 < self.frames.len() {
86 self.current += 1;
87 continue;
88 }
89 if self.repeat {
90 self.current = 0;
91 } else {
92 self.finished = true;
93 }
94 break;
95 }
96 }
97
98 pub const fn is_finished(&self) -> bool {
99 self.finished
100 }
101
102 pub fn restart(&mut self) {
103 self.current = 0;
104 self.elapsed_in_frame_ms = 0;
105 self.finished = false;
106 }
107
108 pub fn current_sprite_rect(&self) -> Option<Rect> {
109 let frame = self.frames.get(self.current)?;
110 Some(self.sheet.sprite_rect(frame.sprite_index as u32))
111 }
112}
113
114impl<'a> SpriteSheet<'a> {
115 pub const fn new(image: ImageRef<'a>, sprite_w: u32, sprite_h: u32) -> Self {
116 let columns = match image.width.checked_div(sprite_w) {
117 Some(c) => c,
118 None => 1,
119 };
120 Self {
121 image,
122 sprite_w,
123 sprite_h,
124 columns,
125 }
126 }
127
128 pub fn sprite_rect(&self, index: u32) -> Rect {
129 let columns = self.columns.max(1);
130 let col = index % columns;
131 let row = index / columns;
132 Rect::new(
133 (col * self.sprite_w) as i32,
134 (row * self.sprite_h) as i32,
135 self.sprite_w,
136 self.sprite_h,
137 )
138 }
139}
140
141#[derive(Clone, Copy, Debug, PartialEq, Eq)]
142pub struct ImageAtlasEntry {
143 pub id: u16,
144 pub rect: Rect,
145}
146
147#[derive(Clone, Copy, Debug, PartialEq, Eq)]
148pub struct ImageAtlas<'a> {
149 pub image: ImageRef<'a>,
150 pub entries: &'a [ImageAtlasEntry],
151}
152
153impl<'a> ImageAtlas<'a> {
154 pub const fn new(image: ImageRef<'a>, entries: &'a [ImageAtlasEntry]) -> Self {
155 Self { image, entries }
156 }
157
158 pub fn rect_for(&self, id: u16) -> Option<Rect> {
159 self.entries
160 .iter()
161 .find(|entry| entry.id == id)
162 .map(|e| e.rect)
163 }
164}
165
166#[cfg(all(feature = "std", feature = "image-decode"))]
167#[derive(Clone, Debug, PartialEq, Eq)]
168pub enum ImageDecodeError {
169 InvalidHeader,
170 Unsupported,
171 InvalidData,
172 Capacity,
173}
174
175#[cfg(all(feature = "std", feature = "image-decode"))]
176#[derive(Clone, Copy, Debug, PartialEq, Eq)]
177pub enum EncodedImageFormat {
178 PpmAscii,
179}
180
181#[cfg(all(feature = "std", feature = "image-decode"))]
182pub trait ImageDecoder {
183 fn decode<const N: usize>(
184 &self,
185 format: EncodedImageFormat,
186 data: &str,
187 out_pixels: &mut heapless::Vec<u16, N>,
188 ) -> Result<(u32, u32), ImageDecodeError>;
189}
190
191#[cfg(all(feature = "std", feature = "image-decode"))]
192#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
193pub struct BasicImageDecoder;
194
195#[cfg(all(feature = "std", feature = "image-decode"))]
196impl ImageDecoder for BasicImageDecoder {
197 fn decode<const N: usize>(
198 &self,
199 format: EncodedImageFormat,
200 data: &str,
201 out_pixels: &mut heapless::Vec<u16, N>,
202 ) -> Result<(u32, u32), ImageDecodeError> {
203 match format {
204 EncodedImageFormat::PpmAscii => decode_ppm_ascii(data, out_pixels),
205 }
206 }
207}
208
209#[cfg(all(feature = "std", feature = "image-decode"))]
210pub fn decode_image_with<const N: usize>(
211 decoder: &impl ImageDecoder,
212 format: EncodedImageFormat,
213 data: &str,
214 out_pixels: &mut heapless::Vec<u16, N>,
215) -> Result<(u32, u32), ImageDecodeError> {
216 decoder.decode(format, data, out_pixels)
217}
218
219#[cfg(all(feature = "std", feature = "image-decode"))]
220pub fn decode_image_auto<const N: usize>(
221 data: &str,
222 out_pixels: &mut heapless::Vec<u16, N>,
223) -> Result<(u32, u32), ImageDecodeError> {
224 let format = if data.trim_start().starts_with("P3") {
225 EncodedImageFormat::PpmAscii
226 } else {
227 return Err(ImageDecodeError::Unsupported);
228 };
229 decode_image_with(&BasicImageDecoder, format, data, out_pixels)
230}
231
232#[cfg(all(feature = "std", feature = "image-decode"))]
233pub fn decode_ppm_ascii<const N: usize>(
234 data: &str,
235 out_pixels: &mut heapless::Vec<u16, N>,
236) -> Result<(u32, u32), ImageDecodeError> {
237 let mut parts = data.split_whitespace();
238 if parts.next() != Some("P3") {
239 return Err(ImageDecodeError::InvalidHeader);
240 }
241 let width: u32 = parts
242 .next()
243 .ok_or(ImageDecodeError::InvalidHeader)?
244 .parse()
245 .map_err(|_| ImageDecodeError::InvalidHeader)?;
246 let height: u32 = parts
247 .next()
248 .ok_or(ImageDecodeError::InvalidHeader)?
249 .parse()
250 .map_err(|_| ImageDecodeError::InvalidHeader)?;
251 let maxv: u32 = parts
252 .next()
253 .ok_or(ImageDecodeError::InvalidHeader)?
254 .parse()
255 .map_err(|_| ImageDecodeError::InvalidHeader)?;
256 if maxv == 0 {
257 return Err(ImageDecodeError::InvalidData);
258 }
259 out_pixels.clear();
260 let count = width.saturating_mul(height);
261 for _ in 0..count {
262 let r: u32 = parts
263 .next()
264 .ok_or(ImageDecodeError::InvalidData)?
265 .parse()
266 .map_err(|_| ImageDecodeError::InvalidData)?;
267 let g: u32 = parts
268 .next()
269 .ok_or(ImageDecodeError::InvalidData)?
270 .parse()
271 .map_err(|_| ImageDecodeError::InvalidData)?;
272 let b: u32 = parts
273 .next()
274 .ok_or(ImageDecodeError::InvalidData)?
275 .parse()
276 .map_err(|_| ImageDecodeError::InvalidData)?;
277 let r5 = ((r.saturating_mul(31)) / maxv) as u16;
278 let g6 = ((g.saturating_mul(63)) / maxv) as u16;
279 let b5 = ((b.saturating_mul(31)) / maxv) as u16;
280 out_pixels
281 .push((r5 << 11) | (g6 << 5) | b5)
282 .map_err(|_| ImageDecodeError::Capacity)?;
283 }
284 Ok((width, height))
285}