st7305-lcd 0.2.0

Platform-independent no_std driver for the Sitronix ST7305 reflective LCD controller (embedded-hal 1.0, blocking and async)
Documentation
//! 1 bpp frame buffer in the native GRAM layout of the panel.

mod pixel_map;
mod rotation;
mod window;

use core::{convert::Infallible, marker::PhantomData};

use embedded_graphics_core::{
    Pixel,
    draw_target::DrawTarget,
    geometry::{OriginDimensions, Point, Size},
    pixelcolor::BinaryColor,
    primitives::Rectangle,
};

pub use pixel_map::{Landscape, PixelMap};
pub use rotation::Rotation;
pub use window::Window;

/// Frame buffer in the native GRAM layout `M`, drawn in the logical orientation
/// `rotation`.
///
/// Pixel coordinates and rows are logical, the bytes ([`Self::as_bytes`]) are
/// always native and are sent to the panel as they are. Changing the rotation
/// does not move the existing pixels: redraw the frame after
/// [`Self::set_rotation`].
pub struct FrameBuffer<M: PixelMap, const N: usize> {
    buf: [u8; N],
    rotation: Rotation,
    map: PhantomData<M>,
}

impl<M: PixelMap, const N: usize> FrameBuffer<M, N> {
    /// Empty (all pixels off) buffer in `Rotation::Deg0`. `N` must equal
    /// `M::BUFFER_LEN` (checked at compile time).
    pub const fn new() -> Self {
        const { assert!(N == M::BUFFER_LEN) };
        Self {
            buf: [0x00; N],
            rotation: Rotation::Deg0,
            map: PhantomData,
        }
    }

    /// Same buffer with another orientation, see [`Self::set_rotation`].
    #[must_use]
    pub const fn with_rotation(mut self, rotation: Rotation) -> Self {
        self.rotation = rotation;
        self
    }

    /// Current orientation.
    pub const fn rotation(&self) -> Rotation {
        self.rotation
    }

    /// Changes the orientation of the coordinates. The buffer content is not
    /// touched, so the image turns into noise until it is redrawn.
    pub fn set_rotation(&mut self, rotation: Rotation) {
        self.rotation = rotation;
    }

    /// Logical `(width, height)` for the current rotation.
    pub const fn logical_size(&self) -> (u32, u32) {
        self.rotation.size(M::WIDTH, M::HEIGHT)
    }

    /// Native `(byte index, bit)` of a logical pixel, `None` outside the
    /// logical size.
    fn locate(&self, x: u32, y: u32) -> Option<(usize, u8)> {
        let (xn, yn) = self.rotation.to_native(M::WIDTH, M::HEIGHT, x, y)?;
        M::locate(xn, yn)
    }

    /// Sets the logical pixel `(x, y)`; coordinates outside the logical size
    /// are ignored.
    pub fn set_pixel(&mut self, x: u32, y: u32, on: bool) {
        let Some((i, bit)) = self.locate(x, y) else {
            return;
        };

        if on {
            self.buf[i] |= 1 << bit;
        } else {
            self.buf[i] &= !(1 << bit);
        }
    }

    /// Reads the logical pixel `(x, y)`, `None` outside the logical size.
    pub fn get_pixel(&self, x: u32, y: u32) -> Option<bool> {
        let (i, bit) = self.locate(x, y)?;
        Some((self.buf[i] >> bit) & 0b1 == 1)
    }

    /// Sets every pixel on or off. Same as `DrawTarget::clear` with
    /// `BinaryColor`.
    pub fn fill(&mut self, on: bool) {
        self.buf.fill(if on { 0xFF } else { 0x00 });
    }

    /// Native bytes, ready to be sent to the panel (`write_ram_data`).
    pub fn as_bytes(&self) -> &[u8] {
        &self.buf
    }

    /// The GRAM window that covers the logical rectangle `area` (clipped to the logical size),
    /// `None` if nothing of `area` is on the panel. Takes the rotation into account; see
    /// [`Window::covering`] for the native version.
    pub fn window(&self, area: Rectangle) -> Option<Window> {
        let (w, h) = self.logical_size();
        let area = area.intersection(&Rectangle::new(Point::zero(), Size::new(w, h)));
        let bottom_right = area.bottom_right()?; // inclusive

        // A rotation maps axis-aligned rectangles to axis-aligned ones, so two opposite
        // corners are enough.
        let to_native = |p: Point| {
            self.rotation
                .to_native(M::WIDTH, M::HEIGHT, p.x as u32, p.y as u32)
                .map(|(x, y)| Point::new(x as i32, y as i32))
        };
        let (a, b) = (to_native(area.top_left)?, to_native(bottom_right)?);
        Window::covering::<M>(Rectangle::with_corners(
            Point::new(a.x.min(b.x), a.y.min(b.y)),
            Point::new(a.x.max(b.x), a.y.max(b.y)),
        ))
    }

