1#![no_std]
9
10use embedded_graphics_core::{
11 draw_target::DrawTarget,
12 geometry::{OriginDimensions, Size},
13 pixelcolor::{Rgb888, RgbColor},
14 Pixel,
15};
16
17use smart_leds::{brightness, gamma, SmartLedsWrite, RGB8};
18
19pub mod layout;
20use layout::Layout;
21
22pub struct SmartLedMatrix<T, L, const N: usize> {
28 writer: T,
29 layout: L,
30 content: [RGB8; N],
31 brightness: u8,
32}
33
34impl<T, L, const N: usize> SmartLedMatrix<T, L, N> {
35 pub fn set_brightness(&mut self, new_brightness: u8) {
36 self.brightness = new_brightness;
37 }
38
39 pub fn brightness(&self) -> u8 {
40 self.brightness
41 }
42}
43
44impl<T: SmartLedsWrite, L: Layout, const N: usize> SmartLedMatrix<T, L, N>
45where
46 <T as SmartLedsWrite>::Color: From<RGB8>,
47{
48 pub fn new(writer: T, layout: L) -> Self {
49 Self {
50 writer,
51 layout,
52 content: [RGB8::default(); N],
53 brightness: 255,
54 }
55 }
56
57 pub fn flush(&mut self) -> Result<(), T::Error> {
58 let iter = brightness(self.content.as_slice().iter().cloned(), self.brightness);
59 self.writer.write(iter)
60 }
61 pub fn flush_with_gamma(&mut self) -> Result<(), T::Error> {
62 let iter = brightness(
63 gamma(self.content.as_slice().iter().cloned()),
64 self.brightness,
65 );
66 self.writer.write(iter)
67 }
68}
69
70impl<T: SmartLedsWrite, L: Layout, const N: usize> DrawTarget for SmartLedMatrix<T, L, N>
71where
72 <T as SmartLedsWrite>::Color: From<RGB8>,
73{
74 type Color = Rgb888;
75 type Error = core::convert::Infallible;
76
77 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
78 where
79 I: IntoIterator<Item = Pixel<Rgb888>>,
80 {
81 for Pixel(pos, color) in pixels {
82 if let Some(t) = self
83 .layout
84 .map(pos)
85 .and_then(|index| self.content.get_mut(index))
86 {
87 *t = RGB8::new(color.r(), color.g(), color.b());
88 }
89 }
90
91 Ok(())
92 }
93}
94
95impl<T, L: Layout, const N: usize> OriginDimensions for SmartLedMatrix<T, L, N> {
96 fn size(&self) -> Size {
97 self.layout.size()
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 use crate::layout::Rectangular;
106 use embedded_graphics_core::{geometry::Point, prelude::Dimensions, primitives::PointsIter};
107
108 struct MockWriter<'a, const N: usize> {
109 content: &'a mut [RGB8; N],
110 }
111
112 impl<'a, const N: usize> SmartLedsWrite for MockWriter<'a, N> {
113 type Error = ();
114 type Color = RGB8;
115
116 fn write<T, I>(&mut self, iterator: T) -> Result<(), Self::Error>
117 where
118 T: IntoIterator<Item = I>,
119 I: Into<Self::Color>,
120 {
121 let mut i = 0;
122 for color in iterator {
123 self.content[i] = color.into();
124 i += 1;
125 }
126 Ok(())
127 }
128 }
129
130 fn get64pixels(color: Rgb888) -> [Pixel<Rgb888>; 64] {
131 let mut pixels: [Pixel<Rgb888>; 64] = [Pixel(Point::new(0, 0), Rgb888::BLACK); 64];
132 for x in 0..8 {
133 for y in 0..8 {
134 pixels[x * 8 + y] = Pixel(Point::new(x as i32, y as i32), color);
135 }
136 }
137 pixels
138 }
139
140 #[test]
141 fn test_y_inversion() {
142 let content = &mut [RGB8::new(0, 0, 0); 64];
143 let writer = MockWriter { content };
144 let mut matrix =
145 SmartLedMatrix::<_, _, { 8 * 8 }>::new(writer, Rectangular::new_invert_y(8, 8));
146 let mut pixels = get64pixels(Rgb888::BLACK);
147
148 pixels[0] = Pixel(Point::new(0, 0), Rgb888::WHITE);
149
150 matrix.draw_iter(pixels).unwrap();
151 matrix.flush().unwrap();
152
153 for i in 0..64 {
154 if i == 56 {
155 assert_eq!(
156 content[i],
157 RGB8::new(255, 255, 255),
158 r#"expected a white pixel after inversion"#
159 );
160 continue;
161 }
162 assert_eq!(content[i], RGB8::new(0, 0, 0), r#"expected black pixel"#);
163 }
164 }
165
166 #[test]
167 fn test_identity() {
168 let content = &mut [RGB8::new(0, 0, 0); 64];
169 let writer = MockWriter { content };
170 let mut matrix = SmartLedMatrix::<_, _, { 8 * 8 }>::new(writer, Rectangular::new(8, 8));
171 let mut pixels = get64pixels(Rgb888::BLACK);
172
173 pixels[0] = Pixel(Point::new(0, 0), Rgb888::WHITE);
174
175 matrix.draw_iter(pixels).unwrap();
176 matrix.flush().unwrap();
177
178 for i in 0..64 {
179 if i == 0 {
180 assert_eq!(
181 content[i],
182 RGB8::new(255, 255, 255),
183 r#"expected a white pixel on it's original place"#
184 );
185 continue;
186 }
187 assert_eq!(content[i], RGB8::new(0, 0, 0), r#"expected black pixel"#);
188 }
189 }
190
191 #[test]
192 fn test_brightness() {
193 let content = &mut [RGB8::new(0, 0, 0); 64];
194 let writer = MockWriter { content };
195 let mut matrix = SmartLedMatrix::<_, _, { 8 * 8 }>::new(writer, Rectangular::new(8, 8));
196 let pixels = get64pixels(Rgb888::WHITE);
197
198 assert_eq!(
199 matrix.brightness(),
200 255,
201 r#"initial brightness shall be set to max (255)"#
202 );
203 matrix.set_brightness(10);
204 assert_eq!(matrix.brightness(), 10, r#"brightness shall be set to 10"#);
205
206 matrix.draw_iter(pixels).unwrap();
207 matrix.flush().unwrap();
208
209 for i in 0..64 {
210 assert_eq!(content[i], RGB8::new(10, 10, 10), r#"expected black pixel"#);
211 }
212 }
213
214 #[test]
215 fn custom_layout() {
216 struct CustomLayout;
217
218 impl Layout for CustomLayout {
227 fn map(&self, p: Point) -> Option<usize> {
228 const LED_PER_ROW: [u8; 3] = [3, 4, 5];
229
230 if p.y < 0
231 || p.y >= LED_PER_ROW.len() as i32
232 || p.x < 0
233 || p.x >= i32::from(LED_PER_ROW[p.y as usize])
234 {
235 return None;
236 }
237
238 let mut index = 0;
239 for y in 0..p.y as usize {
240 index += usize::from(LED_PER_ROW[y]);
241 }
242 index += p.x as usize;
243
244 Some(index)
245 }
246
247 fn size(&self) -> Size {
248 Size::new(5, 3)
249 }
250 }
251
252 let content = &mut [RGB8::new(0, 0, 0); 3 + 4 + 5];
253 let writer = MockWriter { content };
254 let mut matrix = SmartLedMatrix::<_, _, { 3 + 4 + 5 }>::new(writer, CustomLayout);
255
256 let mut bb = matrix.bounding_box();
258 bb.size.width = 1;
259 matrix
260 .draw_iter(bb.points().map(|p| Pixel(p, Rgb888::RED)))
261 .unwrap();
262
263 matrix.flush().unwrap();
264
265 const B: RGB8 = RGB8::new(0, 0, 0);
266 const R: RGB8 = RGB8::new(255, 0, 0);
267 assert_eq!(
268 content,
269 &[
270 R, B, B, R, B, B, B, R, B, B, B, B, ]
274 );
275 }
276}