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