    /// The bytes of `window` taken from the frame into `out`, ready for `RAMWR` after
    /// `set_address_window`; returns `&out[..window.byte_len()]`. The window must come from
    /// [`Self::window`] or [`Window::covering`] for the same `M`.
    ///
    /// # Panics
    ///
    /// If `out` is shorter than `window.byte_len()`.
    pub fn copy_window<'a>(&self, window: &Window, out: &'a mut [u8]) -> &'a [u8] {
        window.copy_from(&self.buf, M::HEIGHT, out)
    }

    /// Mutable native bytes. The layout is that of `M`, independent of the
    /// rotation.
    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
        &mut self.buf
    }

    /// Fills the native bytes with values from `f`, in buffer order.
    pub fn fill_with(&mut self, mut f: impl FnMut() -> u8) {
        self.buf.iter_mut().for_each(|b| *b = f());
    }

    /// Reads a logical row `y` into `out` (`out.len()` == logical width).
    /// Returns `true` if at least one pixel in the row is on.
    ///
    /// A logical row is a native row (`Deg0`, `Deg180`) or a native column
    /// (`Deg90`, `Deg270`), possibly in the reverse order, so every
    /// orientation uses a whole-line path.
    pub fn read_row(&self, y: u32, out: &mut [bool]) -> bool {
        let (w, h) = self.logical_size();
        assert!(y < h && out.len() == w as usize);

        match self.rotation {
            Rotation::Deg0 => M::read_row(&self.buf, y, out),
            Rotation::Deg180 => {
                let any = M::read_row(&self.buf, M::HEIGHT - 1 - y, out);
                out.reverse();
                any
            }
            Rotation::Deg90 => M::read_col(&self.buf, M::WIDTH - 1 - y, out),
            Rotation::Deg270 => {
                let any = M::read_col(&self.buf, y, out);
                out.reverse();
                any
            }
        }
    }

    /// Writes a logical row `y` from `row` (`row.len()` == logical width). Only
    /// sets pixels, never clears them: the row must be cleared beforehand
    /// (see [`PixelMap::write_row`]).
    pub fn write_row(&mut self, y: u32, row: &[bool]) {
        let (w, h) = self.logical_size();
        assert!(y < h && row.len() == w as usize);

        match self.rotation {
            Rotation::Deg0 => M::write_row(&mut self.buf, y, row),
            Rotation::Deg180 => M::write_row_rev(&mut self.buf, M::HEIGHT - 1 - y, row),
            Rotation::Deg90 => M::write_col(&mut self.buf, M::WIDTH - 1 - y, row),
            Rotation::Deg270 => M::write_col_rev(&mut self.buf, y, row),
        }
    }
}

/// Prints the geometry and the rotation, not the bytes.
impl<M: PixelMap, const N: usize> core::fmt::Debug for FrameBuffer<M, N> {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("FrameBuffer")
            .field("width", &M::WIDTH)
            .field("height", &M::HEIGHT)
            .field("rotation", &self.rotation)
            .finish_non_exhaustive()
    }
}

impl<M: PixelMap, const N: usize> Default for FrameBuffer<M, N> {
    fn default() -> Self {
        Self::new()
    }
}

impl<M: PixelMap, const N: usize> OriginDimensions for FrameBuffer<M, N> {
    fn size(&self) -> Size {
        let (w, h) = self.logical_size();
        Size::new(w, h)
    }
}

impl<M: PixelMap, const N: usize> DrawTarget for FrameBuffer<M, N> {
    type Color = BinaryColor;

    type Error = Infallible;

    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
    where
        I: IntoIterator<Item = Pixel<Self::Color>>,
    {
        for Pixel(Point { x, y }, color) in pixels {
            if let (Ok(x), Ok(y)) = (u32::try_from(x), u32::try_from(y)) {
                self.set_pixel(x, y, color.is_on());
            }
        }
        Ok(())
    }

    /// Fills the whole buffer at once instead of pixel by pixel.
    fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> {
        self.fill(color.is_on());
        Ok(())
    }
}