epd_waveshare_async/
buffer.rs

1use core::convert::Infallible;
2
3use embedded_graphics::{
4    pixelcolor::BinaryColor,
5    prelude::{Dimensions, DrawTarget, Point, Size},
6    primitives::Rectangle,
7    Pixel,
8};
9
10/// A compact buffer for storing binary coloured display data.
11///
12/// This buffer packs the data such that each byte represents 8 pixels.
13pub struct BinaryBuffer<const L: usize> {
14    size: Size,
15    bytes_per_row: usize,
16    // Data rounds the length of each row up to the next whole byte.
17    data: [u8; L],
18}
19
20/// Computes the correct size for the binary buffer based on the given dimensions.
21pub const fn binary_buffer_length(size: Size) -> usize {
22    (size.width as usize / 8) * size.height as usize
23}
24
25impl<const L: usize> BinaryBuffer<L> {
26    /// Creates a new [BinaryBuffer] with all pixels set to `BinaryColor::Off`.
27    /// 
28    /// The dimensions must match the buffer length `L`, and the width must be a multiple of 8.
29    ///
30    /// ```
31    /// use embedded_graphics::prelude::Size;
32    /// use epd_waveshare_async::buffer::{binary_buffer_length, BinaryBuffer};
33    ///
34    /// const DIMENSIONS: Size = Size::new(8, 8);
35    /// let buffer = BinaryBuffer::<{binary_buffer_length(DIMENSIONS)}>::new(DIMENSIONS);
36    /// ```
37    pub fn new(dimensions: Size) -> Self {
38        debug_assert_eq!(
39            dimensions.width % 8,
40            0,
41            "Width must be a multiple of 8 for binary packing."
42        );
43        debug_assert_eq!(
44            binary_buffer_length(dimensions),
45            L,
46            "Size must match given dimensions"
47        );
48        Self {
49            bytes_per_row: dimensions.width as usize / 8,
50            size: dimensions,
51            data: [0; L],
52        }
53    }
54
55    /// Access the packed buffer data.
56    pub fn data(&self) -> &[u8] {
57        &self.data
58    }
59}
60
61impl<const L: usize> Dimensions for BinaryBuffer<L> {
62    fn bounding_box(&self) -> Rectangle {
63        Rectangle::new(Point::zero(), self.size)
64    }
65}
66
67impl<const L: usize> DrawTarget for BinaryBuffer<L> {
68    type Color = BinaryColor;
69
70    type Error = Infallible;
71
72    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
73    where
74        I: IntoIterator<Item = Pixel<Self::Color>>,
75    {
76        for Pixel(point, color) in pixels.into_iter() {
77            if point.x < 0
78                || point.x >= self.size.width as i32
79                || point.y < 0
80                || point.y >= self.size.height as i32
81            {
82                continue; // Skip out-of-bounds pixels
83            }
84
85            let byte_index = (point.x as usize) / 8 + (point.y as usize * self.bytes_per_row);
86            let bit_index = (point.x as usize) % 8;
87
88            if color == BinaryColor::On {
89                self.data[byte_index] |= 1 << bit_index;
90            } else {
91                self.data[byte_index] &= !(1 << bit_index);
92            }
93        }
94        Ok(())
95    }
96
97    fn fill_contiguous<I>(&mut self, area: &Rectangle, colors: I) -> Result<(), Self::Error>
98    where
99        I: IntoIterator<Item = Self::Color>,
100    {
101        let drawable_area = self.bounding_box().intersection(area);
102        if drawable_area.size.width == 0 || drawable_area.size.height == 0 {
103            return Ok(()); // Nothing to fill
104        }
105        let y_start = area.top_left.y;
106        let y_end = area.top_left.y + area.size.height as i32;
107        let x_start = area.top_left.x;
108        let x_end = area.top_left.x + area.size.width as i32;
109        let mut row_start_byte = if y_start <= 0 {
110            0
111        } else {
112            y_start as usize * self.bytes_per_row
113        };
114        let (x_start_byte, x_start_bit) = if x_start <= 0 {
115            (0, 0)
116        } else {
117            ((x_start / 8) as usize, (x_start % 8) as usize)
118        };
119        let mut colors_iterator = colors.into_iter();
120        for y in y_start..y_end {
121            let mut byte_index = x_start_byte + row_start_byte;
122            let mut x_bit = x_start_bit;
123            for x in x_start..x_end {
124                let Some(color) = colors_iterator.next() else {
125                    return Ok(()); // Stop if we run out of colors
126                };
127                if y < 0 || y >= self.size.height as i32 || x < 0 || x >= self.size.width as i32 {
128                    continue; // Skip out-of-bounds pixels
129                }
130                let byte = &mut self.data[byte_index];
131                match color {
132                    BinaryColor::On => {
133                        *byte |= 1 << x_bit;
134                    }
135                    BinaryColor::Off => {
136                        *byte &= !(1 << x_bit);
137                    }
138                }
139                x_bit += 1;
140                if x_bit == 8 {
141                    // Move to the next byte
142                    x_bit = 0;
143                    byte_index += 1;
144                }
145            }
146            row_start_byte += self.size.width as usize;
147        }
148
149        Ok(())
150    }
151
152    // TODO: implement `fill_solid`
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158    use embedded_graphics::pixelcolor::BinaryColor;
159
160    #[test]
161    fn test_binary_buffer_draw_iter_singles() {
162        const SIZE: Size = Size::new(16, 4);
163        const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
164        let mut buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
165
166        // Draw a pixel at the beginning.
167        buffer
168            .draw_iter([Pixel(Point::new(0, 0), BinaryColor::On)])
169            .unwrap();
170        assert_eq!(buffer.data[0], 0b1);
171
172        // Draw a pixel in the center.
173        buffer
174            .draw_iter([Pixel(Point::new(10, 2), BinaryColor::On)])
175            .unwrap();
176        assert_eq!(buffer.data[5], 0b100);
177
178        // Draw a pixel at the end.
179        buffer
180            .draw_iter([Pixel(Point::new(15, 3), BinaryColor::On)])
181            .unwrap();
182        assert_eq!(buffer.data[7], 0b10000000);
183    }
184
185    #[test]
186    fn test_binary_buffer_draw_iter_multiple() {
187        const SIZE: Size = Size::new(16, 4);
188        const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
189        let mut buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
190
191        // Draw several pixels in a row.
192        buffer
193            .draw_iter([
194                Pixel(Point::new(1, 0), BinaryColor::On),
195                Pixel(Point::new(2, 0), BinaryColor::On),
196                Pixel(Point::new(3, 0), BinaryColor::On),
197                Pixel(Point::new(2, 0), BinaryColor::Off),
198                Pixel(Point::new(1, 1), BinaryColor::On),
199            ])
200            .unwrap();
201
202        assert_eq!(buffer.data[0], 0b00001010);
203        assert_eq!(buffer.data[2], 0b00000010);
204    }
205
206    #[test]
207    fn test_binary_buffer_draw_iter_out_of_bounds() {
208        const SIZE: Size = Size::new(16, 4);
209        const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
210        let mut buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
211        let previous_data = buffer.data;
212
213        // Draw several pixels in a row.
214        buffer
215            .draw_iter([
216                Pixel(Point::new(-1, 0), BinaryColor::On),
217                Pixel(Point::new(0, -1), BinaryColor::On),
218                Pixel(Point::new(16, 0), BinaryColor::On),
219                Pixel(Point::new(0, 4), BinaryColor::On),
220            ])
221            .unwrap();
222
223        assert_eq!(
224            buffer.data, previous_data,
225            "Data should not change when drawing out-of-bounds pixels."
226        );
227    }
228
229    #[cfg(debug_assertions)]
230    #[test]
231    #[should_panic]
232    fn test_binary_buffer_must_have_aligned_width() {
233        let _ = BinaryBuffer::<16>::new(Size::new(10, 10));
234    }
235
236    #[cfg(debug_assertions)]
237    #[test]
238    #[should_panic]
239    fn test_binary_buffer_size_must_match_dimensions() {
240        let _ = BinaryBuffer::<16>::new(Size::new(16, 10));
241    }
242}