Skip to main content

endbasic_std/gfx/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 crate::console::{RGB, SizeInPixels};
19use std::convert::TryFrom;
20use std::io;
21
22mod buffered;
23pub mod fonts;
24
25pub use buffered::BufferedLcd;
26
27/// Trait to convert a pixel to a sequence of bytes.
28pub trait AsByteSlice {
29    /// Returns the byte representation of a pixel.
30    fn as_slice(&self) -> &[u8];
31}
32
33/// Data for one pixel encoded as RGB565.
34#[derive(Clone, Copy)]
35pub struct RGB565Pixel(pub [u8; 2]);
36
37impl AsByteSlice for RGB565Pixel {
38    fn as_slice(&self) -> &[u8] {
39        &self.0
40    }
41}
42
43/// Primitives that an LCD must define.
44pub trait Lcd {
45    /// The primitive type of the pixel data.
46    type Pixel: AsByteSlice + Copy;
47
48    /// Returns the dimensions of the LCD and size of the `Pixel` (stride).
49    fn info(&self) -> (LcdSize, usize);
50
51    /// Encodes an `rgb` color into the `Pixel` expected by the LCD.
52    fn encode(&self, rgb: RGB) -> Self::Pixel;
53
54    /// Fills the area expressed by `x1y1` to `x2y2` by the pixel `data`.  The length of `data`
55    /// should be the size of the window in pixels multiplied by the `Pixel` size.
56    fn set_data(&mut self, x1y1: LcdXY, x2y2: LcdXY, data: &[u8]) -> io::Result<()>;
57}
58
59/// Represents valid coordinates within the LCD space.
60#[derive(Clone, Copy)]
61#[cfg_attr(test, derive(Debug, PartialEq))]
62pub struct LcdXY {
63    /// The X coordinate.
64    pub x: usize,
65
66    /// The Y coordinate.
67    pub y: usize,
68}
69
70/// Represents a size that fits in the LCD space.
71#[derive(Clone, Copy)]
72#[cfg_attr(test, derive(Debug, PartialEq))]
73pub struct LcdSize {
74    /// The width.
75    pub width: usize,
76
77    /// The height.
78    pub height: usize,
79}
80
81impl LcdSize {
82    /// Calculates the size of the window represented by `x1y1` and `x2y2`.
83    fn between(x1y1: LcdXY, x2y2: LcdXY) -> Self {
84        debug_assert!(x2y2.x >= x1y1.x);
85        debug_assert!(x2y2.y >= x1y1.y);
86        Self { width: x2y2.x - x1y1.x + 1, height: x2y2.y - x1y1.y + 1 }
87    }
88
89    /// Creates a new buffer with enough capacity to hold the content of this LCD size for the given
90    /// `stride``.  The returned buffer is of zero size.
91    fn new_buffer(&self, stride: usize) -> Vec<u8> {
92        Vec::with_capacity(self.width * self.height * stride)
93    }
94}
95
96impl From<LcdSize> for SizeInPixels {
97    fn from(value: LcdSize) -> Self {
98        Self::new(
99            u16::try_from(value.width).expect("Must fit"),
100            u16::try_from(value.height).expect("Must fit"),
101        )
102    }
103}
104
105/// Converts a pair of coordinates to a top-left origin coordinate plus a size.
106pub fn to_xy_size(x1y1: LcdXY, x2y2: LcdXY) -> (LcdXY, LcdSize) {
107    let x1 = std::cmp::min(x1y1.x, x2y2.x);
108    let y1 = std::cmp::min(x1y1.y, x2y2.y);
109
110    let x2 = std::cmp::max(x1y1.x, x2y2.x);
111    let y2 = std::cmp::max(x1y1.y, x2y2.y);
112
113    (LcdXY { x: x1, y: y1 }, LcdSize { width: x2 + 1 - x1, height: y2 + 1 - y1 })
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    /// Syntactic sugar to instantiate a coordinate in the LCD space.
121    fn xy(x: usize, y: usize) -> LcdXY {
122        LcdXY { x, y }
123    }
124
125    /// Syntactic sugar to instantiate a size in the LCD space.
126    fn size(width: usize, height: usize) -> LcdSize {
127        LcdSize { width, height }
128    }
129
130    #[test]
131    fn test_lcdsize_between_one_pixel() {
132        assert_eq!(size(1, 1), LcdSize::between(xy(15, 16), xy(15, 16)));
133    }
134
135    #[test]
136    fn test_lcdsize_between_rect() {
137        assert_eq!(size(4, 5), LcdSize::between(xy(10, 25), xy(13, 29)));
138    }
139
140    #[test]
141    fn test_lcdsize_new_buffer() {
142        let buffer = size(10, 20).new_buffer(3);
143        assert_eq!(10 * 20 * 3, buffer.capacity());
144    }
145
146    #[test]
147    fn test_to_xy_size_one_pixel() {
148        assert_eq!(
149            (LcdXY { x: 10, y: 20 }, LcdSize { width: 1, height: 1 }),
150            to_xy_size(xy(10, 20), xy(10, 20))
151        );
152    }
153
154    #[test]
155    fn test_to_xy_size_rect() {
156        assert_eq!(
157            (LcdXY { x: 10, y: 20 }, LcdSize { width: 5, height: 7 }),
158            to_xy_size(xy(10, 20), xy(14, 26))
159        );
160    }
161}