Skip to main content

pi_led_matrix/
canvas.rs

1use libc::c_int;
2use std::ffi::CString;
3
4use crate::ffi;
5use crate::{LedColor, LedFont};
6
7/// The Rust handle for the matrix canvas to draw on.
8///
9/// ```
10/// use pi_led_matrix::{LedMatrix, LedColor};
11/// let matrix = LedMatrix::new(None, None).unwrap();
12/// let mut canvas = matrix.canvas();
13/// canvas.fill(&LedColor { red: 128, green: 128, blue: 128 });
14/// ```
15pub struct LedCanvas {
16    pub(crate) handle: *mut ffi::CLedCanvas,
17}
18
19/// Implements both the [`Send`] and [`Sync`] traits for [`LedCanvas`].
20///
21/// The underlying handle referenced by this FFI is [heap-allocated],
22/// allowing safe ownership transfer between threads. Additionally,
23/// references to this handle can be safely shared across thread boundaries.
24///
25/// [heap-allocated]: https://github.com/hzeller/rpi-rgb-led-matrix/blob/0ff6a6973f95d14e3206bcef1201237097fa8edd/lib/led-matrix.cc#L501
26unsafe impl Send for LedCanvas {}
27unsafe impl Sync for LedCanvas {}
28
29impl LedCanvas {
30    /// Retrieves the width & height of the canvas
31    #[must_use]
32    pub fn canvas_size(&self) -> (i32, i32) {
33        let (mut width, mut height): (c_int, c_int) = (0, 0);
34        unsafe {
35            ffi::led_canvas_get_size(
36                self.handle,
37                std::ptr::addr_of_mut!(width),
38                std::ptr::addr_of_mut!(height),
39            );
40        }
41        (width as i32, height as i32)
42    }
43
44    /// Sets the pixel at the given coordinate to the given color.
45    pub fn set(&mut self, x: i32, y: i32, color: &LedColor) {
46        unsafe {
47            ffi::led_canvas_set_pixel(
48                self.handle,
49                x as c_int,
50                y as c_int,
51                color.red,
52                color.green,
53                color.blue,
54            );
55        }
56    }
57
58    /// Clears the canvas.
59    pub fn clear(&mut self) {
60        unsafe {
61            ffi::led_canvas_clear(self.handle);
62        }
63    }
64
65    /// Fills the canvas with the given color.
66    pub fn fill(&mut self, color: &LedColor) {
67        unsafe {
68            ffi::led_canvas_fill(self.handle, color.red, color.green, color.blue);
69        }
70    }
71
72    /// Draws a straight, one pixel wide line using the C++ library.
73    ///
74    /// Consider using embedded-graphics for more drawing features.
75    pub fn draw_line(&mut self, x0: i32, y0: i32, x1: i32, y1: i32, color: &LedColor) {
76        unsafe {
77            ffi::draw_line(
78                self.handle,
79                x0,
80                y0,
81                x1,
82                y1,
83                color.red,
84                color.green,
85                color.blue,
86            );
87        }
88    }
89
90    /// Draws a one pixel wide circle using the C++ library.
91    ///
92    /// Consider using embedded-graphics for more drawing features.
93    pub fn draw_circle(&mut self, x: i32, y: i32, radius: u32, color: &LedColor) {
94        unsafe {
95            ffi::draw_circle(
96                self.handle,
97                x as c_int,
98                y as c_int,
99                radius as c_int,
100                color.red,
101                color.green,
102                color.blue,
103            );
104        }
105    }
106
107    #[allow(clippy::too_many_arguments)]
108    /// Renders text using the C++ library.
109    ///
110    /// # Panics
111    /// If the given `text` fails to convert to a `CString`. This can
112    /// occur when there is a null character mid way in the string.
113    pub fn draw_text(
114        &mut self,
115        font: &LedFont,
116        text: &str,
117        x: i32,
118        y: i32,
119        color: &LedColor,
120        kerning_offset: i32,
121        vertical: bool,
122    ) -> i32 {
123        let text = CString::new(text).expect("given string failed to convert into a CString");
124        unsafe {
125            if vertical {
126                ffi::vertical_draw_text(
127                    self.handle,
128                    font.handle,
129                    x as c_int,
130                    y as c_int,
131                    color.red,
132                    color.green,
133                    color.blue,
134                    text.as_ptr(),
135                    kerning_offset as c_int,
136                ) as i32
137            } else {
138                ffi::draw_text(
139                    self.handle,
140                    font.handle,
141                    x as c_int,
142                    y as c_int,
143                    color.red,
144                    color.green,
145                    color.blue,
146                    text.as_ptr(),
147                    kerning_offset as c_int,
148                ) as i32
149            }
150        }
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157    use crate::{LedMatrix, LedMatrixOptions, LedRuntimeOptions};
158    use std::f64::consts::PI;
159    use std::{thread, time};
160
161    fn led_matrix() -> LedMatrix {
162        let mut options = LedMatrixOptions::new();
163        let mut rt_options = LedRuntimeOptions::new();
164        options.set_hardware_mapping("adafruit-hat-pwm");
165        options.set_chain_length(2);
166        options.set_hardware_pulsing(false);
167        options.set_refresh_rate(true);
168        options.set_brightness(10).unwrap();
169        rt_options.set_gpio_slowdown(2);
170        LedMatrix::new(Some(options), Some(rt_options)).unwrap()
171    }
172
173    #[test]
174    #[serial_test::serial]
175    fn size() {
176        let matrix = led_matrix();
177        let canvas = matrix.canvas();
178        assert_eq!(canvas.canvas_size(), (64, 32));
179    }
180
181    #[test]
182    #[serial_test::serial]
183    fn draw_line() {
184        let matrix = led_matrix();
185        let mut canvas = matrix.canvas();
186        let (width, height) = canvas.canvas_size();
187        let mut color = LedColor {
188            red: 127,
189            green: 0,
190            blue: 0,
191        };
192
193        canvas.clear();
194        for x in 0..width {
195            color.blue = 255 - 3 * x as u8;
196            canvas.draw_line(x, 0, width - 1 - x, height - 1, &color);
197            thread::sleep(time::Duration::new(0, 10000000));
198        }
199    }
200
201    #[test]
202    #[serial_test::serial]
203    fn draw_circle() {
204        let matrix = led_matrix();
205        let mut canvas = matrix.canvas();
206        let (width, height) = canvas.canvas_size();
207        let mut color = LedColor {
208            red: 127,
209            green: 0,
210            blue: 0,
211        };
212        let (x, y) = (width / 2, height / 2);
213
214        canvas.clear();
215        for r in 0..(width / 2) {
216            color.green = color.red;
217            color.red = color.blue;
218            color.blue = (r * r) as u8;
219            canvas.draw_circle(x, y, r as u32, &color);
220            thread::sleep(time::Duration::new(0, 100000000));
221        }
222    }
223
224    #[test]
225    #[serial_test::serial]
226    fn gradient() {
227        let matrix = led_matrix();
228        let mut canvas = matrix.canvas();
229        let mut color = LedColor {
230            red: 0,
231            green: 0,
232            blue: 0,
233        };
234        let period = 400;
235        let duration = time::Duration::new(3, 0);
236        let sleep_duration = duration / period;
237
238        for t in 0..period {
239            let t = t as f64;
240            color.red = ((PI * t / period as f64).sin() * 255.) as u8;
241            color.green = ((2. * PI * t / period as f64).cos() * 255.) as u8;
242            color.blue = ((3. * PI * t / period as f64 + 0.3).cos() * 255.) as u8;
243            canvas.fill(&color);
244            thread::sleep(sleep_duration);
245        }
246    }
247
248    #[test]
249    #[serial_test::serial]
250    fn canvas_swap() {
251        let matrix = led_matrix();
252        let mut canvas = matrix.canvas();
253        let mut color = LedColor {
254            red: 127,
255            green: 127,
256            blue: 0,
257        };
258
259        canvas.fill(&color);
260        canvas = matrix.offscreen_canvas();
261        color.blue = 127;
262        canvas.fill(&color);
263        thread::sleep(time::Duration::new(0, 500000000));
264        canvas = matrix.swap(canvas);
265        color.red = 0;
266        canvas.fill(&color);
267        thread::sleep(time::Duration::new(0, 500000000));
268        let _ = matrix.swap(canvas);
269        thread::sleep(time::Duration::new(0, 500000000));
270    }
271}