Skip to main content

embedded_gui/
framebuffer.rs

1//! A RAM-backed, readback-capable framebuffer.
2//!
3//! [`Framebuffer`] implements [`DrawTarget`] **and** [`PixelRead`], so rendering
4//! into it unlocks true per-pixel alpha compositing (`RenderCtx::*_true_alpha`)
5//! instead of the ordered-dither approximation used for write-only displays.
6//! The typical pattern is a software double buffer: render the UI into a
7//! `Framebuffer`, then blit it to the physical panel once per frame.
8//!
9//! Storage is a fixed `[Rgb565; N]` array (no allocator). Pick `N >= W * H`;
10//! put the buffer behind a `StaticCell` (or on the stack for host tooling).
11
12use embedded_graphics_core::{
13    Pixel,
14    draw_target::DrawTarget,
15    geometry::{OriginDimensions, Point, Size},
16    pixelcolor::{Gray8, GrayColor, Rgb565, RgbColor},
17    primitives::Rectangle,
18};
19
20use crate::geometry::Rect;
21use crate::render::PixelRead;
22
23/// An 8-bit RGBA color representation (32-bit total).
24#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
25pub struct Rgba8888 {
26    pub r: u8,
27    pub g: u8,
28    pub b: u8,
29    pub a: u8,
30}
31
32impl Rgba8888 {
33    pub const BLACK: Self = Self {
34        r: 0,
35        g: 0,
36        b: 0,
37        a: 255,
38    };
39    pub const WHITE: Self = Self {
40        r: 255,
41        g: 255,
42        b: 255,
43        a: 255,
44    };
45    pub const TRANSPARENT: Self = Self {
46        r: 0,
47        g: 0,
48        b: 0,
49        a: 0,
50    };
51
52    pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
53        Self { r, g, b, a }
54    }
55
56    pub fn from_rgb565(rgb: Rgb565, alpha: u8) -> Self {
57        let r5 = rgb.r();
58        let g6 = rgb.g();
59        let b5 = rgb.b();
60        let r8 = (r5 << 3) | (r5 >> 2);
61        let g8 = (g6 << 2) | (g6 >> 4);
62        let b8 = (b5 << 3) | (b5 >> 2);
63        Self {
64            r: r8,
65            g: g8,
66            b: b8,
67            a: alpha,
68        }
69    }
70
71    pub fn to_rgb565(self) -> Rgb565 {
72        Rgb565::new(self.r >> 3, self.g >> 2, self.b >> 3)
73    }
74}
75
76impl embedded_graphics_core::pixelcolor::PixelColor for Rgba8888 {
77    type Raw = embedded_graphics_core::pixelcolor::raw::RawU32;
78}
79
80/// A fixed-capacity RGB565 framebuffer. `N` is the backing array length and
81/// must be at least `width * height`.
82#[derive(Clone, Debug, PartialEq, Eq)]
83pub struct Framebuffer<const N: usize> {
84    pixels: [Rgb565; N],
85    width: u32,
86    height: u32,
87}
88
89impl<const N: usize> Framebuffer<N> {
90    /// Create a `width × height` framebuffer cleared to black.
91    ///
92    /// Panics if `width * height > N`.
93    pub fn new(width: u32, height: u32) -> Self {
94        assert!(
95            (width as usize) * (height as usize) <= N,
96            "Framebuffer backing array too small for width * height",
97        );
98        Self {
99            pixels: [Rgb565::BLACK; N],
100            width,
101            height,
102        }
103    }
104
105    /// Fill the whole framebuffer with a single color.
106    pub fn clear_color(&mut self, color: Rgb565) {
107        let len = self.len();
108        for p in self.pixels[..len].iter_mut() {
109            *p = color;
110        }
111    }
112
113    /// The active pixels, row-major, `width * height` long. Handy for blitting
114    /// to a physical display.
115    pub fn pixels(&self) -> &[Rgb565] {
116        &self.pixels[..self.len()]
117    }
118
119    /// Mutable slice of active pixels.
120    pub fn pixels_mut(&mut self) -> &mut [Rgb565] {
121        let len = self.len();
122        &mut self.pixels[..len]
123    }
124
125    pub const fn width(&self) -> u32 {
126        self.width
127    }
128
129    pub const fn height(&self) -> u32 {
130        self.height
131    }
132
133    #[inline]
134    fn len(&self) -> usize {
135        (self.width as usize) * (self.height as usize)
136    }
137
138    #[inline]
139    fn index(&self, x: i32, y: i32) -> Option<usize> {
140        if x < 0 || y < 0 || x as u32 >= self.width || y as u32 >= self.height {
141            return None;
142        }
143        Some((y as usize) * (self.width as usize) + (x as usize))
144    }
145
146    /// Apply Fast IIR Blur to the whole framebuffer.
147    pub fn apply_iir_blur(&mut self, blur_degree: u8) {
148        let rect = Rect::new(0, 0, self.width, self.height);
149        self.blur_rect(rect, blur_degree);
150    }
151
152    /// Apply Fast IIR Blur to a sub-region `rect` of the framebuffer.
153    pub fn blur_rect(&mut self, rect: Rect, blur_degree: u8) {
154        if blur_degree == 0 || self.width == 0 || self.height == 0 {
155            return;
156        }
157
158        let x0 = rect.x.max(0) as u32;
159        let y0 = rect.y.max(0) as u32;
160        let x1 = (rect.right() as u32).min(self.width);
161        let y1 = (rect.bottom() as u32).min(self.height);
162
163        if x0 >= x1 || y0 >= y1 {
164            return;
165        }
166
167        let alpha = 256 - (blur_degree as i32);
168        let w = self.width as usize;
169
170        // Pass 1: Horizontal Forward
171        for y in y0..y1 {
172            let row_start = y as usize * w;
173            let idx0 = row_start + x0 as usize;
174            let raw0 = self.pixels[idx0];
175            let (r5, g6, b5) = (raw0.r(), raw0.g(), raw0.b());
176            let mut acc_r = (((r5 << 3) | (r5 >> 2)) as i32) << 8;
177            let mut acc_g = (((g6 << 2) | (g6 >> 4)) as i32) << 8;
178            let mut acc_b = (((b5 << 3) | (b5 >> 2)) as i32) << 8;
179
180            for x in x0..x1 {
181                let idx = row_start + x as usize;
182                let raw = self.pixels[idx];
183                let (r5, g6, b5) = (raw.r(), raw.g(), raw.b());
184                let r8 = ((r5 << 3) | (r5 >> 2)) as i32;
185                let g8 = ((g6 << 2) | (g6 >> 4)) as i32;
186                let b8 = ((b5 << 3) | (b5 >> 2)) as i32;
187
188                acc_r += (((r8 << 8) - acc_r) * alpha) >> 8;
189                acc_g += (((g8 << 8) - acc_g) * alpha) >> 8;
190                acc_b += (((b8 << 8) - acc_b) * alpha) >> 8;
191
192                let r_out = ((acc_r >> 8).clamp(0, 255) as u8) >> 3;
193                let g_out = ((acc_g >> 8).clamp(0, 255) as u8) >> 2;
194                let b_out = ((acc_b >> 8).clamp(0, 255) as u8) >> 3;
195                self.pixels[idx] = Rgb565::new(r_out, g_out, b_out);
196            }
197        }
198
199        // Pass 2: Horizontal Reverse
200        for y in y0..y1 {
201            let row_start = y as usize * w;
202            let idx_last = row_start + (x1 - 1) as usize;
203            let raw_last = self.pixels[idx_last];
204            let (r5, g6, b5) = (raw_last.r(), raw_last.g(), raw_last.b());
205            let mut acc_r = (((r5 << 3) | (r5 >> 2)) as i32) << 8;
206            let mut acc_g = (((g6 << 2) | (g6 >> 4)) as i32) << 8;
207            let mut acc_b = (((b5 << 3) | (b5 >> 2)) as i32) << 8;
208
209            for x in (x0..x1).rev() {
210                let idx = row_start + x as usize;
211                let raw = self.pixels[idx];
212                let (r5, g6, b5) = (raw.r(), raw.g(), raw.b());
213                let r8 = ((r5 << 3) | (r5 >> 2)) as i32;
214                let g8 = ((g6 << 2) | (g6 >> 4)) as i32;
215                let b8 = ((b5 << 3) | (b5 >> 2)) as i32;
216
217                acc_r += (((r8 << 8) - acc_r) * alpha) >> 8;
218                acc_g += (((g8 << 8) - acc_g) * alpha) >> 8;
219                acc_b += (((b8 << 8) - acc_b) * alpha) >> 8;
220
221                let r_out = ((acc_r >> 8).clamp(0, 255) as u8) >> 3;
222                let g_out = ((acc_g >> 8).clamp(0, 255) as u8) >> 2;
223                let b_out = ((acc_b >> 8).clamp(0, 255) as u8) >> 3;
224                self.pixels[idx] = Rgb565::new(r_out, g_out, b_out);
225            }
226        }
227
228        // Pass 3: Vertical Forward
229        for x in x0..x1 {
230            let idx0 = y0 as usize * w + x as usize;
231            let raw0 = self.pixels[idx0];
232            let (r5, g6, b5) = (raw0.r(), raw0.g(), raw0.b());
233            let mut acc_r = (((r5 << 3) | (r5 >> 2)) as i32) << 8;
234            let mut acc_g = (((g6 << 2) | (g6 >> 4)) as i32) << 8;
235            let mut acc_b = (((b5 << 3) | (b5 >> 2)) as i32) << 8;
236
237            for y in y0..y1 {
238                let idx = y as usize * w + x as usize;
239                let raw = self.pixels[idx];
240                let (r5, g6, b5) = (raw.r(), raw.g(), raw.b());
241                let r8 = ((r5 << 3) | (r5 >> 2)) as i32;
242                let g8 = ((g6 << 2) | (g6 >> 4)) as i32;
243                let b8 = ((b5 << 3) | (b5 >> 2)) as i32;
244
245                acc_r += (((r8 << 8) - acc_r) * alpha) >> 8;
246                acc_g += (((g8 << 8) - acc_g) * alpha) >> 8;
247                acc_b += (((b8 << 8) - acc_b) * alpha) >> 8;
248
249                let r_out = ((acc_r >> 8).clamp(0, 255) as u8) >> 3;
250                let g_out = ((acc_g >> 8).clamp(0, 255) as u8) >> 2;
251                let b_out = ((acc_b >> 8).clamp(0, 255) as u8) >> 3;
252                self.pixels[idx] = Rgb565::new(r_out, g_out, b_out);
253            }
254        }
255
256        // Pass 4: Vertical Reverse
257        for x in x0..x1 {
258            let idx_last = (y1 - 1) as usize * w + x as usize;
259            let raw_last = self.pixels[idx_last];
260            let (r5, g6, b5) = (raw_last.r(), raw_last.g(), raw_last.b());
261            let mut acc_r = (((r5 << 3) | (r5 >> 2)) as i32) << 8;
262            let mut acc_g = (((g6 << 2) | (g6 >> 4)) as i32) << 8;
263            let mut acc_b = (((b5 << 3) | (b5 >> 2)) as i32) << 8;
264
265            for y in (y0..y1).rev() {
266                let idx = y as usize * w + x as usize;
267                let raw = self.pixels[idx];
268                let (r5, g6, b5) = (raw.r(), raw.g(), raw.b());
269                let r8 = ((r5 << 3) | (r5 >> 2)) as i32;
270                let g8 = ((g6 << 2) | (g6 >> 4)) as i32;
271                let b8 = ((b5 << 3) | (b5 >> 2)) as i32;
272
273                acc_r += (((r8 << 8) - acc_r) * alpha) >> 8;
274                acc_g += (((g8 << 8) - acc_g) * alpha) >> 8;
275                acc_b += (((b8 << 8) - acc_b) * alpha) >> 8;
276
277                let r_out = ((acc_r >> 8).clamp(0, 255) as u8) >> 3;
278                let g_out = ((acc_g >> 8).clamp(0, 255) as u8) >> 2;
279                let b_out = ((acc_b >> 8).clamp(0, 255) as u8) >> 3;
280                self.pixels[idx] = Rgb565::new(r_out, g_out, b_out);
281            }
282        }
283    }
284
285    /// Apply reverse colour (color inversion) filter to a sub-region `rect`.
286    pub fn reverse_colour_rect(&mut self, rect: Rect) {
287        let x0 = rect.x.max(0) as u32;
288        let y0 = rect.y.max(0) as u32;
289        let x1 = (rect.right() as u32).min(self.width);
290        let y1 = (rect.bottom() as u32).min(self.height);
291
292        if x0 >= x1 || y0 >= y1 {
293            return;
294        }
295
296        let w = self.width as usize;
297        for y in y0..y1 {
298            let row_start = y as usize * w;
299            for x in x0..x1 {
300                let idx = row_start + x as usize;
301                let c = self.pixels[idx];
302                self.pixels[idx] = Rgb565::new(31 - c.r(), 63 - c.g(), 31 - c.b());
303            }
304        }
305    }
306
307    /// Apply reverse colour filter to the whole framebuffer.
308    pub fn apply_reverse_colour(&mut self) {
309        let rect = Rect::new(0, 0, self.width, self.height);
310        self.reverse_colour_rect(rect);
311    }
312}
313
314impl<const N: usize> OriginDimensions for Framebuffer<N> {
315    fn size(&self) -> Size {
316        Size::new(self.width, self.height)
317    }
318}
319
320impl<const N: usize> DrawTarget for Framebuffer<N> {
321    type Color = Rgb565;
322    type Error = core::convert::Infallible;
323
324    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
325    where
326        I: IntoIterator<Item = Pixel<Self::Color>>,
327    {
328        for Pixel(point, color) in pixels {
329            if let Some(idx) = self.index(point.x, point.y) {
330                self.pixels[idx] = color;
331            }
332        }
333        Ok(())
334    }
335
336    fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> {
337        let intersect = area.intersection(&Rectangle::new(Point::zero(), self.size()));
338        if intersect.is_zero_sized() {
339            return Ok(());
340        }
341        let x0 = intersect.top_left.x as usize;
342        let y0 = intersect.top_left.y as usize;
343        let w = intersect.size.width as usize;
344        let h = intersect.size.height as usize;
345        let stride = self.width as usize;
346
347        for row in 0..h {
348            let start = (y0 + row) * stride + x0;
349            if start + w <= N {
350                self.pixels[start..start + w].fill(color);
351            }
352        }
353        Ok(())
354    }
355
356    fn fill_contiguous<I>(&mut self, area: &Rectangle, colors: I) -> Result<(), Self::Error>
357    where
358        I: IntoIterator<Item = Self::Color>,
359    {
360        let intersect = area.intersection(&Rectangle::new(Point::zero(), self.size()));
361        if intersect.is_zero_sized() {
362            return Ok(());
363        }
364        let mut colors = colors.into_iter();
365        let stride = self.width as usize;
366        let x_end = area.top_left.x + area.size.width as i32;
367        let y_end = area.top_left.y + area.size.height as i32;
368
369        for y in area.top_left.y..y_end {
370            for x in area.top_left.x..x_end {
371                if let Some(c) = colors.next() {
372                    if x >= 0 && y >= 0 && (x as u32) < self.width && (y as u32) < self.height {
373                        let idx = (y as usize) * stride + (x as usize);
374                        if idx < N {
375                            self.pixels[idx] = c;
376                        }
377                    }
378                } else {
379                    return Ok(());
380                }
381            }
382        }
383        Ok(())
384    }
385
386    fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> {
387        let len = self.len();
388        self.pixels[..len].fill(color);
389        Ok(())
390    }
391}
392
393impl<const N: usize> PixelRead for Framebuffer<N> {
394    fn get_pixel(&self, point: Point) -> Rgb565 {
395        match self.index(point.x, point.y) {
396            Some(idx) => self.pixels[idx],
397            None => Rgb565::BLACK,
398        }
399    }
400}
401
402/// A fixed-capacity RGBA8888 framebuffer.
403#[derive(Clone, Debug, PartialEq, Eq)]
404pub struct FramebufferRgba8888<const N: usize> {
405    pixels: [Rgba8888; N],
406    width: u32,
407    height: u32,
408}
409
410impl<const N: usize> FramebufferRgba8888<N> {
411    pub fn new(width: u32, height: u32) -> Self {
412        assert!(
413            (width as usize) * (height as usize) <= N,
414            "Framebuffer backing array too small for width * height",
415        );
416        Self {
417            pixels: [Rgba8888::BLACK; N],
418            width,
419            height,
420        }
421    }
422
423    pub fn clear_color(&mut self, color: Rgba8888) {
424        let len = self.len();
425        for p in self.pixels[..len].iter_mut() {
426            *p = color;
427        }
428    }
429
430    pub fn pixels(&self) -> &[Rgba8888] {
431        &self.pixels[..self.len()]
432    }
433
434    pub fn pixels_mut(&mut self) -> &mut [Rgba8888] {
435        let len = self.len();
436        &mut self.pixels[..len]
437    }
438
439    pub const fn width(&self) -> u32 {
440        self.width
441    }
442
443    pub const fn height(&self) -> u32 {
444        self.height
445    }
446
447    #[inline]
448    fn len(&self) -> usize {
449        (self.width as usize) * (self.height as usize)
450    }
451
452    #[inline]
453    fn index(&self, x: i32, y: i32) -> Option<usize> {
454        if x < 0 || y < 0 || x as u32 >= self.width || y as u32 >= self.height {
455            return None;
456        }
457        Some((y as usize) * (self.width as usize) + (x as usize))
458    }
459
460    pub fn apply_iir_blur(&mut self, blur_degree: u8) {
461        let rect = Rect::new(0, 0, self.width, self.height);
462        self.blur_rect(rect, blur_degree);
463    }
464
465    pub fn blur_rect(&mut self, rect: Rect, blur_degree: u8) {
466        if blur_degree == 0 || self.width == 0 || self.height == 0 {
467            return;
468        }
469        let x0 = rect.x.max(0) as u32;
470        let y0 = rect.y.max(0) as u32;
471        let x1 = (rect.right() as u32).min(self.width);
472        let y1 = (rect.bottom() as u32).min(self.height);
473
474        if x0 >= x1 || y0 >= y1 {
475            return;
476        }
477        let alpha = 256 - (blur_degree as i32);
478        let w = self.width as usize;
479
480        // Pass 1: Horizontal Forward
481        for y in y0..y1 {
482            let row_start = y as usize * w;
483            let p0 = self.pixels[row_start + x0 as usize];
484            let mut acc_r = (p0.r as i32) << 8;
485            let mut acc_g = (p0.g as i32) << 8;
486            let mut acc_b = (p0.b as i32) << 8;
487            let mut acc_a = (p0.a as i32) << 8;
488
489            for x in x0..x1 {
490                let idx = row_start + x as usize;
491                let p = self.pixels[idx];
492                acc_r += ((((p.r as i32) << 8) - acc_r) * alpha) >> 8;
493                acc_g += ((((p.g as i32) << 8) - acc_g) * alpha) >> 8;
494                acc_b += ((((p.b as i32) << 8) - acc_b) * alpha) >> 8;
495                acc_a += ((((p.a as i32) << 8) - acc_a) * alpha) >> 8;
496
497                self.pixels[idx] = Rgba8888 {
498                    r: (acc_r >> 8).clamp(0, 255) as u8,
499                    g: (acc_g >> 8).clamp(0, 255) as u8,
500                    b: (acc_b >> 8).clamp(0, 255) as u8,
501                    a: (acc_a >> 8).clamp(0, 255) as u8,
502                };
503            }
504        }
505
506        // Pass 2: Horizontal Reverse
507        for y in y0..y1 {
508            let row_start = y as usize * w;
509            let p_last = self.pixels[row_start + (x1 - 1) as usize];
510            let mut acc_r = (p_last.r as i32) << 8;
511            let mut acc_g = (p_last.g as i32) << 8;
512            let mut acc_b = (p_last.b as i32) << 8;
513            let mut acc_a = (p_last.a as i32) << 8;
514
515            for x in (x0..x1).rev() {
516                let idx = row_start + x as usize;
517                let p = self.pixels[idx];
518                acc_r += ((((p.r as i32) << 8) - acc_r) * alpha) >> 8;
519                acc_g += ((((p.g as i32) << 8) - acc_g) * alpha) >> 8;
520                acc_b += ((((p.b as i32) << 8) - acc_b) * alpha) >> 8;
521                acc_a += ((((p.a as i32) << 8) - acc_a) * alpha) >> 8;
522
523                self.pixels[idx] = Rgba8888 {
524                    r: (acc_r >> 8).clamp(0, 255) as u8,
525                    g: (acc_g >> 8).clamp(0, 255) as u8,
526                    b: (acc_b >> 8).clamp(0, 255) as u8,
527                    a: (acc_a >> 8).clamp(0, 255) as u8,
528                };
529            }
530        }
531
532        // Pass 3: Vertical Forward
533        for x in x0..x1 {
534            let p0 = self.pixels[y0 as usize * w + x as usize];
535            let mut acc_r = (p0.r as i32) << 8;
536            let mut acc_g = (p0.g as i32) << 8;
537            let mut acc_b = (p0.b as i32) << 8;
538            let mut acc_a = (p0.a as i32) << 8;
539
540            for y in y0..y1 {
541                let idx = y as usize * w + x as usize;
542                let p = self.pixels[idx];
543                acc_r += ((((p.r as i32) << 8) - acc_r) * alpha) >> 8;
544                acc_g += ((((p.g as i32) << 8) - acc_g) * alpha) >> 8;
545                acc_b += ((((p.b as i32) << 8) - acc_b) * alpha) >> 8;
546                acc_a += ((((p.a as i32) << 8) - acc_a) * alpha) >> 8;
547
548                self.pixels[idx] = Rgba8888 {
549                    r: (acc_r >> 8).clamp(0, 255) as u8,
550                    g: (acc_g >> 8).clamp(0, 255) as u8,
551                    b: (acc_b >> 8).clamp(0, 255) as u8,
552                    a: (acc_a >> 8).clamp(0, 255) as u8,
553                };
554            }
555        }
556
557        // Pass 4: Vertical Reverse
558        for x in x0..x1 {
559            let p_last = self.pixels[(y1 - 1) as usize * w + x as usize];
560            let mut acc_r = (p_last.r as i32) << 8;
561            let mut acc_g = (p_last.g as i32) << 8;
562            let mut acc_b = (p_last.b as i32) << 8;
563            let mut acc_a = (p_last.a as i32) << 8;
564
565            for y in (y0..y1).rev() {
566                let idx = y as usize * w + x as usize;
567                let p = self.pixels[idx];
568                acc_r += ((((p.r as i32) << 8) - acc_r) * alpha) >> 8;
569                acc_g += ((((p.g as i32) << 8) - acc_g) * alpha) >> 8;
570                acc_b += ((((p.b as i32) << 8) - acc_b) * alpha) >> 8;
571                acc_a += ((((p.a as i32) << 8) - acc_a) * alpha) >> 8;
572
573                self.pixels[idx] = Rgba8888 {
574                    r: (acc_r >> 8).clamp(0, 255) as u8,
575                    g: (acc_g >> 8).clamp(0, 255) as u8,
576                    b: (acc_b >> 8).clamp(0, 255) as u8,
577                    a: (acc_a >> 8).clamp(0, 255) as u8,
578                };
579            }
580        }
581    }
582
583    /// Apply reverse colour (color inversion) filter to a sub-region `rect`.
584    pub fn reverse_colour_rect(&mut self, rect: Rect) {
585        let x0 = rect.x.max(0) as u32;
586        let y0 = rect.y.max(0) as u32;
587        let x1 = (rect.right() as u32).min(self.width);
588        let y1 = (rect.bottom() as u32).min(self.height);
589
590        if x0 >= x1 || y0 >= y1 {
591            return;
592        }
593
594        let w = self.width as usize;
595        for y in y0..y1 {
596            let row_start = y as usize * w;
597            for x in x0..x1 {
598                let idx = row_start + x as usize;
599                let p = self.pixels[idx];
600                self.pixels[idx] = Rgba8888 {
601                    r: 255 - p.r,
602                    g: 255 - p.g,
603                    b: 255 - p.b,
604                    a: p.a,
605                };
606            }
607        }
608    }
609
610    /// Apply reverse colour filter to the whole framebuffer.
611    pub fn apply_reverse_colour(&mut self) {
612        let rect = Rect::new(0, 0, self.width, self.height);
613        self.reverse_colour_rect(rect);
614    }
615}
616
617impl<const N: usize> OriginDimensions for FramebufferRgba8888<N> {
618    fn size(&self) -> Size {
619        Size::new(self.width, self.height)
620    }
621}
622
623impl<const N: usize> DrawTarget for FramebufferRgba8888<N> {
624    type Color = Rgba8888;
625    type Error = core::convert::Infallible;
626
627    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
628    where
629        I: IntoIterator<Item = Pixel<Self::Color>>,
630    {
631        for Pixel(point, color) in pixels {
632            if let Some(idx) = self.index(point.x, point.y) {
633                self.pixels[idx] = color;
634            }
635        }
636        Ok(())
637    }
638
639    fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> {
640        let intersect = area.intersection(&Rectangle::new(Point::zero(), self.size()));
641        if intersect.is_zero_sized() {
642            return Ok(());
643        }
644        let x0 = intersect.top_left.x as usize;
645        let y0 = intersect.top_left.y as usize;
646        let w = intersect.size.width as usize;
647        let h = intersect.size.height as usize;
648        let stride = self.width as usize;
649
650        for row in 0..h {
651            let start = (y0 + row) * stride + x0;
652            if start + w <= N {
653                self.pixels[start..start + w].fill(color);
654            }
655        }
656        Ok(())
657    }
658
659    fn fill_contiguous<I>(&mut self, area: &Rectangle, colors: I) -> Result<(), Self::Error>
660    where
661        I: IntoIterator<Item = Self::Color>,
662    {
663        let intersect = area.intersection(&Rectangle::new(Point::zero(), self.size()));
664        if intersect.is_zero_sized() {
665            return Ok(());
666        }
667        let mut colors = colors.into_iter();
668        let stride = self.width as usize;
669        let x_end = area.top_left.x + area.size.width as i32;
670        let y_end = area.top_left.y + area.size.height as i32;
671
672        for y in area.top_left.y..y_end {
673            for x in area.top_left.x..x_end {
674                if let Some(c) = colors.next() {
675                    if x >= 0 && y >= 0 && (x as u32) < self.width && (y as u32) < self.height {
676                        let idx = (y as usize) * stride + (x as usize);
677                        if idx < N {
678                            self.pixels[idx] = c;
679                        }
680                    }
681                } else {
682                    return Ok(());
683                }
684            }
685        }
686        Ok(())
687    }
688
689    fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> {
690        let len = self.len();
691        self.pixels[..len].fill(color);
692        Ok(())
693    }
694}
695
696impl<const N: usize> PixelRead for FramebufferRgba8888<N> {
697    fn get_pixel(&self, point: Point) -> Rgba8888 {
698        match self.index(point.x, point.y) {
699            Some(idx) => self.pixels[idx],
700            None => Rgba8888::TRANSPARENT,
701        }
702    }
703}
704
705/// A fixed-capacity Gray8 framebuffer.
706#[derive(Clone, Debug, PartialEq, Eq)]
707pub struct FramebufferGray8<const N: usize> {
708    pixels: [Gray8; N],
709    width: u32,
710    height: u32,
711}
712
713impl<const N: usize> FramebufferGray8<N> {
714    pub fn new(width: u32, height: u32) -> Self {
715        assert!(
716            (width as usize) * (height as usize) <= N,
717            "Framebuffer backing array too small for width * height",
718        );
719        Self {
720            pixels: [Gray8::new(0); N],
721            width,
722            height,
723        }
724    }
725
726    pub fn clear_color(&mut self, color: Gray8) {
727        let len = self.len();
728        for p in self.pixels[..len].iter_mut() {
729            *p = color;
730        }
731    }
732
733    pub fn pixels(&self) -> &[Gray8] {
734        &self.pixels[..self.len()]
735    }
736
737    pub fn pixels_mut(&mut self) -> &mut [Gray8] {
738        let len = self.len();
739        &mut self.pixels[..len]
740    }
741
742    pub const fn width(&self) -> u32 {
743        self.width
744    }
745
746    pub const fn height(&self) -> u32 {
747        self.height
748    }
749
750    #[inline]
751    fn len(&self) -> usize {
752        (self.width as usize) * (self.height as usize)
753    }
754
755    #[inline]
756    fn index(&self, x: i32, y: i32) -> Option<usize> {
757        if x < 0 || y < 0 || x as u32 >= self.width || y as u32 >= self.height {
758            return None;
759        }
760        Some((y as usize) * (self.width as usize) + (x as usize))
761    }
762
763    pub fn apply_iir_blur(&mut self, blur_degree: u8) {
764        let rect = Rect::new(0, 0, self.width, self.height);
765        self.blur_rect(rect, blur_degree);
766    }
767
768    pub fn blur_rect(&mut self, rect: Rect, blur_degree: u8) {
769        if blur_degree == 0 || self.width == 0 || self.height == 0 {
770            return;
771        }
772        let x0 = rect.x.max(0) as u32;
773        let y0 = rect.y.max(0) as u32;
774        let x1 = (rect.right() as u32).min(self.width);
775        let y1 = (rect.bottom() as u32).min(self.height);
776
777        if x0 >= x1 || y0 >= y1 {
778            return;
779        }
780        let alpha = 256 - (blur_degree as i32);
781        let w = self.width as usize;
782
783        // Pass 1: Horizontal Forward
784        for y in y0..y1 {
785            let row_start = y as usize * w;
786            let p0 = self.pixels[row_start + x0 as usize].luma();
787            let mut acc = (p0 as i32) << 8;
788
789            for x in x0..x1 {
790                let idx = row_start + x as usize;
791                let luma = self.pixels[idx].luma();
792                acc += ((((luma as i32) << 8) - acc) * alpha) >> 8;
793                self.pixels[idx] = Gray8::new((acc >> 8).clamp(0, 255) as u8);
794            }
795        }
796
797        // Pass 2: Horizontal Reverse
798        for y in y0..y1 {
799            let row_start = y as usize * w;
800            let p_last = self.pixels[row_start + (x1 - 1) as usize].luma();
801            let mut acc = (p_last as i32) << 8;
802
803            for x in (x0..x1).rev() {
804                let idx = row_start + x as usize;
805                let luma = self.pixels[idx].luma();
806                acc += ((((luma as i32) << 8) - acc) * alpha) >> 8;
807                self.pixels[idx] = Gray8::new((acc >> 8).clamp(0, 255) as u8);
808            }
809        }
810
811        // Pass 3: Vertical Forward
812        for x in x0..x1 {
813            let p0 = self.pixels[y0 as usize * w + x as usize].luma();
814            let mut acc = (p0 as i32) << 8;
815
816            for y in y0..y1 {
817                let idx = y as usize * w + x as usize;
818                let luma = self.pixels[idx].luma();
819                acc += ((((luma as i32) << 8) - acc) * alpha) >> 8;
820                self.pixels[idx] = Gray8::new((acc >> 8).clamp(0, 255) as u8);
821            }
822        }
823
824        // Pass 4: Vertical Reverse
825        for x in x0..x1 {
826            let p_last = self.pixels[(y1 - 1) as usize * w + x as usize].luma();
827            let mut acc = (p_last as i32) << 8;
828
829            for y in (y0..y1).rev() {
830                let idx = y as usize * w + x as usize;
831                let luma = self.pixels[idx].luma();
832                acc += ((((luma as i32) << 8) - acc) * alpha) >> 8;
833                self.pixels[idx] = Gray8::new((acc >> 8).clamp(0, 255) as u8);
834            }
835        }
836    }
837}
838
839impl<const N: usize> OriginDimensions for FramebufferGray8<N> {
840    fn size(&self) -> Size {
841        Size::new(self.width, self.height)
842    }
843}
844
845impl<const N: usize> DrawTarget for FramebufferGray8<N> {
846    type Color = Gray8;
847    type Error = core::convert::Infallible;
848
849    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
850    where
851        I: IntoIterator<Item = Pixel<Self::Color>>,
852    {
853        for Pixel(point, color) in pixels {
854            if let Some(idx) = self.index(point.x, point.y) {
855                self.pixels[idx] = color;
856            }
857        }
858        Ok(())
859    }
860
861    fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> {
862        let intersect = area.intersection(&Rectangle::new(Point::zero(), self.size()));
863        if intersect.is_zero_sized() {
864            return Ok(());
865        }
866        let x0 = intersect.top_left.x as usize;
867        let y0 = intersect.top_left.y as usize;
868        let w = intersect.size.width as usize;
869        let h = intersect.size.height as usize;
870        let stride = self.width as usize;
871
872        for row in 0..h {
873            let start = (y0 + row) * stride + x0;
874            if start + w <= N {
875                self.pixels[start..start + w].fill(color);
876            }
877        }
878        Ok(())
879    }
880
881    fn fill_contiguous<I>(&mut self, area: &Rectangle, colors: I) -> Result<(), Self::Error>
882    where
883        I: IntoIterator<Item = Self::Color>,
884    {
885        let intersect = area.intersection(&Rectangle::new(Point::zero(), self.size()));
886        if intersect.is_zero_sized() {
887            return Ok(());
888        }
889        let mut colors = colors.into_iter();
890        let stride = self.width as usize;
891        let x_end = area.top_left.x + area.size.width as i32;
892        let y_end = area.top_left.y + area.size.height as i32;
893
894        for y in area.top_left.y..y_end {
895            for x in area.top_left.x..x_end {
896                if let Some(c) = colors.next() {
897                    if x >= 0 && y >= 0 && (x as u32) < self.width && (y as u32) < self.height {
898                        let idx = (y as usize) * stride + (x as usize);
899                        if idx < N {
900                            self.pixels[idx] = c;
901                        }
902                    }
903                } else {
904                    return Ok(());
905                }
906            }
907        }
908        Ok(())
909    }
910
911    fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> {
912        let len = self.len();
913        self.pixels[..len].fill(color);
914        Ok(())
915    }
916}
917
918impl<const N: usize> PixelRead for FramebufferGray8<N> {
919    fn get_pixel(&self, point: Point) -> Gray8 {
920        match self.index(point.x, point.y) {
921            Some(idx) => self.pixels[idx],
922            None => Gray8::new(0),
923        }
924    }
925}
926
927#[cfg(test)]
928mod tests {
929    use super::*;
930
931    #[test]
932    fn test_iir_blur_rgb565() {
933        let mut fb = Framebuffer::<100>::new(10, 10);
934        fb.clear_color(Rgb565::WHITE);
935        for y in 3..7 {
936            for x in 3..7 {
937                fb.pixels_mut()[y * 10 + x] = Rgb565::BLACK;
938            }
939        }
940        fb.apply_iir_blur(128);
941        let center_pixel = fb.pixels()[5 * 10 + 5];
942        assert_ne!(center_pixel, Rgb565::BLACK);
943    }
944
945    #[test]
946    fn test_iir_blur_rgba8888() {
947        let mut fb = FramebufferRgba8888::<100>::new(10, 10);
948        fb.clear_color(Rgba8888::WHITE);
949        for y in 3..7 {
950            for x in 3..7 {
951                fb.pixels_mut()[y * 10 + x] = Rgba8888::BLACK;
952            }
953        }
954        fb.apply_iir_blur(128);
955        let center_pixel = fb.pixels()[5 * 10 + 5];
956        assert_ne!(center_pixel, Rgba8888::BLACK);
957        assert!(center_pixel.r > 0);
958    }
959
960    #[test]
961    fn test_iir_blur_gray8() {
962        let mut fb = FramebufferGray8::<100>::new(10, 10);
963        fb.clear_color(Gray8::new(255));
964        for y in 3..7 {
965            for x in 3..7 {
966                fb.pixels_mut()[y * 10 + x] = Gray8::new(0);
967            }
968        }
969        fb.apply_iir_blur(128);
970        let center_pixel = fb.pixels()[5 * 10 + 5].luma();
971        assert!(center_pixel > 0 && center_pixel < 255);
972    }
973}