Skip to main content

epd_datafuri/graphics/
mod.rs

1//! graphics module
2pub mod display290_gray4_mfgn;
3pub mod display290_gray4_t5;
4pub mod display290_mono;
5
6use crate::color::Color;
7use embedded_graphics::{pixelcolor::BinaryColor, prelude::*};
8
9/// Necessary traits for all displays to implement for drawing
10///
11/// Adds support for:
12/// - Drawing (With the help of DrawTarget/Embedded Graphics)
13/// - Rotations
14/// - Clearing
15pub trait Display: DrawTarget {
16    /// Clears the buffer of the display with the chosen background color
17    fn clear_buffer(&mut self, background_color: Color) {
18        let fill_color = if self.is_inverted() {
19            background_color.inverse().get_byte_value()
20        } else {
21            background_color.get_byte_value()
22        };
23
24        for elem in self.get_mut_buffer().iter_mut() {
25            *elem = fill_color
26        }
27    }
28
29    /// Returns the buffer
30    fn buffer(&self) -> &[u8];
31
32    /// Returns a mutable buffer
33    fn get_mut_buffer(&mut self) -> &mut [u8];
34
35    /// Sets the rotation of the display
36    fn set_rotation(&mut self, rotation: DisplayRotation);
37
38    /// Get the current rotation of the display
39    fn rotation(&self) -> DisplayRotation;
40
41    /// If the color for this display is inverted
42    fn is_inverted(&self) -> bool;
43
44    /// Helper function for the Embedded Graphics draw trait
45    fn draw_helper(
46        &mut self,
47        width: u32,
48        height: u32,
49        pixel: Pixel<BinaryColor>,
50    ) -> Result<(), Self::Error> {
51        let rotation = self.rotation();
52        let is_inverted = self.is_inverted();
53        let buffer = self.get_mut_buffer();
54
55        let Pixel(point, color) = pixel;
56        if outside_display(point, width, height, rotation) {
57            return Ok(());
58        }
59
60        // Give us index inside the buffer and the bit-position in that u8 which needs to be changed
61        let (index, bit) = find_position(point.x as u32, point.y as u32, width, height, rotation);
62        let index = index as usize;
63
64        // "Draw" the Pixel on that bit
65        match color {
66            // White/Red
67            BinaryColor::On => {
68                if is_inverted {
69                    buffer[index] &= !bit;
70                } else {
71                    buffer[index] |= bit;
72                }
73            }
74            //Black
75            BinaryColor::Off => {
76                if is_inverted {
77                    buffer[index] |= bit;
78                } else {
79                    buffer[index] &= !bit;
80                }
81            }
82        }
83        Ok(())
84    }
85}
86
87/// Displayrotation
88#[derive(Clone, Copy, Default)]
89pub enum DisplayRotation {
90    /// No rotation
91    #[default]
92    Rotate0,
93    /// Rotate by 90 degrees clockwise
94    Rotate90,
95    /// Rotate by 180 degrees clockwise
96    Rotate180,
97    /// Rotate 270 degrees clockwise
98    Rotate270,
99}
100
101// Checks if a pos is outside the defined display
102fn outside_display(p: Point, width: u32, height: u32, rotation: DisplayRotation) -> bool {
103    if p.x < 0 || p.y < 0 {
104        return true;
105    }
106    let (x, y) = (p.x as u32, p.y as u32);
107    match rotation {
108        DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => {
109            if x >= width || y >= height {
110                return true;
111            }
112        }
113        DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => {
114            if y >= width || x >= height {
115                return true;
116            }
117        }
118    }
119    false
120}
121
122fn find_rotation(x: u32, y: u32, width: u32, height: u32, rotation: DisplayRotation) -> (u32, u32) {
123    let nx;
124    let ny;
125    match rotation {
126        DisplayRotation::Rotate0 => {
127            nx = x;
128            ny = y;
129        }
130        DisplayRotation::Rotate90 => {
131            nx = width - 1 - y;
132            ny = x;
133        }
134        DisplayRotation::Rotate180 => {
135            nx = width - 1 - x;
136            ny = height - 1 - y;
137        }
138        DisplayRotation::Rotate270 => {
139            nx = y;
140            ny = height - 1 - x;
141        }
142    }
143    (nx, ny)
144}
145
146#[rustfmt::skip]
147//returns index position in the u8-slice and the bit-position inside that u8
148fn find_position(x: u32, y: u32, width: u32, height: u32, rotation: DisplayRotation) -> (u32, u8) {
149    let (nx, ny) = find_rotation(x, y, width, height, rotation);
150    (
151        nx / 8 + width.div_ceil(8) * ny,
152        0x80 >> (nx % 8)
153    )
154}
155
156/// Computes the needed buffer length. Takes care of rounding up in case width
157/// is not divisible by 8.
158#[must_use]
159const fn buffer_len(width: usize, height: usize) -> usize {
160    width.div_ceil(8) * height
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166    use core::convert::Infallible;
167    use embedded_graphics::pixelcolor::BinaryColor;
168    use embedded_graphics::prelude::{Pixel, Point};
169
170    // Simple dummy display with a fixed-size stack buffer. Size chosen large enough for test cases.
171    struct DummyDisplay {
172        buffer: [u8; 4736],
173        rotation: DisplayRotation,
174        inverted: bool,
175        width: u32,
176        height: u32,
177    }
178
179    impl DummyDisplay {
180        fn new(width: u32, height: u32, rotation: DisplayRotation, inverted: bool) -> Self {
181            Self {
182                buffer: [0u8; 4736],
183                rotation,
184                inverted,
185                width,
186                height,
187            }
188        }
189    }
190
191    impl embedded_graphics::geometry::OriginDimensions for DummyDisplay {
192        fn size(&self) -> embedded_graphics::prelude::Size {
193            match self.rotation {
194                DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => {
195                    embedded_graphics::prelude::Size::new(self.width, self.height)
196                }
197                DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => {
198                    embedded_graphics::prelude::Size::new(self.height, self.width)
199                }
200            }
201        }
202    }
203
204    impl embedded_graphics::draw_target::DrawTarget for DummyDisplay {
205        type Error = Infallible;
206        type Color = BinaryColor;
207
208        fn draw_iter<I>(&mut self, _pixels: I) -> Result<(), Self::Error>
209        where
210            I: IntoIterator<Item = Pixel<Self::Color>>,
211        {
212            // Not used in these unit tests.
213            for _ in _pixels.into_iter() {}
214            Ok(())
215        }
216    }
217
218    impl Display for DummyDisplay {
219        fn buffer(&self) -> &[u8] {
220            &self.buffer
221        }
222
223        fn get_mut_buffer(&mut self) -> &mut [u8] {
224            &mut self.buffer
225        }
226
227        fn set_rotation(&mut self, rotation: DisplayRotation) {
228            self.rotation = rotation;
229        }
230
231        fn rotation(&self) -> DisplayRotation {
232            self.rotation
233        }
234
235        fn is_inverted(&self) -> bool {
236            self.inverted
237        }
238    }
239
240    #[test]
241    fn draw_helper_sets_msb_first_bits() {
242        // width 16 gives two bytes per row
243        let mut d = DummyDisplay::new(296, 128, DisplayRotation::Rotate0, false);
244
245        // draw pixels x = 0..7 at y = 0 -> should fill byte 0 with 0xFF (msb-first ordering)
246        for x in 0..8 {
247            let p = Pixel(Point::new(x, 0), BinaryColor::On);
248            let w = d.width;
249            let h = d.height;
250            Display::draw_helper(&mut d, w, h, p).unwrap();
251        }
252
253        assert_eq!(d.buffer[0], 0xFF);
254
255        // other bytes should remain zero
256        for &b in d.buffer.iter().skip(1) {
257            assert_eq!(b, 0);
258        }
259    }
260
261    #[test]
262    fn draw_helper_sets_last_byte_in_buffer() {
263        let width = 296u32;
264        let height = 128u32;
265        let mut d = DummyDisplay::new(width, height, DisplayRotation::Rotate0, false);
266
267        // draw the bottom-right most pixel
268        let x = width - 1;
269        let y = height - 1;
270        Display::draw_helper(
271            &mut d,
272            width,
273            height,
274            Pixel(Point::new(x as i32, y as i32), BinaryColor::On),
275        )
276        .unwrap();
277
278        let bytes_per_row = width.div_ceil(8);
279        let idx = (x / 8 + bytes_per_row * y) as usize;
280        let bit = 0x80u8 >> (x % 8);
281
282        // the bit in the last byte should be set
283        assert_eq!(d.buffer[idx] & bit, bit);
284
285        // and this should be the final byte in the buffer
286        assert_eq!(idx, (bytes_per_row * height - 1) as usize);
287    }
288
289    #[test]
290    fn draw_helper_respects_rotation() {
291        let mut d = DummyDisplay::new(296, 128, DisplayRotation::Rotate90, false);
292
293        // logical point (1,1) under Rotate90 should map to physical (nx,ny)
294        let logical_x = 1u32;
295        let logical_y = 1u32;
296
297        let w = d.width;
298        let h = d.height;
299        Display::draw_helper(
300            &mut d,
301            w,
302            h,
303            Pixel(
304                Point::new(logical_x as i32, logical_y as i32),
305                BinaryColor::On,
306            ),
307        )
308        .unwrap();
309
310        let (nx, ny) = super::find_rotation(
311            logical_x,
312            logical_y,
313            d.width,
314            d.height,
315            DisplayRotation::Rotate90,
316        );
317        let bytes_per_row = 296u32.div_ceil(8);
318        let idx = (nx / 8 + bytes_per_row * ny) as usize;
319        let bit = 0x80u8 >> (nx % 8);
320
321        assert_eq!(d.buffer[idx] & bit, bit);
322    }
323
324    #[test]
325    fn draw_helper_inversion_behavior() {
326        let mut d = DummyDisplay::new(8, 1, DisplayRotation::Rotate0, true);
327
328        // initialize to all ones (inverted display uses cleared=0 for 'On')
329        for b in d.buffer.iter_mut() {
330            *b = 0xFF;
331        }
332
333        // draw On -> should clear the bit at x=0
334        let w = d.width;
335        let h = d.height;
336        Display::draw_helper(&mut d, w, h, Pixel(Point::new(0, 0), BinaryColor::On)).unwrap();
337        assert_eq!(d.buffer[0] & 0x80, 0);
338
339        // draw Off -> on inverted display should set the bit at x=1 (0x40)
340        let w = d.width;
341        let h = d.height;
342        Display::draw_helper(&mut d, w, h, Pixel(Point::new(1, 0), BinaryColor::Off)).unwrap();
343        assert_eq!(d.buffer[0] & 0x40, 0x40);
344    }
345}