sensehat_screen/
frame.rs

1//! Frames for the LED Matrix screen
2#[cfg(feature = "clip")]
3#[path = "frame_clip.rs"]
4pub mod clip;
5#[cfg(feature = "offset")]
6#[path = "frame_offset.rs"]
7pub mod offset;
8#[cfg(feature = "rotate")]
9#[path = "frame_rotate.rs"]
10pub mod rotate;
11
12use super::color::{PixelColor, Rgb565};
13use std::fmt::{self, Write};
14use std::ops::{Index, IndexMut};
15
16/// A single frame on the screen. Contains a private `[Rgb565; 64]`.
17#[derive(Copy, Clone)]
18pub struct FrameLine([Rgb565; 64]);
19
20impl FrameLine {
21    //  Defaults to an empty vector with capacity for 128 bytes.
22    fn new() -> Self {
23        FrameLine([Rgb565::default(); 64])
24    }
25
26    /// Create a new `FrameLine` instance, given a slice of bytes.
27    pub fn from_slice(bytes: &[u8; 128]) -> Self {
28        let colors = bytes
29            .chunks(2)
30            .map(|chunk| Rgb565::from([chunk[0], chunk[1]]))
31            .enumerate()
32            .fold(
33                [Rgb565::default(); 64],
34                |mut color_array, (index, color)| {
35                    color_array[index] = color;
36                    color_array
37                },
38            );
39        FrameLine(colors)
40    }
41
42    /// Create a new `FrameLine` instance, given a slice of `PixelColor`.
43    pub fn from_pixels(pixels: &[PixelColor; 64]) -> Self {
44        let colors = pixels.iter().map(Rgb565::from).enumerate().fold(
45            [Rgb565::default(); 64],
46            |mut color_array, (index, color)| {
47                color_array[index] = color;
48                color_array
49            },
50        );
51        FrameLine(colors)
52    }
53
54    /// Returns the `FrameLine` as a slice of bytes.
55    pub fn as_bytes(&self) -> [u8; 128] {
56        self.0
57            .iter()
58            .cloned()
59            .map(|color| {
60                let bytes: [u8; 2] = color.into();
61                bytes
62            })
63            .enumerate()
64            .fold([0u8; 128], |mut byte_array, (index, color)| {
65                byte_array[index * 2] = color[0];
66                byte_array[(index * 2) + 1] = color[1];
67                byte_array
68            })
69    }
70}
71
72impl Default for FrameLine {
73    fn default() -> Self {
74        FrameLine::new()
75    }
76}
77
78impl fmt::Debug for FrameLine {
79    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
80        let rows = self.0.chunks(8).fold(String::new(), |mut s, row| {
81            write!(&mut s, "\n[").unwrap();
82            for &px in row {
83                let rgbu16: u16 = px.into();
84                write!(&mut s, " {:04X}", rgbu16).unwrap();
85            }
86            write!(&mut s, " ]").unwrap();
87            s
88        });
89        write!(f, "FrameLine:\n{}", rows)
90    }
91}
92
93impl PartialEq for FrameLine {
94    fn eq(&self, other: &FrameLine) -> bool {
95        self.0
96            .iter()
97            .zip(other.0.iter())
98            .fold(true, |eq, (a, b)| eq && a == b)
99    }
100}
101
102/// A frame of pixels. This is the basic representation for the LED Matrix display.
103#[derive(Copy, Clone)]
104pub struct PixelFrame([PixelColor; 64]);
105
106impl fmt::Debug for PixelFrame {
107    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
108        let rows = self.0.chunks(8).fold(String::new(), |mut s, row| {
109            writeln!(&mut s, "{:?}", row).unwrap();
110            s
111        });
112        write!(f, "PixelFrame:\n{}", rows)
113    }
114}
115
116impl Default for PixelFrame {
117    fn default() -> Self {
118        PixelFrame([PixelColor::BLACK; 64])
119    }
120}
121
122impl PartialEq for PixelFrame {
123    fn eq(&self, other: &PixelFrame) -> bool {
124        self.0
125            .iter()
126            .zip(other.0.iter())
127            .fold(true, |eq, (a, b)| eq && a == b)
128    }
129}
130
131impl PixelFrame {
132    pub const BLACK: PixelFrame = PixelFrame([PixelColor::BLACK; 64]);
133    pub const RED: PixelFrame = PixelFrame([PixelColor::RED; 64]);
134    pub const BLUE: PixelFrame = PixelFrame([PixelColor::BLUE; 64]);
135    pub const GREEN: PixelFrame = PixelFrame([PixelColor::GREEN; 64]);
136    pub const WHITE: PixelFrame = PixelFrame([PixelColor::WHITE; 64]);
137    pub const YELLOW: PixelFrame = PixelFrame([PixelColor::YELLOW; 64]);
138    pub const CYAN: PixelFrame = PixelFrame([PixelColor::CYAN; 64]);
139    pub const MAGENTA: PixelFrame = PixelFrame([PixelColor::MAGENTA; 64]);
140}
141
142impl PixelFrame {
143    /// Create a `FrameLine` representing the current `PixelFrame`.
144    pub fn new(pixels: &[PixelColor; 64]) -> Self {
145        PixelFrame(*pixels)
146    }
147    /// Create a `FrameLine` representing the current `PixelFrame`.
148    pub fn frame_line(&self) -> FrameLine {
149        let colors = self
150            .0
151            .iter()
152            .enumerate()
153            .fold([PixelColor::BLACK; 64], |mut c, (idx, px)| {
154                c[idx] = *px;
155                c
156            });
157        FrameLine::from_pixels(&colors)
158    }
159
160    /// Transpose the LED Matrix. Rows become columns.
161    pub fn transpose(&mut self) {
162        for row in 0..8 {
163            for col in row..8 {
164                let idx = row * 8 + col;
165                let idx_transpose = col * 8 + row;
166                self.0.swap(idx, idx_transpose);
167            }
168        }
169    }
170
171    /// Flip the LED Matrix horizontally.
172    pub fn flip_h(&mut self) {
173        for row in self.0.chunks_mut(8) {
174            row.reverse();
175        }
176    }
177
178    /// Flip the LED Matrix vertically.
179    pub fn flip_v(&mut self) {
180        self.reverse();
181        self.flip_h();
182    }
183
184    /// Reverse the LED Matrix.
185    pub fn reverse(&mut self) {
186        self.0.reverse();
187    }
188
189    /// Returns a `[[PixelColor; 8]; 8]`, organized by rows, from top to bottom.
190    pub fn as_rows(&self) -> [[PixelColor; 8]; 8] {
191        let pixels = self.0;
192        let mut rows = [[PixelColor::default(); 8]; 8];
193        pixels.chunks(8).enumerate().for_each(|(idx, row)| {
194            rows[idx].copy_from_slice(row);
195        });
196        rows
197    }
198
199    /// Returns a `[[PixelColor; 8]; 8]`, organized by columns, from left to right.
200    pub fn as_columns(&self) -> [[PixelColor; 8]; 8] {
201        let mut pixels = *self;
202        pixels.transpose();
203        let mut columns = [[PixelColor::default(); 8]; 8];
204        pixels.0.chunks(8).enumerate().for_each(|(idx, col)| {
205            columns[idx].copy_from_slice(col);
206        });
207        columns
208    }
209
210    /// Create a new `PixelFrame` from a `[[PixelColor; 8]; 8]`, of 8 rows with 8 `PixelColor`s.
211    pub fn from_rows(rows: &[[PixelColor; 8]; 8]) -> Self {
212        let mut pixels = [PixelColor::default(); 64];
213        for (row_idx, row) in rows.iter().enumerate() {
214            for (col_idx, &px) in row.iter().enumerate() {
215                pixels[row_idx * 8 + col_idx] = px;
216            }
217        }
218        PixelFrame(pixels)
219    }
220
221    /// Create a new `PixelFrame` from a `[[PixelColor; 8]; 8]`, of 8 columns with 8 `PixelColor`s.
222    pub fn from_columns(columns: &[[PixelColor; 8]; 8]) -> Self {
223        let mut pixels = [PixelColor::default(); 64];
224        for (col_idx, col) in columns.iter().enumerate() {
225            for (row_idx, &px) in col.iter().enumerate() {
226                pixels[row_idx * 8 + col_idx] = px;
227            }
228        }
229        PixelFrame(pixels)
230    }
231}
232
233impl<'a> From<&'a [PixelColor; 64]> for PixelFrame {
234    fn from(array: &'a [PixelColor; 64]) -> Self {
235        PixelFrame::new(array)
236    }
237}
238
239impl From<[PixelColor; 64]> for PixelFrame {
240    fn from(array: [PixelColor; 64]) -> Self {
241        PixelFrame(array)
242    }
243}
244
245impl Into<[PixelColor; 64]> for PixelFrame {
246    fn into(self) -> [PixelColor; 64] {
247        self.0
248    }
249}
250
251impl Index<usize> for PixelFrame {
252    type Output = PixelColor;
253
254    fn index(&self, index: usize) -> &Self::Output {
255        &self.0[index]
256    }
257}
258
259impl IndexMut<usize> for PixelFrame {
260    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
261        &mut self.0[index]
262    }
263}
264
265/// Offset for `PixelFrame` displacement in a given direction
266#[cfg(any(feature = "offset", feature = "clip"))]
267#[derive(Copy, Clone, Debug, PartialEq)]
268pub enum Offset {
269    Left(u8),
270    Right(u8),
271    Bottom(u8),
272    Top(u8),
273}
274
275#[cfg(any(feature = "offset", feature = "clip"))]
276impl Offset {
277    /// Offset by `offset` pixels to the left of the LED Matrix.
278    ///
279    /// # Panics
280    /// If `offset` is greater than 8.
281    pub fn left(offset: u8) -> Self {
282        assert!(offset < 9);
283        Offset::Left(offset)
284    }
285
286    /// Offset by `offset` pixels to the right of the LED Matrix.
287    ///
288    /// # Panics
289    /// If `offset` is greater than 8.
290    pub fn right(offset: u8) -> Self {
291        assert!(offset < 9);
292        Offset::Right(offset)
293    }
294
295    /// Offset by `offset` pixels to the bottom of the LED Matrix.
296    ///
297    /// # Panics
298    /// If `offset` is greater than 8.
299    pub fn bottom(offset: u8) -> Self {
300        assert!(offset < 9);
301        Offset::Bottom(offset)
302    }
303
304    /// Offset by `offset` pixels to the top of the LED Matrix.
305    ///
306    /// # Panics
307    /// If `offset` is greater than 8.
308    pub fn top(offset: u8) -> Self {
309        assert!(offset < 9);
310        Offset::Top(offset)
311    }
312}
313
314#[cfg(any(feature = "offset", feature = "clip"))]
315fn clip_pixel_frames_offset_left(first: PixelFrame, second: PixelFrame, offset: u8) -> PixelFrame {
316    assert!(offset < 9);
317    match offset as usize {
318        0 => first,
319        8 => second,
320        n => {
321            let mut orig = first.as_columns();
322            let second = second.as_columns();
323            {
324                orig.rotate_left(n);
325                let (_, right) = orig.split_at_mut(8 - n);
326                right.copy_from_slice(&second[..n]);
327            }
328            PixelFrame::from_columns(&orig)
329        }
330    }
331}
332
333#[cfg(any(feature = "offset", feature = "clip"))]
334fn clip_pixel_frames_offset_right(first: PixelFrame, second: PixelFrame, offset: u8) -> PixelFrame {
335    match offset as usize {
336        0 => first,
337        8 => second,
338        n => {
339            let mut orig = first.as_columns();
340            let second = second.as_columns();
341            {
342                orig.rotate_right(n);
343                let (left, _) = orig.split_at_mut(n);
344                left.copy_from_slice(&second[8 - n..]);
345            }
346            PixelFrame::from_columns(&orig)
347        }
348    }
349}
350
351#[cfg(any(feature = "offset", feature = "clip"))]
352fn clip_pixel_frames_offset_top(first: PixelFrame, second: PixelFrame, offset: u8) -> PixelFrame {
353    match offset as usize {
354        0 => first,
355        8 => second,
356        n => {
357            let mut orig = first.as_rows();
358            let second = second.as_rows();
359            {
360                orig.rotate_left(n);
361                let (_, right) = orig.split_at_mut(8 - n);
362                right.copy_from_slice(&second[..n]);
363            }
364            PixelFrame::from_rows(&orig)
365        }
366    }
367}
368
369#[cfg(any(feature = "offset", feature = "clip"))]
370fn clip_pixel_frames_offset_bottom(
371    first: PixelFrame,
372    second: PixelFrame,
373    offset: u8,
374) -> PixelFrame {
375    match offset as usize {
376        0 => first,
377        8 => second,
378        n => {
379            let mut orig = first.as_rows();
380            let second = second.as_rows();
381            {
382                orig.rotate_right(n);
383                let (left, _) = orig.split_at_mut(n);
384                left.copy_from_slice(&second[8 - n..]);
385            }
386            PixelFrame::from_rows(&orig)
387        }
388    }
389}
390
391#[cfg(test)]
392mod tests {
393    use super::*;
394
395    const RED: PixelColor = PixelColor::RED;
396    const ONE: PixelColor = PixelColor::WHITE;
397    const TWO: PixelColor = PixelColor::BLUE;
398    const PIXEL_FRAME: &[PixelColor; 64] = &[
399        RED, ONE, RED, TWO, RED, ONE, RED, TWO, //
400        RED, ONE, RED, TWO, RED, ONE, RED, TWO, //
401        RED, ONE, RED, TWO, RED, ONE, RED, TWO, //
402        RED, ONE, RED, TWO, RED, ONE, RED, TWO, //
403        RED, ONE, RED, TWO, RED, ONE, RED, TWO, //
404        RED, ONE, RED, TWO, RED, ONE, RED, TWO, //
405        RED, ONE, RED, TWO, RED, ONE, RED, TWO, //
406        RED, ONE, RED, TWO, RED, ONE, RED, TWO, //
407    ];
408    fn test_rows() -> [[PixelColor; 8]; 8] {
409        [[RED, ONE, RED, TWO, RED, ONE, RED, TWO]; 8]
410    }
411    fn test_columns() -> [[PixelColor; 8]; 8] {
412        [
413            [RED; 8], [ONE; 8], [RED; 8], [TWO; 8], [RED; 8], [ONE; 8], [RED; 8], [TWO; 8],
414        ]
415    }
416
417    #[test]
418    fn frame_line_is_created_from_slice_of_bytes() {
419        let color: [u8; 128] = [0xE0; 128];
420        let frame_line = FrameLine::from_slice(&color);
421        frame_line
422            .as_bytes()
423            .iter()
424            .zip(color.iter())
425            .for_each(|(a, b)| {
426                assert_eq!(a, b);
427            });
428    }
429
430    #[cfg(not(feature = "big-endian"))]
431    #[test]
432    fn frame_line_is_created_from_slice_of_pixel_color() {
433        let blue = PixelColor::from_rgb565_bytes([0x1F, 0x00]);
434        let frame_line = FrameLine::from_pixels(&[blue; 64]);
435        frame_line.as_bytes().chunks(2).for_each(|chunk| {
436            assert_eq!([chunk[0], chunk[1]], [0x1F, 0x00]);
437        });
438    }
439
440    #[cfg(feature = "big-endian")]
441    #[test]
442    fn frame_line_is_created_from_slice_of_pixel_color() {
443        let blue = PixelColor::from_rgb565_bytes([0x00, 0x1F]);
444        let frame_line = FrameLine::from_pixels(&[blue; 64]);
445        frame_line.as_bytes().chunks(2).for_each(|chunk| {
446            assert_eq!([chunk[0], chunk[1]], [0x00, 0x1F]);
447        });
448    }
449
450    #[test]
451    fn pixel_frame_is_created_from_a_slice_of_pixel_color() {
452        let color_frame = [PixelColor::YELLOW; 64];
453        let pixel_frame = PixelFrame::new(&color_frame);
454        pixel_frame
455            .0
456            .iter()
457            .zip(color_frame.iter())
458            .for_each(|(a, b)| {
459                assert_eq!(a, b);
460            });
461    }
462
463    #[test]
464    fn pixel_frame_creates_a_frame_line_of_the_current_state() {
465        let color_frame = [PixelColor::GREEN; 64];
466        let pixel_frame = PixelFrame::new(&color_frame);
467        assert_eq!(
468            pixel_frame.frame_line(),
469            FrameLine::from_pixels(&color_frame)
470        );
471    }
472
473    #[test]
474    fn pixel_frame_is_represented_as_rows_of_pixel_color() {
475        let pixel_frame = PixelFrame::new(PIXEL_FRAME);
476        assert_eq!(pixel_frame.as_rows(), test_rows());
477    }
478
479    #[test]
480    fn pixel_frame_is_represented_as_columns_of_pixel_color() {
481        let pixel_frame = PixelFrame::new(PIXEL_FRAME);
482        assert_eq!(pixel_frame.as_columns(), test_columns());
483    }
484
485    #[test]
486    fn pixel_frame_is_created_from_rows_of_pixel_color() {
487        let pixel_frame = PixelFrame::new(PIXEL_FRAME);
488        assert_eq!(PixelFrame::from_rows(&test_rows()), pixel_frame);
489    }
490
491    #[test]
492    fn pixel_frame_is_created_from_columns_of_pixel_color() {
493        let pixel_frame = PixelFrame::new(PIXEL_FRAME);
494        assert_eq!(PixelFrame::from_columns(&test_columns()), pixel_frame);
495    }
496}