endbasic_rpi/lcd/
mod.rs

1// EndBASIC
2// Copyright 2024 Julio Merino
3//
4// Licensed under the Apache License, Version 2.0 (the "License"); you may not
5// use this file except in compliance with the License.  You may obtain a copy
6// of the License at:
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
13// License for the specific language governing permissions and limitations
14// under the License.
15
16//! Generic types to represent and manipulate LCDs.
17
18use endbasic_std::console::{SizeInPixels, RGB};
19use std::convert::TryFrom;
20use std::io;
21
22mod buffered;
23mod font8;
24
25pub(crate) use buffered::BufferedLcd;
26
27pub(crate) trait AsByteSlice {
28    fn as_slice(&self) -> &[u8];
29}
30
31/// Data for one pixel encoded as RGB565.
32#[derive(Clone, Copy)]
33pub(crate) struct RGB565Pixel(pub(crate) [u8; 2]);
34
35impl AsByteSlice for RGB565Pixel {
36    fn as_slice(&self) -> &[u8] {
37        &self.0
38    }
39}
40
41/// Data for one pixel encoded as RGB888.
42#[cfg(test)]
43#[derive(Clone, Copy)]
44pub(crate) struct RGB888Pixel(pub(crate) [u8; 3]);
45
46#[cfg(test)]
47impl AsByteSlice for RGB888Pixel {
48    fn as_slice(&self) -> &[u8] {
49        &self.0
50    }
51}
52
53/// Primitives that an LCD must define.
54pub(crate) trait Lcd {
55    /// The primitive type of the pixel data.
56    type Pixel: AsByteSlice + Copy;
57
58    /// Returns the dimensions of the LCD and size of the `Pixel` (stride).
59    fn info(&self) -> (LcdSize, usize);
60
61    /// Encodes an `rgb` color into the `Pixel` expected by the LCD.
62    fn encode(&self, rgb: RGB) -> Self::Pixel;
63
64    /// Fills the area expressed by `x1y1` to `x2y2` by the pixel `data`.  The length of `data`
65    /// should be the size of the window in pixels multiplied by the `Pixel` size.
66    fn set_data(&mut self, x1y1: LcdXY, x2y2: LcdXY, data: &[u8]) -> io::Result<()>;
67}
68
69/// Represents valid coordinates within the LCD space.
70#[derive(Clone, Copy)]
71#[cfg_attr(test, derive(Debug, PartialEq))]
72pub(crate) struct LcdXY {
73    pub(crate) x: usize,
74    pub(crate) y: usize,
75}
76
77/// Represents a size that fits in the LCD space.
78#[derive(Clone, Copy)]
79#[cfg_attr(test, derive(Debug, PartialEq))]
80pub(crate) struct LcdSize {
81    pub(crate) width: usize,
82    pub(crate) height: usize,
83}
84
85impl LcdSize {
86    /// Calculates the size of the window represented by `x1y1` and `x2y2`.
87    fn between(x1y1: LcdXY, x2y2: LcdXY) -> Self {
88        debug_assert!(x2y2.x >= x1y1.x);
89        debug_assert!(x2y2.y >= x1y1.y);
90        Self { width: x2y2.x - x1y1.x + 1, height: x2y2.y - x1y1.y + 1 }
91    }
92
93    /// Creates a new buffer with enough capacity to hold the content of this LCD size for the given
94    /// `stride``.  The returned buffer is of zero size.
95    fn new_buffer(&self, stride: usize) -> Vec<u8> {
96        Vec::with_capacity(self.width * self.height * stride)
97    }
98}
99
100impl From<LcdSize> for SizeInPixels {
101    fn from(value: LcdSize) -> Self {
102        Self::new(
103            u16::try_from(value.width).expect("Must fit"),
104            u16::try_from(value.height).expect("Must fit"),
105        )
106    }
107}
108
109/// Converts a pair of coordinates to a top-left origin coordinate plus a size.
110pub(crate) fn to_xy_size(x1y1: LcdXY, x2y2: LcdXY) -> (LcdXY, LcdSize) {
111    let x1 = std::cmp::min(x1y1.x, x2y2.x);
112    let y1 = std::cmp::min(x1y1.y, x2y2.y);
113
114    let x2 = std::cmp::max(x1y1.x, x2y2.x);
115    let y2 = std::cmp::max(x1y1.y, x2y2.y);
116
117    (LcdXY { x: x1, y: y1 }, LcdSize { width: x2 + 1 - x1, height: y2 + 1 - y1 })
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    /// Syntactic sugar to instantiate a coordinate in the LCD space.
125    fn xy(x: usize, y: usize) -> LcdXY {
126        LcdXY { x, y }
127    }
128
129    /// Syntactic sugar to instantiate a size in the LCD space.
130    fn size(width: usize, height: usize) -> LcdSize {
131        LcdSize { width, height }
132    }
133
134    #[test]
135    fn test_lcdsize_between_one_pixel() {
136        assert_eq!(size(1, 1), LcdSize::between(xy(15, 16), xy(15, 16)));
137    }
138
139    #[test]
140    fn test_lcdsize_between_rect() {
141        assert_eq!(size(4, 5), LcdSize::between(xy(10, 25), xy(13, 29)));
142    }
143
144    #[test]
145    fn test_lcdsize_new_buffer() {
146        let buffer = size(10, 20).new_buffer(3);
147        assert_eq!(10 * 20 * 3, buffer.capacity());
148    }
149
150    #[test]
151    fn test_to_xy_size_one_pixel() {
152        assert_eq!(
153            (LcdXY { x: 10, y: 20 }, LcdSize { width: 1, height: 1 }),
154            to_xy_size(xy(10, 20), xy(10, 20))
155        );
156    }
157
158    #[test]
159    fn test_to_xy_size_rect() {
160        assert_eq!(
161            (LcdXY { x: 10, y: 20 }, LcdSize { width: 5, height: 7 }),
162            to_xy_size(xy(10, 20), xy(14, 26))
163        );
164    }
165}