lcd_async/
raw_framebuf.rs

1//! A framebuffer that stores pixels as raw bytes, suitable for direct display transmission.
2//!
3//! This module provides [`RawFrameBuf`], a `DrawTarget` implementation that allows
4//! `embedded-graphics` primitives to be rendered directly into an in-memory byte buffer.
5//! This is a common pattern for "off-screen rendering," where a complete frame is prepared
6//! in a buffer before being sent to the display in a single, efficient operation (e.g., via DMA).
7//!
8//! The framebuffer is generic over a color type `C` and a buffer backend `BUF`. The key
9//! component is the [`IntoRawBytes`] trait, which defines how a given `PixelColor` is
10//! converted into its byte representation. This allows the framebuffer to automatically
11//! handle different color formats (like `Rgb565`, `Rgb888`, etc.) without needing
12//! the user to specify the bytes-per-pixel manually.
13//!
14//! # Usage with an Async Display Driver
15//!
16//! This framebuffer is ideal for use with asynchronous display drivers, like an async fork of `mipidsi`.
17//! The typical workflow is:
18//!
19//! 1.  Allocate a buffer large enough for one full frame (often on the heap using `alloc`).
20//! 2.  Create a `RawFrameBuf` inside a new scope, wrapping a mutable slice of the buffer.
21//! 3.  Use `embedded-graphics` commands to draw a complete scene to the `RawFrameBuf`.
22//! 4.  Once the scope ends, `RawFrameBuf` is dropped, releasing its borrow on the buffer.
23//! 5.  The now-populated buffer is passed to an async method on the display driver to be rendered.
24//!
25//! # Example
26//!
27//! ```
28//! use embedded_graphics::pixelcolor::Rgb565;
29//! use embedded_graphics::prelude::*;
30//! use embedded_graphics::primitives::{Circle, PrimitiveStyle};
31//! use lcd_async::raw_framebuf::{RawFrameBuf, IntoRawBytes};
32//!
33//! const WIDTH: usize = 64;
34//! const HEIGHT: usize = 64;
35//! const FRAME_SIZE: usize = WIDTH * HEIGHT * 2; // Rgb565 = 2 bytes per pixel
36//!
37//! // Create a static buffer
38//! let mut frame_buffer = [0u8; FRAME_SIZE];
39//!
40//! // Create the framebuffer
41//! let mut fbuf = RawFrameBuf::<Rgb565, _>::new(&mut frame_buffer[..], WIDTH, HEIGHT);
42//!
43//! // Draw a scene to the in-memory framebuffer
44//! fbuf.clear(Rgb565::BLACK).unwrap();
45//! Circle::new(Point::new(32, 32), 20)
46//!     .into_styled(PrimitiveStyle::with_fill(Rgb565::RED))
47//!     .draw(&mut fbuf)
48//!     .unwrap();
49//!
50//! // The frame_buffer now contains the rendered frame data
51//! assert_eq!(fbuf.width(), WIDTH);
52//! assert_eq!(fbuf.height(), HEIGHT);
53//! ```
54
55use embedded_graphics::{
56    draw_target::DrawTarget,
57    geometry::{Dimensions, OriginDimensions},
58    pixelcolor::{raw::RawU16, PixelColor, RgbColor},
59    prelude::*,
60    primitives::Rectangle,
61    Pixel,
62};
63
64/// A trait for converting a `PixelColor` into its raw byte representation.
65///
66/// This trait is the bridge between `embedded-graphics` color types and a raw byte
67/// buffer. By implementing this trait for a color, you define how it should be serialized
68/// into bytes for the display.
69pub trait IntoRawBytes: PixelColor + Sized {
70    /// The number of bytes used to represent one pixel of this color.
71    const BYTES_PER_PIXEL: usize;
72
73    /// The fixed-size array type that holds the raw byte data for a single pixel.
74    type Raw: AsRef<[u8]> + AsMut<[u8]> + Copy + Default;
75
76    /// Converts the color instance into its raw byte representation.
77    fn into_raw_bytes(self) -> <Self as IntoRawBytes>::Raw;
78}
79
80impl IntoRawBytes for embedded_graphics::pixelcolor::Rgb565 {
81    const BYTES_PER_PIXEL: usize = 2;
82    type Raw = [u8; 2];
83
84    fn into_raw_bytes(self) -> <Self as IntoRawBytes>::Raw {
85        RawU16::from(self).into_inner().to_be_bytes()
86    }
87}
88
89impl IntoRawBytes for embedded_graphics::pixelcolor::Rgb666 {
90    const BYTES_PER_PIXEL: usize = 3;
91    type Raw = [u8; 3];
92
93    //scale up by 8bits/ 6bits = 256/64 = 4
94    fn into_raw_bytes(self) -> <Self as IntoRawBytes>::Raw {
95        [self.r() * 4, self.g() * 4, self.b() * 4]
96    }
97}
98
99impl IntoRawBytes for embedded_graphics::pixelcolor::Rgb888 {
100    const BYTES_PER_PIXEL: usize = 3;
101    type Raw = [u8; 3];
102
103    fn into_raw_bytes(self) -> <Self as IntoRawBytes>::Raw {
104        [self.r(), self.g(), self.b()]
105    }
106}
107
108/// A trait for abstracting over a mutable byte buffer.
109///
110/// This allows [`RawFrameBuf`] to be agnostic to the underlying buffer's storage,
111/// accepting anything that can provide a mutable byte slice, such as a `&mut [u8]`,
112/// a `Vec<u8>`, or a custom memory-mapped region.
113pub trait RawBufferBackendMut {
114    /// Returns a mutable slice to the entire buffer.
115    fn as_mut_u8_slice(&mut self) -> &mut [u8];
116
117    /// Returns an immutable slice to the entire buffer.
118    fn as_u8_slice(&self) -> &[u8];
119
120    /// Returns the total length of the buffer in bytes.
121    fn u8_len(&self) -> usize;
122}
123
124impl RawBufferBackendMut for &mut [u8] {
125    fn as_mut_u8_slice(&mut self) -> &mut [u8] {
126        self
127    }
128
129    fn as_u8_slice(&self) -> &[u8] {
130        self
131    }
132
133    fn u8_len(&self) -> usize {
134        self.len()
135    }
136}
137
138/// A framebuffer that writes pixel data directly into a raw byte buffer.
139///
140/// This struct implements [`DrawTarget`] and is generic over a color format `C`
141/// (which must implement [`IntoRawBytes`]) and a buffer backend `BUF` (which must
142/// implement [`RawBufferBackendMut`]). See the module-level documentation for a usage example.
143pub struct RawFrameBuf<C, BUF>
144where
145    C: IntoRawBytes,
146    BUF: RawBufferBackendMut,
147{
148    buffer: BUF,
149    width: usize,
150    height: usize,
151    _phantom_color: core::marker::PhantomData<C>,
152}
153
154impl<C, BUF> RawFrameBuf<C, BUF>
155where
156    C: IntoRawBytes,
157    BUF: RawBufferBackendMut,
158{
159    /// Creates a new raw framebuffer.
160    ///
161    /// # Panics
162    ///
163    /// Panics if the provided `buffer` is smaller than `width * height * C::BYTES_PER_PIXEL`.
164    pub fn new(buffer: BUF, width: usize, height: usize) -> Self {
165        let expected_len = width * height * C::BYTES_PER_PIXEL;
166        assert!(
167            buffer.u8_len() >= expected_len,
168            "RawFrameBuf underlying buffer is too small. Expected at least {}, got {}.",
169            expected_len,
170            buffer.u8_len()
171        );
172        Self {
173            buffer,
174            width,
175            height,
176            _phantom_color: core::marker::PhantomData,
177        }
178    }
179
180    /// Returns the width of the framebuffer in pixels.
181    pub fn width(&self) -> usize {
182        self.width
183    }
184
185    /// Returns the height of the framebuffer in pixels.
186    pub fn height(&self) -> usize {
187        self.height
188    }
189
190    /// Returns the raw framebuffer data as an immutable byte slice.
191    pub fn as_bytes(&self) -> &[u8] {
192        let expected_len = self.width * self.height * C::BYTES_PER_PIXEL;
193        &self.buffer.as_u8_slice()[0..expected_len]
194    }
195
196    /// Returns the raw framebuffer data as a mutable byte slice.
197    pub fn as_mut_bytes(&mut self) -> &mut [u8] {
198        let expected_len = self.width * self.height * C::BYTES_PER_PIXEL;
199        &mut self.buffer.as_mut_u8_slice()[0..expected_len]
200    }
201}
202
203impl<C, BUF> OriginDimensions for RawFrameBuf<C, BUF>
204where
205    C: IntoRawBytes,
206    BUF: RawBufferBackendMut,
207{
208    fn size(&self) -> Size {
209        Size::new(self.width as u32, self.height as u32)
210    }
211}
212
213impl<C, BUF> DrawTarget for RawFrameBuf<C, BUF>
214where
215    C: IntoRawBytes,
216    BUF: RawBufferBackendMut,
217{
218    type Color = C;
219    type Error = core::convert::Infallible;
220
221    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
222    where
223        I: IntoIterator<Item = Pixel<Self::Color>>,
224    {
225        let bounding_box = self.bounding_box();
226        let current_width = self.width;
227
228        let buffer_slice = self.buffer.as_mut_u8_slice();
229        let active_buffer_len = self.width * self.height * C::BYTES_PER_PIXEL;
230
231        for Pixel(coord, color) in pixels.into_iter() {
232            if bounding_box.contains(coord) {
233                let byte_index =
234                    (coord.y as usize * current_width + coord.x as usize) * C::BYTES_PER_PIXEL;
235
236                let color_bytes = color.into_raw_bytes();
237
238                if byte_index + C::BYTES_PER_PIXEL <= active_buffer_len {
239                    buffer_slice[byte_index..byte_index + C::BYTES_PER_PIXEL]
240                        .copy_from_slice(color_bytes.as_ref());
241                }
242            }
243        }
244        Ok(())
245    }
246
247    fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> {
248        let color_bytes_array = color.into_raw_bytes();
249        let color_bytes = color_bytes_array.as_ref();
250
251        let buffer_slice = self.buffer.as_mut_u8_slice();
252        let active_buffer_len = self.width * self.height * C::BYTES_PER_PIXEL;
253        let active_slice = &mut buffer_slice[0..active_buffer_len];
254
255        let all_bytes_same = if let Some(first) = color_bytes.first() {
256            color_bytes.iter().all(|&b| b == *first)
257        } else {
258            true
259        };
260
261        if all_bytes_same && !color_bytes.is_empty() {
262            active_slice.fill(color_bytes[0]);
263        } else if C::BYTES_PER_PIXEL > 0 {
264            for chunk in active_slice.chunks_exact_mut(C::BYTES_PER_PIXEL) {
265                chunk.copy_from_slice(color_bytes);
266            }
267        }
268        Ok(())
269    }
270
271    fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> {
272        let drawable_area = area.intersection(&self.bounding_box());
273        if drawable_area.is_zero_sized() {
274            return Ok(());
275        }
276
277        let color_bytes_array = color.into_raw_bytes();
278        let color_bytes = color_bytes_array.as_ref();
279
280        let current_width = self.width;
281        let buffer_slice = self.buffer.as_mut_u8_slice();
282
283        for p in drawable_area.points() {
284            let byte_index = (p.y as usize * current_width + p.x as usize) * C::BYTES_PER_PIXEL;
285
286            if byte_index + C::BYTES_PER_PIXEL <= buffer_slice.len() {
287                buffer_slice[byte_index..byte_index + C::BYTES_PER_PIXEL]
288                    .copy_from_slice(color_bytes);
289            }
290        }
291        Ok(())
292    }
293}