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