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::{Rgb565, RgbColor},
17};
18
19use crate::render::PixelRead;
20
21/// A fixed-capacity RGB565 framebuffer. `N` is the backing array length and
22/// must be at least `width * height`.
23#[derive(Clone, Debug, PartialEq, Eq)]
24pub struct Framebuffer<const N: usize> {
25    pixels: [Rgb565; N],
26    width: u32,
27    height: u32,
28}
29
30impl<const N: usize> Framebuffer<N> {
31    /// Create a `width × height` framebuffer cleared to black.
32    ///
33    /// Panics if `width * height > N`.
34    pub fn new(width: u32, height: u32) -> Self {
35        assert!(
36            (width as usize) * (height as usize) <= N,
37            "Framebuffer backing array too small for width * height",
38        );
39        Self {
40            pixels: [Rgb565::BLACK; N],
41            width,
42            height,
43        }
44    }
45
46    /// Fill the whole framebuffer with a single color.
47    pub fn clear_color(&mut self, color: Rgb565) {
48        let len = self.len();
49        for p in self.pixels[..len].iter_mut() {
50            *p = color;
51        }
52    }
53
54    /// The active pixels, row-major, `width * height` long. Handy for blitting
55    /// to a physical display.
56    pub fn pixels(&self) -> &[Rgb565] {
57        &self.pixels[..self.len()]
58    }
59
60    pub const fn width(&self) -> u32 {
61        self.width
62    }
63
64    pub const fn height(&self) -> u32 {
65        self.height
66    }
67
68    #[inline]
69    fn len(&self) -> usize {
70        (self.width as usize) * (self.height as usize)
71    }
72
73    #[inline]
74    fn index(&self, x: i32, y: i32) -> Option<usize> {
75        if x < 0 || y < 0 || x as u32 >= self.width || y as u32 >= self.height {
76            return None;
77        }
78        Some((y as usize) * (self.width as usize) + (x as usize))
79    }
80}
81
82impl<const N: usize> OriginDimensions for Framebuffer<N> {
83    fn size(&self) -> Size {
84        Size::new(self.width, self.height)
85    }
86}
87
88impl<const N: usize> DrawTarget for Framebuffer<N> {
89    type Color = Rgb565;
90    type Error = core::convert::Infallible;
91
92    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
93    where
94        I: IntoIterator<Item = Pixel<Self::Color>>,
95    {
96        for Pixel(point, color) in pixels {
97            if let Some(idx) = self.index(point.x, point.y) {
98                self.pixels[idx] = color;
99            }
100        }
101        Ok(())
102    }
103}
104
105impl<const N: usize> PixelRead for Framebuffer<N> {
106    fn get_pixel(&self, point: Point) -> Rgb565 {
107        match self.index(point.x, point.y) {
108            Some(idx) => self.pixels[idx],
109            None => Rgb565::BLACK,
110        }
111    }
112}