Skip to main content

st7305_lcd/
framebuffer.rs

1//! 1 bpp frame buffer in the native GRAM layout of the panel.
2
3mod pixel_map;
4mod rotation;
5mod window;
6
7use core::{convert::Infallible, marker::PhantomData};
8
9use embedded_graphics_core::{
10    Pixel,
11    draw_target::DrawTarget,
12    geometry::{OriginDimensions, Point, Size},
13    pixelcolor::BinaryColor,
14    primitives::Rectangle,
15};
16
17pub use pixel_map::{Landscape, PixelMap};
18pub use rotation::Rotation;
19pub use window::Window;
20
21/// Frame buffer in the native GRAM layout `M`, drawn in the logical orientation
22/// `rotation`.
23///
24/// Pixel coordinates and rows are logical, the bytes ([`Self::as_bytes`]) are
25/// always native and are sent to the panel as they are. Changing the rotation
26/// does not move the existing pixels: redraw the frame after
27/// [`Self::set_rotation`].
28pub struct FrameBuffer<M: PixelMap, const N: usize> {
29    buf: [u8; N],
30    rotation: Rotation,
31    map: PhantomData<M>,
32}
33
34impl<M: PixelMap, const N: usize> FrameBuffer<M, N> {
35    /// Empty (all pixels off) buffer in `Rotation::Deg0`. `N` must equal
36    /// `M::BUFFER_LEN` (checked at compile time).
37    pub const fn new() -> Self {
38        const { assert!(N == M::BUFFER_LEN) };
39        Self {
40            buf: [0x00; N],
41            rotation: Rotation::Deg0,
42            map: PhantomData,
43        }
44    }
45
46    /// Same buffer with another orientation, see [`Self::set_rotation`].
47    #[must_use]
48    pub const fn with_rotation(mut self, rotation: Rotation) -> Self {
49        self.rotation = rotation;
50        self
51    }
52
53    /// Current orientation.
54    pub const fn rotation(&self) -> Rotation {
55        self.rotation
56    }
57
58    /// Changes the orientation of the coordinates. The buffer content is not
59    /// touched, so the image turns into noise until it is redrawn.
60    pub fn set_rotation(&mut self, rotation: Rotation) {
61        self.rotation = rotation;
62    }
63
64    /// Logical `(width, height)` for the current rotation.
65    pub const fn logical_size(&self) -> (u32, u32) {
66        self.rotation.size(M::WIDTH, M::HEIGHT)
67    }
68
69    /// Native `(byte index, bit)` of a logical pixel, `None` outside the
70    /// logical size.
71    fn locate(&self, x: u32, y: u32) -> Option<(usize, u8)> {
72        let (xn, yn) = self.rotation.to_native(M::WIDTH, M::HEIGHT, x, y)?;
73        M::locate(xn, yn)
74    }
75
76    /// Sets the logical pixel `(x, y)`; coordinates outside the logical size
77    /// are ignored.
78    pub fn set_pixel(&mut self, x: u32, y: u32, on: bool) {
79        let Some((i, bit)) = self.locate(x, y) else {
80            return;
81        };
82
83        if on {
84            self.buf[i] |= 1 << bit;
85        } else {
86            self.buf[i] &= !(1 << bit);
87        }
88    }
89
90    /// Reads the logical pixel `(x, y)`, `None` outside the logical size.
91    pub fn get_pixel(&self, x: u32, y: u32) -> Option<bool> {
92        let (i, bit) = self.locate(x, y)?;
93        Some((self.buf[i] >> bit) & 0b1 == 1)
94    }
95
96    /// Sets every pixel on or off. Same as `DrawTarget::clear` with
97    /// `BinaryColor`.
98    pub fn fill(&mut self, on: bool) {
99        self.buf.fill(if on { 0xFF } else { 0x00 });
100    }
101
102    /// Native bytes, ready to be sent to the panel (`write_ram_data`).
103    pub fn as_bytes(&self) -> &[u8] {
104        &self.buf
105    }
106
107    /// The GRAM window that covers the logical rectangle `area` (clipped to the logical size),
108    /// `None` if nothing of `area` is on the panel. Takes the rotation into account; see
109    /// [`Window::covering`] for the native version.
110    pub fn window(&self, area: Rectangle) -> Option<Window> {
111        let (w, h) = self.logical_size();
112        let area = area.intersection(&Rectangle::new(Point::zero(), Size::new(w, h)));
113        let bottom_right = area.bottom_right()?; // inclusive
114
115        // A rotation maps axis-aligned rectangles to axis-aligned ones, so two opposite
116        // corners are enough.
117        let to_native = |p: Point| {
118            self.rotation
119                .to_native(M::WIDTH, M::HEIGHT, p.x as u32, p.y as u32)
120                .map(|(x, y)| Point::new(x as i32, y as i32))
121        };
122        let (a, b) = (to_native(area.top_left)?, to_native(bottom_right)?);
123        Window::covering::<M>(Rectangle::with_corners(
124            Point::new(a.x.min(b.x), a.y.min(b.y)),
125            Point::new(a.x.max(b.x), a.y.max(b.y)),
126        ))
127    }
128
129    /// The bytes of `window` taken from the frame into `out`, ready for `RAMWR` after
130    /// `set_address_window`; returns `&out[..window.byte_len()]`. The window must come from
131    /// [`Self::window`] or [`Window::covering`] for the same `M`.
132    ///
133    /// # Panics
134    ///
135    /// If `out` is shorter than `window.byte_len()`.
136    pub fn copy_window<'a>(&self, window: &Window, out: &'a mut [u8]) -> &'a [u8] {
137        window.copy_from(&self.buf, M::HEIGHT, out)
138    }
139
140    /// Mutable native bytes. The layout is that of `M`, independent of the
141    /// rotation.
142    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
143        &mut self.buf
144    }
145
146    /// Fills the native bytes with values from `f`, in buffer order.
147    pub fn fill_with(&mut self, mut f: impl FnMut() -> u8) {
148        self.buf.iter_mut().for_each(|b| *b = f());
149    }
150
151    /// Reads a logical row `y` into `out` (`out.len()` == logical width).
152    /// Returns `true` if at least one pixel in the row is on.
153    ///
154    /// A logical row is a native row (`Deg0`, `Deg180`) or a native column
155    /// (`Deg90`, `Deg270`), possibly in the reverse order, so every
156    /// orientation uses a whole-line path.
157    pub fn read_row(&self, y: u32, out: &mut [bool]) -> bool {
158        let (w, h) = self.logical_size();
159        assert!(y < h && out.len() == w as usize);
160
161        match self.rotation {
162            Rotation::Deg0 => M::read_row(&self.buf, y, out),
163            Rotation::Deg180 => {
164                let any = M::read_row(&self.buf, M::HEIGHT - 1 - y, out);
165                out.reverse();
166                any
167            }
168            Rotation::Deg90 => M::read_col(&self.buf, M::WIDTH - 1 - y, out),
169            Rotation::Deg270 => {
170                let any = M::read_col(&self.buf, y, out);
171                out.reverse();
172                any
173            }
174        }
175    }
176
177    /// Writes a logical row `y` from `row` (`row.len()` == logical width). Only
178    /// sets pixels, never clears them: the row must be cleared beforehand
179    /// (see [`PixelMap::write_row`]).
180    pub fn write_row(&mut self, y: u32, row: &[bool]) {
181        let (w, h) = self.logical_size();
182        assert!(y < h && row.len() == w as usize);
183
184        match self.rotation {
185            Rotation::Deg0 => M::write_row(&mut self.buf, y, row),
186            Rotation::Deg180 => M::write_row_rev(&mut self.buf, M::HEIGHT - 1 - y, row),
187            Rotation::Deg90 => M::write_col(&mut self.buf, M::WIDTH - 1 - y, row),
188            Rotation::Deg270 => M::write_col_rev(&mut self.buf, y, row),
189        }
190    }
191}
192
193/// Prints the geometry and the rotation, not the bytes.
194impl<M: PixelMap, const N: usize> core::fmt::Debug for FrameBuffer<M, N> {
195    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
196        f.debug_struct("FrameBuffer")
197            .field("width", &M::WIDTH)
198            .field("height", &M::HEIGHT)
199            .field("rotation", &self.rotation)
200            .finish_non_exhaustive()
201    }
202}
203
204impl<M: PixelMap, const N: usize> Default for FrameBuffer<M, N> {
205    fn default() -> Self {
206        Self::new()
207    }
208}
209
210impl<M: PixelMap, const N: usize> OriginDimensions for FrameBuffer<M, N> {
211    fn size(&self) -> Size {
212        let (w, h) = self.logical_size();
213        Size::new(w, h)
214    }
215}
216
217impl<M: PixelMap, const N: usize> DrawTarget for FrameBuffer<M, N> {
218    type Color = BinaryColor;
219
220    type Error = Infallible;
221
222    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
223    where
224        I: IntoIterator<Item = Pixel<Self::Color>>,
225    {
226        for Pixel(Point { x, y }, color) in pixels {
227            if let (Ok(x), Ok(y)) = (u32::try_from(x), u32::try_from(y)) {
228                self.set_pixel(x, y, color.is_on());
229            }
230        }
231        Ok(())
232    }
233
234    /// Fills the whole buffer at once instead of pixel by pixel.
235    fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> {
236        self.fill(color.is_on());
237        Ok(())
238    }
239}