unicorn_hat/
buffer.rs

1//! Buffer abstraction for pixel operations.
2//!
3//! This module provides a trait-based abstraction for pixel buffers,
4//! enabling unit testing of drawing operations without hardware.
5
6use crate::color::RGB8;
7use crate::Error;
8
9/// Trait for types that can store and retrieve pixel data.
10///
11/// This abstraction allows drawing operations to work with both hardware
12/// (via `UnicornHat`) and in-memory buffers (via `TestBuffer`).
13pub trait PixelBuffer {
14    /// Set a pixel at the given coordinates.
15    ///
16    /// # Arguments
17    /// * `x` - X coordinate (0-7)
18    /// * `y` - Y coordinate (0-7)
19    /// * `color` - RGB color value
20    ///
21    /// # Errors
22    /// Returns `Error::InvalidCoordinate` if coordinates are out of bounds.
23    fn set_pixel(&mut self, x: usize, y: usize, color: RGB8) -> Result<(), Error>;
24
25    /// Get the color of a pixel at the given coordinates.
26    ///
27    /// # Arguments
28    /// * `x` - X coordinate (0-7)
29    /// * `y` - Y coordinate (0-7)
30    ///
31    /// # Errors
32    /// Returns `Error::InvalidCoordinate` if coordinates are out of bounds.
33    fn get_pixel(&self, x: usize, y: usize) -> Result<RGB8, Error>;
34
35    /// Get the width of the buffer (always 8 for Unicorn HAT).
36    fn width(&self) -> usize {
37        8
38    }
39
40    /// Get the height of the buffer (always 8 for Unicorn HAT).
41    fn height(&self) -> usize {
42        8
43    }
44}
45
46/// In-memory buffer for unit testing drawing operations.
47///
48/// `TestBuffer` provides a simple 8×8 pixel array that implements
49/// `PixelBuffer`, allowing drawing primitives to be tested without
50/// hardware access.
51///
52/// # Example
53/// ```
54/// use unicorn_hat::{RGB8, buffer::{PixelBuffer, TestBuffer}};
55///
56/// let mut buf = TestBuffer::new();
57/// buf.set_pixel(0, 0, RGB8::RED).unwrap();
58/// assert_eq!(buf.get_pixel(0, 0).unwrap(), RGB8::RED);
59/// ```
60#[derive(Debug, Clone, PartialEq)]
61pub struct TestBuffer {
62    pixels: [[RGB8; 8]; 8],
63}
64
65impl TestBuffer {
66    /// Create a new buffer filled with black pixels.
67    pub fn new() -> Self {
68        Self {
69            pixels: [[RGB8::BLACK; 8]; 8],
70        }
71    }
72
73    /// Create a buffer filled with a specific color.
74    pub fn with_color(color: RGB8) -> Self {
75        Self {
76            pixels: [[color; 8]; 8],
77        }
78    }
79
80    /// Fill the entire buffer with a color.
81    pub fn fill(&mut self, color: RGB8) {
82        self.pixels = [[color; 8]; 8];
83    }
84
85    /// Get all pixels as a 2D array reference.
86    pub fn get_all(&self) -> &[[RGB8; 8]; 8] {
87        &self.pixels
88    }
89
90    /// Set all pixels from a 2D array.
91    pub fn set_all(&mut self, pixels: [[RGB8; 8]; 8]) {
92        self.pixels = pixels;
93    }
94}
95
96impl Default for TestBuffer {
97    fn default() -> Self {
98        Self::new()
99    }
100}
101
102impl PixelBuffer for TestBuffer {
103    fn set_pixel(&mut self, x: usize, y: usize, color: RGB8) -> Result<(), Error> {
104        if x >= 8 || y >= 8 {
105            return Err(Error::InvalidCoordinate(x, y));
106        }
107        self.pixels[y][x] = color;
108        Ok(())
109    }
110
111    fn get_pixel(&self, x: usize, y: usize) -> Result<RGB8, Error> {
112        if x >= 8 || y >= 8 {
113            return Err(Error::InvalidCoordinate(x, y));
114        }
115        Ok(self.pixels[y][x])
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn test_new_buffer_is_black() {
125        let buf = TestBuffer::new();
126        for y in 0..8 {
127            for x in 0..8 {
128                assert_eq!(buf.get_pixel(x, y).unwrap(), RGB8::BLACK);
129            }
130        }
131    }
132
133    #[test]
134    fn test_with_color() {
135        let buf = TestBuffer::with_color(RGB8::RED);
136        for y in 0..8 {
137            for x in 0..8 {
138                assert_eq!(buf.get_pixel(x, y).unwrap(), RGB8::RED);
139            }
140        }
141    }
142
143    #[test]
144    fn test_set_get_pixel() {
145        let mut buf = TestBuffer::new();
146        buf.set_pixel(3, 4, RGB8::BLUE).unwrap();
147        assert_eq!(buf.get_pixel(3, 4).unwrap(), RGB8::BLUE);
148        assert_eq!(buf.get_pixel(0, 0).unwrap(), RGB8::BLACK);
149    }
150
151    #[test]
152    fn test_fill() {
153        let mut buf = TestBuffer::new();
154        buf.fill(RGB8::GREEN);
155        for y in 0..8 {
156            for x in 0..8 {
157                assert_eq!(buf.get_pixel(x, y).unwrap(), RGB8::GREEN);
158            }
159        }
160    }
161
162    #[test]
163    fn test_invalid_coordinates() {
164        let mut buf = TestBuffer::new();
165        assert!(buf.set_pixel(8, 0, RGB8::RED).is_err());
166        assert!(buf.set_pixel(0, 8, RGB8::RED).is_err());
167        assert!(buf.set_pixel(8, 8, RGB8::RED).is_err());
168        assert!(buf.get_pixel(8, 0).is_err());
169        assert!(buf.get_pixel(0, 8).is_err());
170    }
171
172    #[test]
173    fn test_all_corners() {
174        let mut buf = TestBuffer::new();
175        buf.set_pixel(0, 0, RGB8::RED).unwrap();
176        buf.set_pixel(7, 0, RGB8::GREEN).unwrap();
177        buf.set_pixel(0, 7, RGB8::BLUE).unwrap();
178        buf.set_pixel(7, 7, RGB8::YELLOW).unwrap();
179
180        assert_eq!(buf.get_pixel(0, 0).unwrap(), RGB8::RED);
181        assert_eq!(buf.get_pixel(7, 0).unwrap(), RGB8::GREEN);
182        assert_eq!(buf.get_pixel(0, 7).unwrap(), RGB8::BLUE);
183        assert_eq!(buf.get_pixel(7, 7).unwrap(), RGB8::YELLOW);
184    }
185
186    #[test]
187    fn test_dimensions() {
188        let buf = TestBuffer::new();
189        assert_eq!(buf.width(), 8);
190        assert_eq!(buf.height(), 8);
191    }
192
193    #[test]
194    fn test_get_set_all() {
195        let mut buf = TestBuffer::new();
196        let mut pixels = [[RGB8::BLACK; 8]; 8];
197        pixels[0][0] = RGB8::RED;
198        pixels[7][7] = RGB8::BLUE;
199
200        buf.set_all(pixels);
201        let result = buf.get_all();
202
203        assert_eq!(result[0][0], RGB8::RED);
204        assert_eq!(result[7][7], RGB8::BLUE);
205        assert_eq!(result[0][1], RGB8::BLACK);
206    }
207}