embedded_graphics_framebuf/
lib.rs

1//! # Embedded Graphics FrameBuffer
2//!
3//! [embedded-graphics-framebuf](https://crates.io/crates/embedded-graphics-framebuf) is a
4//! generalized frame buffer implementation for use with Rust's
5//! [`embedded-graphics`](https://crates.io/crates/embedded-graphics) library.
6//!
7//! The framebuffer approach helps to deal with display flickering when you
8//! update multiple parts of the display in separate operations. Instead, with
9//! this approach, you're going to write to a in-memory display and push it all
10//! at once into your hardware display when the whole picture is drawn.
11//!
12//! This technique is useful when you're updating large portions of screen
13//! or just simply don't want to deal with partial display updates.
14//! The downside is a higher RAM consumption for to the framebuffer.
15//!
16//! The approach has been tested on TTGO (esp32) with ST7789
17//!
18//! ## Usage example
19//!
20//! ```rust
21//! use embedded_graphics::{
22//!     draw_target::DrawTarget,
23//!     mock_display::MockDisplay,
24//!     pixelcolor::BinaryColor,
25//!     prelude::{Point, Primitive},
26//!     primitives::{Line, PrimitiveStyle, Rectangle},
27//!     Drawable,
28//! };
29//! use embedded_graphics_framebuf::FrameBuf;
30//! let mut data = [BinaryColor::Off; 12 * 11];
31//! let mut fbuf = FrameBuf::new(&mut data, 12, 11);
32//!
33//! let mut display: MockDisplay<BinaryColor> = MockDisplay::new();
34//! Line::new(Point::new(2, 2), Point::new(10, 2))
35//!     .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 2))
36//!     .draw(&mut fbuf)
37//!     .unwrap();
38//! let area = Rectangle::new(Point::new(0, 0), fbuf.size());
39//! display.fill_contiguous(&area, data).unwrap();
40//! ```
41
42#![no_std]
43use embedded_dma::{ReadBuffer, WriteBuffer};
44use embedded_graphics::{
45    draw_target::DrawTarget,
46    geometry::OriginDimensions,
47    prelude::{PixelColor, Point, Size},
48    Pixel,
49};
50
51pub mod backends;
52use backends::{DMACapableFrameBufferBackend, FrameBufferBackend};
53
54/// Constructs a frame buffer in memory. Lets you define the width(`X`), height
55/// (`Y`) and pixel type your using in your display (RGB, Monochrome etc.)
56///
57/// # Example
58/// ```
59/// use embedded_graphics::{
60///     mono_font::{ascii::FONT_10X20, MonoTextStyle},
61///     pixelcolor::Rgb565,
62///     prelude::*,
63///     text::Text,
64/// };
65/// use embedded_graphics_framebuf::FrameBuf;
66///
67/// // Create a framebuffer for a 16-Bit 240x135px display
68/// let mut data = [Rgb565::BLACK; 240 * 135];
69/// let mut fbuff = FrameBuf::new(&mut data, 240, 135);
70///
71/// // write "Good luck" into the framebuffer.
72/// Text::new(
73///     &"Good luck!",
74///     Point::new(10, 13),
75///     MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE.into()),
76/// )
77/// .draw(&mut fbuff)
78/// .unwrap();
79/// ```
80// TODO: Once https://github.com/rust-lang/rust/issues/76560 is resolved, change this to `pub struct
81// FrameBuf<C: PixelColor, const X: usize, const Y: usize>(pub [C; X * Y]);`
82pub struct FrameBuf<C: PixelColor, B: FrameBufferBackend<Color = C>> {
83    pub data: B,
84    width: usize,
85    height: usize,
86    origin: Point,
87}
88
89impl<C: PixelColor, B: FrameBufferBackend<Color = C>> FrameBuf<C, B> {
90    /// Create a new [`FrameBuf`] on top of an existing memory slice.
91    ///
92    /// # Panic
93    /// Panics if the size of the memory does not match the given width and
94    /// height.
95    ///
96    /// # Example
97    /// ```rust
98    /// use embedded_graphics::{pixelcolor::Rgb565, prelude::RgbColor};
99    /// use embedded_graphics_framebuf::FrameBuf;
100    /// let mut data = [Rgb565::BLACK; 240 * 135];
101    /// let mut fbuff = FrameBuf::new(&mut data, 240, 135);
102    /// ```
103    pub fn new(data: B, width: usize, height: usize) -> Self {
104        Self::new_with_origin(data, width, height, Point::new(0, 0))
105    }
106
107    /// Create a new [`FrameBuf`] on top of an existing memory slice where pixels
108    /// from `.draw_iter()` are mapped relative to provided origin.
109    ///
110    /// # Panic
111    /// Panics if the size of the memory does not match the given width and
112    /// height.
113    ///
114    /// # Example
115    /// ```rust
116    /// use embedded_graphics::{pixelcolor::Rgb565, prelude::RgbColor, prelude::Point};
117    /// use embedded_graphics_framebuf::FrameBuf;
118    /// let mut data = [Rgb565::BLACK; 240 * 135];
119    /// let mut fbuff = FrameBuf::new_with_origin(&mut data, 240, 135, Point::new(100, 100));
120    /// ```
121    pub fn new_with_origin(data: B, width: usize, height: usize, origin: Point) -> Self {
122        assert_eq!(
123            data.nr_elements(),
124            width * height,
125            "FrameBuf underlying data size does not match width ({}) * height ({}) = {} but is {}",
126            width,
127            height,
128            width * height,
129            data.nr_elements(),
130        );
131        Self {
132            data,
133            width,
134            height,
135            origin,
136        }
137    }
138
139    /// Get the framebuffers width.
140    pub fn width(&self) -> usize {
141        self.width
142    }
143
144    /// Get the framebuffers height.
145    pub fn height(&self) -> usize {
146        self.height
147    }
148
149    /// Get the framebuffers size.
150    pub fn size(&self) -> Size {
151        Size::new(self.width as u32, self.height as u32)
152    }
153
154    pub fn origin(&self) -> Point {
155        self.origin
156    }
157
158    fn point_to_index(&self, p: Point) -> usize {
159        self.width * p.y as usize + p.x as usize
160    }
161
162    /// Set a pixel's color.
163    pub fn set_color_at(&mut self, p: Point, color: C) {
164        self.data.set(self.point_to_index(p), color)
165    }
166
167    /// Get a pixel's color.
168    pub fn get_color_at(&self, p: Point) -> C {
169        self.data.get(self.point_to_index(p))
170    }
171}
172impl<C: PixelColor + Default, B: FrameBufferBackend<Color = C>> FrameBuf<C, B> {
173    pub fn reset(&mut self) {
174        self.clear(C::default()).unwrap();
175    }
176}
177
178impl<'a, C: PixelColor, B: FrameBufferBackend<Color = C>> IntoIterator for &'a FrameBuf<C, B> {
179    type Item = Pixel<C>;
180    type IntoIter = PixelIterator<'a, C, B>;
181
182    /// Creates an iterator over all [Pixels](Pixel) in the frame buffer. Can be
183    /// used for rendering the framebuffer to the physical display.
184    ///
185    /// # Example
186    /// ```rust
187    /// use embedded_graphics::{
188    ///     draw_target::DrawTarget,
189    ///     mock_display::MockDisplay,
190    ///     pixelcolor::BinaryColor,
191    ///     prelude::{Point, Primitive},
192    ///     primitives::{Line, PrimitiveStyle},
193    ///     Drawable,
194    /// };
195    /// use embedded_graphics_framebuf::FrameBuf;
196    /// let mut data = [BinaryColor::Off; 12 * 11];
197    /// let mut fbuf = FrameBuf::new(&mut data, 12, 11);
198    /// let mut display: MockDisplay<BinaryColor> = MockDisplay::new();
199    /// Line::new(Point::new(2, 2), Point::new(10, 2))
200    ///     .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 2))
201    ///     .draw(&mut fbuf)
202    ///     .unwrap();
203    /// display.draw_iter(fbuf.into_iter()).unwrap();
204    /// ```
205    fn into_iter(self) -> Self::IntoIter {
206        PixelIterator {
207            fbuf: self,
208            index: 0,
209        }
210    }
211}
212
213impl<C: PixelColor, B: FrameBufferBackend<Color = C>> OriginDimensions for FrameBuf<C, B> {
214    fn size(&self) -> Size {
215        self.size()
216    }
217}
218
219impl<C: PixelColor, B: FrameBufferBackend<Color = C>> DrawTarget for FrameBuf<C, B> {
220    type Color = C;
221    type Error = core::convert::Infallible;
222
223    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
224    where
225        I: IntoIterator<Item = Pixel<Self::Color>>,
226    {
227        for Pixel(coord, color) in pixels.into_iter() {
228            if coord.x >= 0
229                && coord.x < self.width as i32
230                && coord.y >= 0
231                && coord.y < self.height as i32
232            {
233                self.set_color_at(coord, color);
234            }
235        }
236        Ok(())
237    }
238
239    fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> {
240        for y in 0..self.height {
241            for x in 0..self.width {
242                self.set_color_at(Point::new(x as i32, y as i32), color);
243            }
244        }
245        Ok(())
246    }
247}
248
249/// An iterator for all [Pixels](Pixel) in the framebuffer.
250pub struct PixelIterator<'a, C: PixelColor, B: FrameBufferBackend<Color = C>> {
251    fbuf: &'a FrameBuf<C, B>,
252    index: usize,
253}
254
255impl<'a, C: PixelColor, B: FrameBufferBackend<Color = C>> Iterator for PixelIterator<'a, C, B> {
256    type Item = Pixel<C>;
257    fn next(&mut self) -> Option<Pixel<C>> {
258        let y = self.index / self.fbuf.width;
259        let x = self.index - y * self.fbuf.width;
260
261        if self.index >= self.fbuf.width * self.fbuf.height {
262            return None;
263        }
264        self.index += 1;
265        let p = Point::new(x as i32, y as i32);
266        Some(Pixel(self.fbuf.origin + p, self.fbuf.get_color_at(p)))
267    }
268}
269
270unsafe impl<C: PixelColor, B: DMACapableFrameBufferBackend<Color = C>> ReadBuffer
271    for FrameBuf<C, B>
272{
273    type Word = u8;
274    unsafe fn read_buffer(&self) -> (*const Self::Word, usize) {
275        (
276            (self.data.data_ptr() as *const Self::Word),
277            self.height
278                * self.width
279                * (core::mem::size_of::<C>() / core::mem::size_of::<Self::Word>()),
280        )
281    }
282}
283
284unsafe impl<C: PixelColor, B: DMACapableFrameBufferBackend<Color = C>> WriteBuffer
285    for FrameBuf<C, B>
286{
287    type Word = u8;
288    unsafe fn write_buffer(&mut self) -> (*mut Self::Word, usize) {
289        (
290            (self.data.data_ptr() as *mut Self::Word),
291            self.height
292                * self.width
293                * (core::mem::size_of::<C>() / core::mem::size_of::<Self::Word>()),
294        )
295    }
296}
297
298#[cfg(test)]
299mod tests {
300    extern crate std;
301
302    use embedded_graphics::mock_display::MockDisplay;
303    use embedded_graphics::pixelcolor::BinaryColor;
304    use embedded_graphics::prelude::Point;
305    use embedded_graphics::prelude::Primitive;
306    use embedded_graphics::primitives::Line;
307    use embedded_graphics::primitives::PrimitiveStyle;
308    use embedded_graphics::Drawable;
309    use embedded_graphics::{pixelcolor::Rgb565, prelude::RgbColor};
310    use std::collections::HashMap;
311    use std::fmt::Debug;
312    use std::hash::Hash;
313
314    use super::*;
315
316    fn get_px_nums<C: PixelColor, B: FrameBufferBackend<Color = C>>(
317        fbuf: &FrameBuf<C, B>,
318    ) -> HashMap<C, i32>
319    where
320        C: Hash,
321        C: std::cmp::Eq,
322    {
323        let mut px_nums: HashMap<C, i32> = HashMap::new();
324        for px in fbuf.into_iter() {
325            //for px in col {
326            match px_nums.get_mut(&px.1) {
327                Some(v) => *v += 1,
328                None => {
329                    px_nums.insert(px.1, 1);
330                }
331            };
332            //}
333        }
334        px_nums
335    }
336
337    #[test]
338    fn clears_buffer() {
339        let mut data = [Rgb565::WHITE; 5 * 10];
340        let mut fbuf = FrameBuf::new(&mut data, 5, 10);
341        fbuf.reset();
342
343        let px_nums = get_px_nums(&fbuf);
344
345        assert_eq!(px_nums.get(&Rgb565::BLACK).unwrap(), &50);
346        assert_eq!(px_nums.get(&Rgb565::WHITE), None);
347    }
348
349    #[test]
350    fn clears_with_color() {
351        let mut data = [Rgb565::WHITE; 5 * 5];
352        let mut fbuf = FrameBuf::new(&mut data, 5, 5);
353        fbuf.clear(Rgb565::BLUE).unwrap();
354
355        let px_nums = get_px_nums(&fbuf);
356
357        assert_eq!(px_nums.get(&Rgb565::BLUE).unwrap(), &25);
358        assert_eq!(px_nums.get(&Rgb565::RED), None);
359    }
360
361    #[test]
362    fn draws_into_display() {
363        let mut data = [BinaryColor::Off; 12 * 11];
364        let mut fbuf = FrameBuf::new(&mut data, 12, 11);
365        let mut display: MockDisplay<BinaryColor> = MockDisplay::new();
366
367        // Horizontal line
368        Line::new(Point::new(2, 2), Point::new(10, 2))
369            .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 2))
370            .draw(&mut fbuf)
371            .unwrap();
372
373        // Vertical line
374        Line::new(Point::new(2, 5), Point::new(2, 10))
375            .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 3))
376            .draw(&mut fbuf)
377            .unwrap();
378
379        display.draw_iter(fbuf.into_iter()).unwrap();
380        display.assert_pattern(&[
381            "............",
382            "..#########.",
383            "..#########.",
384            "............",
385            "............",
386            ".###........",
387            ".###........",
388            ".###........",
389            ".###........",
390            ".###........",
391            ".###........",
392        ]);
393    }
394
395    fn draw_into_drawtarget<D>(mut dt: D)
396    where
397        D: DrawTarget<Color = BinaryColor>,
398        D::Error: Debug,
399    {
400        Line::new(Point::new(2, 2), Point::new(10, 2))
401            .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 2))
402            .draw(&mut dt)
403            .unwrap();
404    }
405
406    #[test]
407    fn usable_as_draw_target() {
408        let mut data = [BinaryColor::Off; 15 * 5];
409        let fbuf = FrameBuf::new(&mut data, 15, 5);
410        draw_into_drawtarget(fbuf)
411    }
412
413    #[test]
414    fn raw_data() {
415        let mut data = [Rgb565::new(1, 2, 3); 3 * 3];
416        let mut fbuf = FrameBuf::new(&mut data, 3, 3);
417        fbuf.set_color_at(Point { x: 1, y: 0 }, Rgb565::new(3, 2, 1));
418        let mut raw_iter = fbuf.data.iter();
419        assert_eq!(*raw_iter.next().unwrap(), Rgb565::new(1, 2, 3));
420        assert_eq!(*raw_iter.next().unwrap(), Rgb565::new(3, 2, 1));
421    }
422
423    #[test]
424    #[should_panic]
425    fn wrong_data_size() {
426        let mut data = [BinaryColor::Off; 5 * 5];
427        let _ = &mut FrameBuf::new(&mut data, 12, 3);
428    }
429}