1use core::convert::Infallible;
2use core::ops::Range;
3
4use embedded_graphics_core::Pixel;
5use embedded_graphics_core::draw_target::DrawTarget;
6use embedded_graphics_core::geometry::{OriginDimensions, Point, Size};
7use embedded_graphics_core::pixelcolor::{Rgb565, raw::RawU16};
8use embedded_graphics_core::prelude::{Dimensions, IntoStorage};
9use embedded_graphics_core::primitives::Rectangle;
10
11pub struct Framebuffer<const W: usize, const H: usize> {
30 rows: [[u16; W]; H],
32 dirty_start: u16,
36 dirty_end: u16,
37}
38
39#[derive(Debug)]
41pub struct DirtyRows<'a> {
42 pub y: u16,
44 pub rows: u16,
46 pub bytes: &'a [u8],
48}
49
50impl<const W: usize, const H: usize> Framebuffer<W, H> {
51 const ASSERT_FITS_U16: () =
52 assert!(W > 0 && H > 0 && W <= u16::MAX as usize && H <= u16::MAX as usize);
53
54 pub const fn new() -> Self {
56 let () = Self::ASSERT_FITS_U16;
57 Self {
58 rows: [[0; W]; H],
59 dirty_start: 0,
60 dirty_end: 0,
61 }
62 }
63
64 pub const fn width(&self) -> usize {
66 W
67 }
68
69 pub const fn height(&self) -> usize {
71 H
72 }
73
74 pub fn pixel(&self, x: usize, y: usize) -> Option<Rgb565> {
76 let raw = *self.rows.get(y)?.get(x)?;
77 Some(RawU16::new(u16::from_be(raw)).into())
78 }
79
80 pub fn mark_all_dirty(&mut self) {
82 self.mark_rows(0, H);
83 }
84
85 pub fn take_dirty(&mut self) -> Option<DirtyRows<'_>> {
88 let (start, end) = (self.dirty_start, self.dirty_end);
89 if start >= end {
90 return None;
91 }
92 self.dirty_start = 0;
93 self.dirty_end = 0;
94 let bytes = &self.as_bytes()[usize::from(start) * W * 2..usize::from(end) * W * 2];
95 Some(DirtyRows {
96 y: start,
97 rows: end - start,
98 bytes,
99 })
100 }
101
102 pub fn wire_rows_mut(&mut self, rows: Range<usize>) -> &mut [[u16; W]] {
125 let _ = &self.rows[rows.clone()];
127 if !rows.is_empty() {
128 self.mark_rows(rows.start, rows.end);
129 }
130 &mut self.rows[rows]
131 }
132
133 pub fn as_bytes(&self) -> &[u8] {
135 let px = self.rows.as_flattened();
136 unsafe { core::slice::from_raw_parts(px.as_ptr().cast::<u8>(), px.len() * 2) }
139 }
140
141 #[inline]
142 fn mark_rows(&mut self, start: usize, end: usize) {
143 let (start, end) = (start as u16, end as u16);
145 if self.dirty_start >= self.dirty_end {
146 self.dirty_start = start;
147 self.dirty_end = end;
148 } else {
149 self.dirty_start = self.dirty_start.min(start);
150 self.dirty_end = self.dirty_end.max(end);
151 }
152 }
153
154 #[inline]
155 fn wire(color: Rgb565) -> u16 {
156 wire_color(color)
157 }
158}
159
160#[inline]
163pub fn wire_color(color: Rgb565) -> u16 {
164 color.into_storage().to_be()
165}
166
167impl<const W: usize, const H: usize> Default for Framebuffer<W, H> {
168 fn default() -> Self {
169 Self::new()
170 }
171}
172
173impl<const W: usize, const H: usize> OriginDimensions for Framebuffer<W, H> {
174 fn size(&self) -> Size {
175 Size::new(W as u32, H as u32)
176 }
177}
178
179impl<const W: usize, const H: usize> DrawTarget for Framebuffer<W, H> {
180 type Color = Rgb565;
181 type Error = Infallible;
182
183 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Infallible>
184 where
185 I: IntoIterator<Item = Pixel<Rgb565>>,
186 {
187 let (mut lo, mut hi) = (usize::MAX, 0);
188 for Pixel(Point { x, y }, color) in pixels {
189 let (x, y) = (x as usize, y as usize);
191 if x < W && y < H {
192 self.rows[y][x] = Self::wire(color);
193 lo = lo.min(y);
194 hi = hi.max(y + 1);
195 }
196 }
197 if lo < hi {
198 self.mark_rows(lo, hi);
199 }
200 Ok(())
201 }
202
203 fn fill_solid(&mut self, area: &Rectangle, color: Rgb565) -> Result<(), Infallible> {
204 let area = area.intersection(&self.bounding_box());
205 let Some(br) = area.bottom_right() else {
206 return Ok(());
207 };
208 let (x0, y0) = (area.top_left.x as usize, area.top_left.y as usize);
209 let (x1, y1) = (br.x as usize + 1, br.y as usize + 1);
210 let raw = Self::wire(color);
211 for row in &mut self.rows[y0..y1] {
212 row[x0..x1].fill(raw);
213 }
214 self.mark_rows(y0, y1);
215 Ok(())
216 }
217
218 fn clear(&mut self, color: Rgb565) -> Result<(), Infallible> {
219 self.rows.as_flattened_mut().fill(Self::wire(color));
220 self.mark_all_dirty();
221 Ok(())
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228 use embedded_graphics::prelude::*;
229 use embedded_graphics::primitives::{Line, PrimitiveStyle};
230
231 type Fb = Framebuffer<8, 6>;
232
233 #[test]
234 fn new_is_clean_and_black() {
235 let mut fb = Fb::new();
236 assert!(fb.take_dirty().is_none());
237 assert_eq!(fb.pixel(0, 0), Some(Rgb565::BLACK));
238 assert_eq!(fb.pixel(8, 0), None);
239 }
240
241 #[test]
242 fn pixels_are_stored_big_endian() {
243 let mut fb = Fb::new();
244 let c = Rgb565::new(0b11111, 0, 0b00001); Pixel(Point::new(1, 0), c).draw(&mut fb).unwrap();
246 assert_eq!(&fb.as_bytes()[2..4], &[0xF8, 0x01]);
247 assert_eq!(fb.pixel(1, 0), Some(c));
248 }
249
250 #[test]
251 fn dirty_rows_track_changes() {
252 let mut fb = Fb::new();
253 Line::new(Point::new(0, 2), Point::new(7, 3))
254 .into_styled(PrimitiveStyle::with_stroke(Rgb565::WHITE, 1))
255 .draw(&mut fb)
256 .unwrap();
257 let d = fb.take_dirty().unwrap();
258 assert_eq!((d.y, d.rows, d.bytes.len()), (2, 2, 2 * 8 * 2), "span");
259 assert!(fb.take_dirty().is_none(), "take_dirty marks clean");
260
261 Pixel(Point::new(0, 1), Rgb565::RED).draw(&mut fb).unwrap();
262 Pixel(Point::new(0, 4), Rgb565::RED).draw(&mut fb).unwrap();
263 let d = fb.take_dirty().unwrap();
264 assert_eq!((d.y, d.rows), (1, 4), "separate draws merge");
265 }
266
267 #[test]
268 fn wire_rows_mut_marks_rows_dirty() {
269 let mut fb = Fb::new();
270 assert!(fb.wire_rows_mut(3..3).is_empty());
271 assert!(fb.take_dirty().is_none(), "empty range marks nothing");
272
273 fb.wire_rows_mut(4..5)[0][7] = wire_color(Rgb565::BLUE);
274 fb.wire_rows_mut(1..2);
275 assert_eq!(fb.pixel(7, 4), Some(Rgb565::BLUE));
276 let d = fb.take_dirty().unwrap();
277 assert_eq!((d.y, d.rows), (1, 4), "ranges merge");
278 }
279
280 #[test]
281 fn out_of_bounds_drawing_is_clipped() {
282 let mut fb = Fb::new();
283 Pixel(Point::new(-1, 0), Rgb565::RED).draw(&mut fb).unwrap();
284 Pixel(Point::new(0, 6), Rgb565::RED).draw(&mut fb).unwrap();
285 assert!(fb.take_dirty().is_none());
286
287 fb.fill_solid(
288 &Rectangle::new(Point::new(6, 4), Size::new(10, 10)),
289 Rgb565::GREEN,
290 )
291 .unwrap();
292 assert_eq!(fb.pixel(7, 5), Some(Rgb565::GREEN));
293 assert_eq!(fb.pixel(5, 5), Some(Rgb565::BLACK));
294 let d = fb.take_dirty().unwrap();
295 assert_eq!((d.y, d.rows), (4, 2));
296
297 fb.fill_solid(
298 &Rectangle::new(Point::new(20, 20), Size::new(2, 2)),
299 Rgb565::GREEN,
300 )
301 .unwrap();
302 assert!(fb.take_dirty().is_none());
303 }
304}