Skip to content
Snippets Groups Projects
buffered_display.rs 3.46 KiB
Newer Older
// Uses too much RAM to be useful ):
// 320 * 240 * 2 * 2 bytes/pixel = 307KB of RAM

use core::convert::Infallible;

use embedded_graphics::{
    draw_target::DrawTarget,
    geometry::{Dimensions, Point},
    pixelcolor::PixelColor,
    Pixel,
};

pub struct BufferedDisplay<const L: usize, C: 'static, E, DT: DrawTarget<Color = C, Error = E>> {
    target: DT,
    width: usize,
    height: usize,
    last: &'static mut [C; L],
    cur: &'static mut [C; L],
}

impl<const L: usize, C: PixelColor, E, DT: DrawTarget<Color = C, Error = E>>
    BufferedDisplay<L, C, E, DT>
{
    pub fn new(
        mut target: DT,
        color: C,
        last: &'static mut [C; L],
        cur: &'static mut [C; L],
    ) -> Result<Self, E> {
        let size = target.bounding_box().size;
        let width = size.width.try_into().unwrap();
        let height = size.height.try_into().unwrap();
        assert_eq!(L, width * height);
        target.fill_solid(&target.bounding_box(), color)?;

        Ok(Self {
            target,
            width,
            height,
            last,
            cur,
        })
    }

    pub fn flush(&mut self) -> Result<(), E> {
        let iter = BufferedDisplayIter::new(self.width, self.height, self.last, self.cur);
        self.target.draw_iter(iter)?;

        Ok(())
    }

    #[inline(always)]
    fn index<X: TryInto<usize>, Y: TryInto<usize>>(&self, x: X, y: Y) -> Option<usize> {
        let x: usize = x.try_into().ok()?;
        let y: usize = y.try_into().ok()?;

        if y >= self.height || x >= self.width {
            return None;
        }

        Some(x + y * self.width)
    }
}

impl<const L: usize, C: PixelColor, E, DT: DrawTarget<Color = C, Error = E>> Dimensions
    for BufferedDisplay<L, C, E, DT>
{
    fn bounding_box(&self) -> embedded_graphics::primitives::Rectangle {
        self.target.bounding_box()
    }
}

impl<const L: usize, C: PixelColor, E, DT: DrawTarget<Color = C, Error = E>> DrawTarget
    for BufferedDisplay<L, C, E, DT>
{
    type Color = C;
    type Error = Infallible;

    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
    where
        I: IntoIterator<Item = embedded_graphics::prelude::Pixel<Self::Color>>,
    {
        for Pixel(p, c) in pixels {
            if let Some(idx) = self.index(p.x, p.y) {
                self.cur[idx] = c;
            }
        }

        Ok(())
    }
}

struct BufferedDisplayIter<'a, C: PixelColor> {
    width: usize,
    height: usize,
    last: &'a mut [C],
    cur: &'a mut [C],
    i: usize,
}

impl<'a, C: PixelColor> BufferedDisplayIter<'a, C> {
    fn new(width: usize, height: usize, last: &'a mut [C], cur: &'a mut [C]) -> Self {
        Self {
            width,
            height,
            last,
            cur,
            i: 0,
        }
    }

    #[inline(always)]
    fn get_xy(&self, idx: usize) -> (i32, i32) {
        let y = (idx / self.width).try_into().unwrap();
        let x = (idx % self.width).try_into().unwrap();

        (x, y)
    }
}

impl<'a, C: PixelColor> Iterator for BufferedDisplayIter<'a, C> {
    type Item = Pixel<C>;

    fn next(&mut self) -> Option<Self::Item> {
        loop {
            if self.i >= self.cur.len() {
                return None;
            }

            let c = self.cur[self.i];
            if self.last[self.i] != c {
                self.last[self.i] = c;

                let (x, y) = self.get_xy(self.i);

                return Some(Pixel(Point::new(x, y), c));
            }

            self.i += 1;
        }
    }
}