Skip to main content

epd_waveshare_async/
buffer.rs

1use core::{
2    cmp::{max, min},
3    convert::Infallible,
4};
5
6use embedded_graphics::{
7    pixelcolor::{BinaryColor, Gray2},
8    prelude::{Dimensions, DrawTarget, GrayColor, Point, Size},
9    primitives::Rectangle,
10    Pixel,
11};
12use heapless::Vec;
13
14/// Provides a view into a display buffer's data. This buffer is encoded into a set number of frames and bits per pixel.
15pub trait BufferView<const BITS: usize, const FRAMES: usize> {
16    /// Returns the display window covered by this buffer.
17    fn window(&self) -> Rectangle;
18
19    /// Returns the data to be written to this window.
20    fn data(&self) -> [&[u8]; FRAMES];
21}
22
23/// A compact buffer for storing binary coloured display data.
24///
25/// This buffer packs the data such that each byte represents 8 pixels.
26#[derive(Clone)]
27pub struct BinaryBuffer<const L: usize> {
28    size: Size,
29    bytes_per_row: usize,
30    // Data rounds the length of each row up to the next whole byte.
31    data: [u8; L],
32}
33
34/// Computes the correct size for the binary buffer based on the given dimensions.
35pub const fn binary_buffer_length(size: Size) -> usize {
36    (size.width as usize / 8) * size.height as usize
37}
38
39impl<const L: usize> BinaryBuffer<L> {
40    /// Creates a new [BinaryBuffer] with all pixels set to `BinaryColor::Off`.
41    ///
42    /// The dimensions must match the buffer length `L`, and the width must be a multiple of 8.
43    ///
44    /// ```
45    /// use embedded_graphics::prelude::Size;
46    /// use epd_waveshare_async::buffer::{binary_buffer_length, BinaryBuffer};
47    ///
48    /// const DIMENSIONS: Size = Size::new(8, 8);
49    /// let buffer = BinaryBuffer::<{binary_buffer_length(DIMENSIONS)}>::new(DIMENSIONS);
50    /// ```
51    pub const fn new(dimensions: Size) -> Self {
52        assert!(
53            dimensions.width % 8 == 0,
54            "Width must be a multiple of 8 for binary packing."
55        );
56        assert!(
57            binary_buffer_length(dimensions) == L,
58            "Size must match given dimensions"
59        );
60
61        Self {
62            bytes_per_row: dimensions.width as usize / 8,
63            size: dimensions,
64            data: [0; L],
65        }
66    }
67
68    /// Access the packed buffer data.
69    pub fn data(&self) -> &[u8] {
70        &self.data
71    }
72}
73
74impl<const L: usize> BufferView<1, 1> for BinaryBuffer<L> {
75    fn window(&self) -> Rectangle {
76        Rectangle::new(Point::zero(), self.size)
77    }
78
79    fn data(&self) -> [&[u8]; 1] {
80        [self.data()]
81    }
82}
83
84impl<const L: usize> Dimensions for BinaryBuffer<L> {
85    fn bounding_box(&self) -> Rectangle {
86        Rectangle::new(Point::zero(), self.size)
87    }
88}
89
90impl<const L: usize> DrawTarget for BinaryBuffer<L> {
91    type Color = BinaryColor;
92
93    type Error = Infallible;
94
95    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
96    where
97        I: IntoIterator<Item = Pixel<Self::Color>>,
98    {
99        // Benchmarking: 60ms for checker pattern in epd2in9 sample program.
100        for Pixel(point, color) in pixels.into_iter() {
101            if point.x < 0
102                || point.x >= self.size.width as i32
103                || point.y < 0
104                || point.y >= self.size.height as i32
105            {
106                continue; // Skip out-of-bounds pixels
107            }
108
109            let byte_index = (point.x as usize) / 8 + (point.y as usize * self.bytes_per_row);
110            let bit_index = (point.x as usize) % 8;
111
112            if color == BinaryColor::On {
113                self.data[byte_index] |= 0x80 >> bit_index;
114            } else {
115                self.data[byte_index] &= !(0x80 >> bit_index);
116            }
117        }
118        Ok(())
119    }
120
121    fn fill_contiguous<I>(&mut self, area: &Rectangle, colors: I) -> Result<(), Self::Error>
122    where
123        I: IntoIterator<Item = Self::Color>,
124    {
125        // Benchmarking: 39ms for checker pattern in epd2in9 sample program.
126        {
127            let drawable_area = self.bounding_box().intersection(area);
128            if drawable_area.size.width == 0 || drawable_area.size.height == 0 {
129                return Ok(()); // Nothing to fill
130            }
131        }
132
133        let y_start = area.top_left.y;
134        let y_end = area.top_left.y + area.size.height as i32;
135        let x_start = area.top_left.x;
136        let x_end = area.top_left.x + area.size.width as i32;
137
138        let mut colors_iter = colors.into_iter();
139        let mut byte_index = max(y_start, 0) as usize * self.bytes_per_row;
140        let row_start_byte_offset = max(x_start, 0) as usize / 8;
141        let row_end_byte_offset =
142            self.bytes_per_row - (min(x_end, self.size.width as i32) as usize / 8);
143        for y in y_start..y_end {
144            if y < 0 || y >= self.size.height as i32 {
145                // Skip out-of-bounds rows
146                for _ in x_start..x_end {
147                    colors_iter.next();
148                }
149                continue;
150            }
151
152            byte_index += row_start_byte_offset;
153            let mut bit_index = (max(x_start, 0) as usize) % 8;
154
155            // Y is within bounds, check X.
156            for x in x_start..x_end {
157                if x < 0 || x >= self.size.width as i32 {
158                    // Skip out-of-bounds pixels
159                    colors_iter.next();
160                    continue;
161                }
162
163                // Exit if there are no more colors to apply.
164                let Some(color) = colors_iter.next() else {
165                    return Ok(());
166                };
167
168                if color == BinaryColor::On {
169                    self.data[byte_index] |= 0x80 >> bit_index;
170                } else {
171                    self.data[byte_index] &= !(0x80 >> bit_index);
172                }
173
174                bit_index += 1;
175                if bit_index == 8 {
176                    // Move to the next byte after every 8 pixels
177                    byte_index += 1;
178                    bit_index = 0;
179                }
180            }
181
182            byte_index += row_end_byte_offset;
183        }
184
185        Ok(())
186    }
187
188    fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> {
189        // Benchmarking: 3ms for checker pattern in epd2in9 sample program.
190        let drawable_area = self.bounding_box().intersection(area);
191        if drawable_area.size.width == 0 || drawable_area.size.height == 0 {
192            return Ok(()); // Nothing to fill
193        }
194
195        let y_start = drawable_area.top_left.y;
196        let y_end = drawable_area.top_left.y + drawable_area.size.height as i32;
197        let x_start = drawable_area.top_left.x;
198        let x_end = drawable_area.top_left.x + drawable_area.size.width as i32;
199
200        let x_full_bytes_start = min(x_start + x_start % 8, x_end);
201        let x_full_bytes_end = max(x_end - (x_end % 8), x_start);
202        let num_full_bytes_per_row = (x_full_bytes_end - x_full_bytes_start) / 8;
203
204        let mut byte_index = y_start as usize * self.bytes_per_row;
205        let row_start_byte_offset = x_start as usize / 8;
206        let row_end_byte_offset = self.bytes_per_row - (x_end as usize / 8);
207        for _y in y_start..y_end {
208            byte_index += row_start_byte_offset;
209            let mut bit_index = (x_start as usize) % 8;
210
211            /// Sets the next bit from `color` and advances `bit_index` and `byte_index`
212            /// appropriately.
213            macro_rules! set_next_bit {
214                () => {
215                    if color == BinaryColor::On {
216                        self.data[byte_index] |= 0x80 >> bit_index;
217                    } else {
218                        self.data[byte_index] &= !(0x80 >> bit_index);
219                    }
220                    bit_index += 1;
221                    if bit_index == 8 {
222                        // Move to the next byte after every 8 pixels
223                        byte_index += 1;
224                        bit_index = 0;
225                    }
226                };
227            }
228
229            if num_full_bytes_per_row == 0 {
230                // There are no full bytes in this row, so just set colors bitwise.
231                for _x in x_start..x_end {
232                    set_next_bit!();
233                }
234            } else {
235                // Set colors bitwise in the first byte if it's not byte-aligned.
236                for _x in x_start..x_full_bytes_start {
237                    set_next_bit!();
238                }
239
240                // Fast fill for any fully covered bytes in the row.
241                for _ in 0..num_full_bytes_per_row {
242                    if color == BinaryColor::On {
243                        self.data[byte_index] = 0xFF;
244                    } else {
245                        self.data[byte_index] = 0x00;
246                    }
247                    byte_index += 1;
248                }
249
250                // Set the partially covered byte at the end of the row, if any.
251                bit_index = x_full_bytes_end as usize % 8;
252                for _x in x_full_bytes_end..x_end {
253                    set_next_bit!();
254                }
255            }
256
257            byte_index += row_end_byte_offset;
258        }
259
260        Ok(())
261    }
262}
263
264/// A buffer supporting 2-bit grayscale colours. This buffer splits the 2 bits into two separate single-bit framebuffers.
265#[derive(Clone)]
266pub struct Gray2SplitBuffer<const L: usize> {
267    pub low: BinaryBuffer<L>,
268    pub high: BinaryBuffer<L>,
269}
270
271/// Computes the correct size for the [Gray2SplitBuffer] based on the given dimensions.
272pub const fn gray2_split_buffer_length(size: Size) -> usize {
273    binary_buffer_length(size)
274}
275
276impl<const L: usize> Gray2SplitBuffer<L> {
277    /// Creates a new [Gray2SplitBuffer] with all pixels set to 0.
278    ///
279    /// The dimensions must match the buffer length `L`, and the width must be a multiple of 8.
280    ///
281    /// ```
282    /// use embedded_graphics::prelude::Size;
283    /// use epd_waveshare_async::buffer::{gray2_split_buffer_length, Gray2SplitBuffer};
284    ///
285    /// const DIMENSIONS: Size = Size::new(8, 8);
286    /// let buffer = Gray2SplitBuffer::<{gray2_split_buffer_length(DIMENSIONS)}>::new(DIMENSIONS);
287    /// ```
288    pub const fn new(dimensions: Size) -> Self {
289        Self {
290            low: BinaryBuffer::new(dimensions),
291            high: BinaryBuffer::new(dimensions),
292        }
293    }
294}
295
296impl<const L: usize> BufferView<1, 2> for Gray2SplitBuffer<L> {
297    fn window(&self) -> Rectangle {
298        Rectangle::new(Point::zero(), self.low.size)
299    }
300
301    fn data(&self) -> [&[u8]; 2] {
302        [self.low.data(), self.high.data()]
303    }
304}
305
306impl<const L: usize> Dimensions for Gray2SplitBuffer<L> {
307    fn bounding_box(&self) -> Rectangle {
308        Rectangle::new(Point::zero(), self.low.size)
309    }
310}
311
312fn to_low_and_high_as_binary(g: Gray2) -> (BinaryColor, BinaryColor) {
313    let luma = g.luma();
314    let low = if (luma & 1) == 0 {
315        BinaryColor::Off
316    } else {
317        BinaryColor::On
318    };
319    let high = if (luma & 0b10) == 0 {
320        BinaryColor::Off
321    } else {
322        BinaryColor::On
323    };
324    (low, high)
325}
326
327const GRAY_ITER_CHUNK_SIZE: usize = 128;
328
329impl<const L: usize> DrawTarget for Gray2SplitBuffer<L> {
330    type Color = Gray2;
331
332    type Error = Infallible;
333
334    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
335    where
336        I: IntoIterator<Item = Pixel<Self::Color>>,
337    {
338        // We iterate the data into chunks because:
339        // 1. It's usually less memory pressure than creating two more full-size vectors.
340        // 2. The iterator is allowed to go out-of-bounds, so it might actually be longer than L.
341        let mut low_chunk: Vec<Pixel<BinaryColor>, GRAY_ITER_CHUNK_SIZE> = Vec::new();
342        let mut high_chunk: Vec<Pixel<BinaryColor>, GRAY_ITER_CHUNK_SIZE> = Vec::new();
343        for p in pixels.into_iter() {
344            let (low, high) = to_low_and_high_as_binary(p.1);
345            if low_chunk.is_full() {
346                self.low.draw_iter(low_chunk)?;
347                low_chunk = Vec::new();
348                self.high.draw_iter(high_chunk)?;
349                high_chunk = Vec::new();
350            }
351            unsafe {
352                low_chunk.push_unchecked(Pixel(p.0, low));
353                high_chunk.push_unchecked(Pixel(p.0, high));
354            }
355        }
356        if !low_chunk.is_empty() {
357            self.low.draw_iter(low_chunk)?;
358            self.high.draw_iter(high_chunk)?;
359        }
360        Ok(())
361    }
362
363    fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> {
364        let (low, high) = to_low_and_high_as_binary(color);
365        self.low.fill_solid(area, low)?;
366        self.high.fill_solid(area, high)?;
367        Ok(())
368    }
369}
370
371pub trait Rotation {
372    /// Returns the inverse rotation that reverses this rotation's effect.
373    fn inverse(&self) -> Self;
374
375    /// Rotates the given size according to this rotation type.
376    fn rotate_size(&self, size: Size) -> Size;
377
378    /// Rotates a point according to this rotation type, within overall source bounds of the given size.
379    ///
380    /// For example, if the given `point` is (1,2) from a 10x20 space, then [Rotate::Degrees90] would
381    /// return (17, 1) in a 20x10 space. `bounds` should be the source dimensions of 10x20.
382    ///
383    /// ```rust
384    /// # use embedded_graphics::prelude::{Point, Size};
385    /// # use epd_waveshare_async::buffer::{Rotate, Rotation};
386    ///
387    /// let r = Rotate::Degrees90;
388    /// assert_eq!(r.rotate_point(Point::new(1, 2), Size::new(10, 20)), Point::new(17, 1));
389    /// ```
390    fn rotate_point(&self, point: Point, bounds: Size) -> Point;
391
392    /// Rotates a rectangle according to this rotation type, within overall source bounds of the given size.
393    fn rotate_rectangle(&self, rectangle: Rectangle, bounds: Size) -> Rectangle;
394}
395
396/// Represents a 90, 180, or 270 degree clockwise rotation of a point within a given size.
397#[derive(Clone, Copy, Debug, PartialEq, Eq)]
398pub enum Rotate {
399    Degrees90,
400    Degrees180,
401    Degrees270,
402}
403
404impl Rotation for Rotate {
405    fn inverse(&self) -> Self {
406        match self {
407            Rotate::Degrees90 => Rotate::Degrees270,
408            Rotate::Degrees180 => Rotate::Degrees180,
409            Rotate::Degrees270 => Rotate::Degrees90,
410        }
411    }
412
413    fn rotate_size(&self, size: Size) -> Size {
414        match self {
415            Rotate::Degrees90 | Rotate::Degrees270 => Size::new(size.height, size.width),
416            Rotate::Degrees180 => size,
417        }
418    }
419
420    fn rotate_point(&self, point: Point, source_bounds: Size) -> Point {
421        match self {
422            Rotate::Degrees90 => Point::new(source_bounds.height as i32 - point.y - 1, point.x),
423            Rotate::Degrees180 => Point::new(
424                source_bounds.width as i32 - point.x - 1,
425                source_bounds.height as i32 - point.y - 1,
426            ),
427            Rotate::Degrees270 => Point::new(point.y, source_bounds.width as i32 - point.x - 1),
428        }
429    }
430
431    fn rotate_rectangle(&self, rectangle: Rectangle, source_bounds: Size) -> Rectangle {
432        match self {
433            Rotate::Degrees90 => {
434                let old_bottom_left =
435                    rectangle.top_left + Point::new(0, rectangle.size.height as i32 - 1);
436                let new_top_left = self.rotate_point(old_bottom_left, source_bounds);
437                Rectangle::new(new_top_left, self.rotate_size(rectangle.size))
438            }
439            Rotate::Degrees180 => {
440                let old_bottom_right = rectangle.top_left + rectangle.size - Point::new(1, 1);
441                let new_top_left = self.rotate_point(old_bottom_right, source_bounds);
442                Rectangle::new(new_top_left, self.rotate_size(rectangle.size))
443            }
444            Rotate::Degrees270 => {
445                let old_top_right =
446                    rectangle.top_left + Point::new(rectangle.size.width as i32 - 1, 0);
447                let new_top_left = self.rotate_point(old_top_right, source_bounds);
448                Rectangle::new(new_top_left, self.rotate_size(rectangle.size))
449            }
450        }
451    }
452}
453
454/// Enables arbitrarily rotating an underlying [DrawTarget] buffer. This is useful if the default display
455/// orientation does not match the desired orientation of the content.
456///
457/// ```text
458/// let mut default_buffer = epd.new_buffer();
459/// // If the default buffer is portrait, this would rotate it so you can draw to it as if it's in landscape mode.
460/// let rotated_buffer = RotatedBuffer::new(&mut default_buffer, Rotate::Degrees90);
461///
462/// // ... Use the buffer here
463///
464/// epd.display_buffer(&mut spi, rotated_buffer.inner()).await?;
465/// ```
466pub struct RotatedBuffer<B: DrawTarget, R: Rotation> {
467    bounds: Rectangle,
468    buffer: B,
469    rotation: R,
470}
471
472impl<B: DrawTarget, R: Rotation> RotatedBuffer<B, R> {
473    pub fn new(buffer: B, rotation: R) -> Self {
474        let inverse_rotation = rotation.inverse();
475        let inner_bounds = buffer.bounding_box();
476        let bounds = inverse_rotation.rotate_rectangle(inner_bounds, inner_bounds.size);
477        Self {
478            bounds,
479            buffer,
480            rotation,
481        }
482    }
483
484    /// Provides read-only access to the inner buffer.
485    pub fn inner(&mut self) -> &B {
486        &self.buffer
487    }
488
489    /// Drops this rotated buffer wrapper and takes out the inner buffer.
490    pub fn take_inner(self) -> B {
491        self.buffer
492    }
493}
494
495impl<B: DrawTarget, R: Rotation> Dimensions for RotatedBuffer<B, R> {
496    fn bounding_box(&self) -> Rectangle {
497        self.bounds
498    }
499}
500
501impl<B: DrawTarget, R: Rotation> DrawTarget for RotatedBuffer<B, R> {
502    type Color = B::Color;
503    type Error = B::Error;
504
505    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
506    where
507        I: IntoIterator<Item = Pixel<Self::Color>>,
508    {
509        let rotated_pixels = pixels.into_iter().map(|Pixel(point, color)| {
510            let rotated_point = self.rotation.rotate_point(point, self.bounds.size);
511            Pixel(rotated_point, color)
512        });
513        self.buffer.draw_iter(rotated_pixels)
514    }
515}
516
517#[inline(always)]
518/// Splits a 16-bit value into the two 8-bit values representing the low and high bytes.
519pub(crate) fn split_low_and_high(value: u16) -> (u8, u8) {
520    let low = (value & 0xFF) as u8;
521    let high = ((value >> 8) & 0xFF) as u8;
522    (low, high)
523}
524
525#[cfg(test)]
526mod tests {
527    use super::*;
528    use embedded_graphics::pixelcolor::BinaryColor;
529
530    #[test]
531    fn test_binary_buffer_draw_iter_singles() {
532        const SIZE: Size = Size::new(16, 4);
533        const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
534        let mut buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
535
536        // Draw a pixel at the beginning.
537        buffer
538            .draw_iter([Pixel(Point::new(0, 0), BinaryColor::On)])
539            .unwrap();
540        assert_eq!(buffer.data[0], 0b10000000);
541
542        // Draw a pixel in the center.
543        buffer
544            .draw_iter([Pixel(Point::new(10, 2), BinaryColor::On)])
545            .unwrap();
546        assert_eq!(buffer.data[5], 0b00100000);
547
548        // Draw a pixel at the end.
549        buffer
550            .draw_iter([Pixel(Point::new(15, 3), BinaryColor::On)])
551            .unwrap();
552        assert_eq!(buffer.data[7], 0b1);
553    }
554
555    #[test]
556    fn test_binary_buffer_draw_iter_multiple() {
557        const SIZE: Size = Size::new(16, 4);
558        const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
559        let mut buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
560
561        // Draw several pixels in a row.
562        buffer
563            .draw_iter([
564                Pixel(Point::new(1, 0), BinaryColor::On),
565                Pixel(Point::new(2, 0), BinaryColor::On),
566                Pixel(Point::new(3, 0), BinaryColor::On),
567                Pixel(Point::new(2, 0), BinaryColor::Off),
568                Pixel(Point::new(1, 1), BinaryColor::On),
569            ])
570            .unwrap();
571
572        assert_eq!(buffer.data[0], 0b01010000);
573        assert_eq!(buffer.data[2], 0b01000000);
574    }
575
576    #[test]
577    fn test_binary_buffer_draw_iter_out_of_bounds() {
578        const SIZE: Size = Size::new(16, 4);
579        const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
580        let mut buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
581        let previous_data = buffer.data;
582
583        // Draw several pixels in a row.
584        buffer
585            .draw_iter([
586                Pixel(Point::new(-1, 0), BinaryColor::On),
587                Pixel(Point::new(0, -1), BinaryColor::On),
588                Pixel(Point::new(16, 0), BinaryColor::On),
589                Pixel(Point::new(0, 4), BinaryColor::On),
590            ])
591            .unwrap();
592
593        assert_eq!(
594            buffer.data, previous_data,
595            "Data should not change when drawing out-of-bounds pixels."
596        );
597    }
598
599    #[cfg(debug_assertions)]
600    #[test]
601    #[should_panic]
602    fn test_binary_buffer_must_have_aligned_width() {
603        let _ = BinaryBuffer::<16>::new(Size::new(10, 10));
604    }
605
606    #[cfg(debug_assertions)]
607    #[test]
608    #[should_panic]
609    fn test_binary_buffer_size_must_match_dimensions() {
610        let _ = BinaryBuffer::<16>::new(Size::new(16, 10));
611    }
612
613    #[test]
614    fn test_binary_buffer_fill_continguous() {
615        // 8 rows, 1 byte each.
616        const SIZE: Size = Size::new(24, 8);
617        const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
618        let mut buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
619
620        // Draw diagonal squares.
621        buffer
622            .fill_contiguous(
623                &Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
624                [BinaryColor::On; 8 * 8],
625            )
626            .unwrap();
627        buffer
628            .fill_contiguous(
629                // Go out of bounds to ensure it doesn't panic.
630                &Rectangle::new(Point::new(6, 2), Size::new(12, 4)),
631                [BinaryColor::On; 12 * 4],
632            )
633            .unwrap();
634        buffer
635            .fill_contiguous(
636                // Go out of bounds to ensure it doesn't panic.
637                &Rectangle::new(Point::new(20, 4), Size::new(8, 8)),
638                [BinaryColor::On; 8 * 8],
639            )
640            .unwrap();
641
642        #[rustfmt::skip]
643        let expected: [u8; 3 * 8] = [
644            0b11110000, 0b00000000, 0b00000000,
645            0b11110000, 0b00000000, 0b00000000,
646            0b11110011, 0b11111111, 0b11000000,
647            0b11110011, 0b11111111, 0b11000000,
648            0b00000011, 0b11111111, 0b11001111,
649            0b00000011, 0b11111111, 0b11001111,
650            0b00000000, 0b00000000, 0b00001111,
651            0b00000000, 0b00000000, 0b00001111,
652        ];
653        assert_eq!(buffer.data(), &expected);
654    }
655
656    #[test]
657    fn test_binary_buffer_fill_solid() {
658        // 8 rows, 1 byte each.
659        const SIZE: Size = Size::new(24, 8);
660        const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
661        let mut buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
662
663        // Draw diagonal squares.
664        buffer
665            .fill_solid(
666                &Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
667                BinaryColor::On,
668            )
669            .unwrap();
670        buffer
671            .fill_solid(
672                // Go out of bounds to ensure it doesn't panic.
673                &Rectangle::new(Point::new(6, 2), Size::new(12, 4)),
674                BinaryColor::On,
675            )
676            .unwrap();
677        buffer
678            .fill_solid(
679                // Go out of bounds to ensure it doesn't panic.
680                &Rectangle::new(Point::new(20, 4), Size::new(8, 8)),
681                BinaryColor::On,
682            )
683            .unwrap();
684
685        #[rustfmt::skip]
686        let expected: [u8; 3 * 8] = [
687            0b11110000, 0b00000000, 0b00000000,
688            0b11110000, 0b00000000, 0b00000000,
689            0b11110011, 0b11111111, 0b11000000,
690            0b11110011, 0b11111111, 0b11000000,
691            0b00000011, 0b11111111, 0b11001111,
692            0b00000011, 0b11111111, 0b11001111,
693            0b00000000, 0b00000000, 0b00001111,
694            0b00000000, 0b00000000, 0b00001111,
695        ];
696        assert_eq!(buffer.data(), &expected);
697    }
698
699    #[test]
700    fn test_gray2_split_buffer_draw_iter_singles() {
701        const SIZE: Size = Size::new(16, 4);
702        const BUFFER_LENGTH: usize = gray2_split_buffer_length(SIZE);
703        let mut buffer = Gray2SplitBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
704
705        // Draw a pixel at the beginning.
706        buffer
707            .draw_iter([Pixel(Point::new(0, 0), Gray2::new(0b11))])
708            .unwrap();
709        assert_eq!(buffer.low.data[0], 0b10000000);
710        assert_eq!(buffer.high.data[0], 0b10000000);
711
712        // Draw a pixel in the center.
713        buffer
714            .draw_iter([Pixel(Point::new(10, 2), Gray2::new(0b10))])
715            .unwrap();
716        assert_eq!(buffer.data()[0][5], 0b00000000);
717        assert_eq!(buffer.data()[1][5], 0b00100000);
718
719        // Draw a pixel at the end.
720        buffer
721            .draw_iter([Pixel(Point::new(15, 3), Gray2::new(0b01))])
722            .unwrap();
723        assert_eq!(buffer.low.data[7], 0b1);
724        assert_eq!(buffer.high.data[7], 0b0);
725    }
726
727    #[test]
728    fn test_gray2_buffer_draw_iter_multiple() {
729        const SIZE: Size = Size::new(16, 4);
730        const BUFFER_LENGTH: usize = gray2_split_buffer_length(SIZE);
731        let mut buffer = Gray2SplitBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
732
733        // Draw several pixels in a row.
734        buffer
735            .draw_iter([
736                Pixel(Point::new(1, 0), Gray2::new(0b11)),
737                Pixel(Point::new(2, 0), Gray2::new(0b11)),
738                Pixel(Point::new(3, 0), Gray2::new(0b01)),
739                Pixel(Point::new(2, 0), Gray2::new(0)),
740                Pixel(Point::new(1, 1), Gray2::new(0b10)),
741            ])
742            .unwrap();
743
744        assert_eq!(buffer.low.data[0], 0b01010000);
745        assert_eq!(buffer.high.data[0], 0b01000000);
746        assert_eq!(buffer.low.data[2], 0b00000000);
747        assert_eq!(buffer.high.data[2], 0b01000000);
748    }
749
750    #[test]
751    fn test_gray2_buffer_draw_iter_out_of_bounds() {
752        const SIZE: Size = Size::new(16, 4);
753        const BUFFER_LENGTH: usize = gray2_split_buffer_length(SIZE);
754        let mut buffer = Gray2SplitBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
755        let previous = buffer.clone();
756
757        // Draw several pixels in a row.
758        buffer
759            .draw_iter([
760                Pixel(Point::new(-1, 0), Gray2::new(0b11)),
761                Pixel(Point::new(0, -1), Gray2::new(0b11)),
762                Pixel(Point::new(16, 0), Gray2::new(0b11)),
763                Pixel(Point::new(0, 4), Gray2::new(0b11)),
764            ])
765            .unwrap();
766
767        assert_eq!(
768            buffer.data(),
769            previous.data(),
770            "Data should not change when drawing out-of-bounds pixels."
771        );
772    }
773
774    #[cfg(debug_assertions)]
775    #[test]
776    #[should_panic]
777    fn test_gray2_buffer_must_have_aligned_width() {
778        let _ = Gray2SplitBuffer::<16>::new(Size::new(10, 10));
779    }
780
781    #[cfg(debug_assertions)]
782    #[test]
783    #[should_panic]
784    fn test_gray2_buffer_size_must_match_dimensions() {
785        let _ = Gray2SplitBuffer::<16>::new(Size::new(16, 10));
786    }
787
788    #[test]
789    fn test_gray2_buffer_fill_solid() {
790        // 8 rows, 1 byte each.
791        const SIZE: Size = Size::new(24, 8);
792        const BUFFER_LENGTH: usize = gray2_split_buffer_length(SIZE);
793        let mut buffer = Gray2SplitBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
794
795        // Draw diagonal squares.
796        buffer
797            .fill_solid(
798                &Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
799                Gray2::new(0b11),
800            )
801            .unwrap();
802        buffer
803            .fill_solid(
804                // Go out of bounds to ensure it doesn't panic.
805                &Rectangle::new(Point::new(6, 2), Size::new(12, 4)),
806                Gray2::new(0b10),
807            )
808            .unwrap();
809        buffer
810            .fill_solid(
811                // Go out of bounds to ensure it doesn't panic.
812                &Rectangle::new(Point::new(20, 4), Size::new(8, 8)),
813                Gray2::new(0b01),
814            )
815            .unwrap();
816
817        #[rustfmt::skip]
818        let expected_low: [u8; 3 * 8] = [
819            0b11110000, 0b00000000, 0b00000000,
820            0b11110000, 0b00000000, 0b00000000,
821            0b11110000, 0b00000000, 0b00000000,
822            0b11110000, 0b00000000, 0b00000000,
823            0b00000000, 0b00000000, 0b00001111,
824            0b00000000, 0b00000000, 0b00001111,
825            0b00000000, 0b00000000, 0b00001111,
826            0b00000000, 0b00000000, 0b00001111,
827        ];
828        #[rustfmt::skip]
829        let expected_high: [u8; 3 * 8] = [
830            0b11110000, 0b00000000, 0b00000000,
831            0b11110000, 0b00000000, 0b00000000,
832            0b11110011, 0b11111111, 0b11000000,
833            0b11110011, 0b11111111, 0b11000000,
834            0b00000011, 0b11111111, 0b11000000,
835            0b00000011, 0b11111111, 0b11000000,
836            0b00000000, 0b00000000, 0b00000000,
837            0b00000000, 0b00000000, 0b00000000,
838        ];
839        assert_eq!(buffer.data()[0], &expected_low);
840        assert_eq!(buffer.data()[1], &expected_high);
841    }
842
843    #[test]
844    fn test_rotated_buffer_bounds() {
845        const SIZE: Size = Size::new(8, 24);
846        const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
847
848        let mut rotated_buffer = RotatedBuffer::new(
849            BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE),
850            Rotate::Degrees90,
851        );
852        assert_eq!(
853            rotated_buffer.bounding_box(),
854            Rectangle::new(Point::new(0, 0), Size::new(24, 8))
855        );
856
857        rotated_buffer = RotatedBuffer::new(
858            BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE),
859            Rotate::Degrees180,
860        );
861        assert_eq!(
862            rotated_buffer.bounding_box(),
863            Rectangle::new(Point::new(0, 0), Size::new(8, 24))
864        );
865
866        rotated_buffer = RotatedBuffer::new(
867            BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE),
868            Rotate::Degrees270,
869        );
870        assert_eq!(
871            rotated_buffer.bounding_box(),
872            Rectangle::new(Point::new(0, 0), Size::new(24, 8))
873        );
874    }
875
876    #[test]
877    fn test_rotated_buffer_draw_iter() {
878        const SIZE: Size = Size::new(8, 4);
879        const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
880
881        let mut rotated_buffer = RotatedBuffer::new(
882            BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE),
883            Rotate::Degrees90,
884        );
885        rotated_buffer
886            .draw_iter([
887                Pixel(Point::new(-1, -1), BinaryColor::On), // Should be ignored.
888                Pixel(Point::new(0, 0), BinaryColor::On),
889                Pixel(Point::new(1, 1), BinaryColor::On),
890                Pixel(Point::new(2, 2), BinaryColor::On),
891            ])
892            .unwrap();
893        #[rustfmt::skip]
894        let expected: [u8; 4] = [
895                0b00000001,
896                0b00000010,
897                0b00000100,
898                0b00000000,
899            ];
900        assert_eq!(rotated_buffer.inner().data(), &expected);
901
902        rotated_buffer = RotatedBuffer::new(
903            BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE),
904            Rotate::Degrees180,
905        );
906        rotated_buffer
907            .draw_iter([
908                Pixel(Point::new(-1, -1), BinaryColor::On), // Should be ignored.
909                Pixel(Point::new(0, 0), BinaryColor::On),
910                Pixel(Point::new(1, 1), BinaryColor::On),
911                Pixel(Point::new(2, 2), BinaryColor::On),
912            ])
913            .unwrap();
914        #[rustfmt::skip]
915        let expected: [u8; 4] = [
916                0b00000000,
917                0b00000100,
918                0b00000010,
919                0b00000001,
920            ];
921        assert_eq!(rotated_buffer.inner().data(), &expected);
922
923        rotated_buffer = RotatedBuffer::new(
924            BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE),
925            Rotate::Degrees270,
926        );
927        rotated_buffer
928            .draw_iter([
929                Pixel(Point::new(-1, -1), BinaryColor::On), // Should be ignored.
930                Pixel(Point::new(0, 0), BinaryColor::On),
931                Pixel(Point::new(1, 1), BinaryColor::On),
932                Pixel(Point::new(2, 2), BinaryColor::On),
933            ])
934            .unwrap();
935        #[rustfmt::skip]
936        let expected: [u8; 4] = [
937                0b00000000,
938                0b00100000,
939                0b01000000,
940                0b10000000,
941            ];
942        assert_eq!(rotated_buffer.inner().data(), &expected);
943    }
944
945    #[test]
946    fn test_rotated_buffer_fill_contiguous() {
947        // 8 rows, 1 byte each.
948        const SIZE: Size = Size::new(8, 6);
949        const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
950        let buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
951
952        let mut rotated_buffer = RotatedBuffer::new(buffer.clone(), Rotate::Degrees90);
953        rotated_buffer
954            .fill_contiguous(
955                &Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
956                [BinaryColor::On; 8 * 8],
957            )
958            .unwrap();
959
960        #[rustfmt::skip]
961        let expected: [u8; 6] = [
962            0b00001111,
963            0b00001111,
964            0b00001111,
965            0b00001111,
966            0b00000000,
967            0b00000000,
968        ];
969        assert_eq!(rotated_buffer.inner().data(), &expected);
970
971        rotated_buffer = RotatedBuffer::new(buffer.clone(), Rotate::Degrees180);
972        rotated_buffer
973            .fill_contiguous(
974                &Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
975                [BinaryColor::On; 8 * 8],
976            )
977            .unwrap();
978
979        #[rustfmt::skip]
980        let expected: [u8; 6] = [
981            0b00000000,
982            0b00000000,
983            0b00001111,
984            0b00001111,
985            0b00001111,
986            0b00001111,
987        ];
988        assert_eq!(rotated_buffer.inner().data(), &expected);
989
990        rotated_buffer = RotatedBuffer::new(buffer.clone(), Rotate::Degrees270);
991        rotated_buffer
992            .fill_contiguous(
993                &Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
994                [BinaryColor::On; 8 * 8],
995            )
996            .unwrap();
997
998        #[rustfmt::skip]
999        let expected: [u8; 6] = [
1000            0b00000000,
1001            0b00000000,
1002            0b11110000,
1003            0b11110000,
1004            0b11110000,
1005            0b11110000,
1006        ];
1007        assert_eq!(rotated_buffer.inner().data(), &expected);
1008    }
1009
1010    #[test]
1011    fn test_rotated_buffer_fill_solid() {
1012        // 8 rows, 1 byte each.
1013        const SIZE: Size = Size::new(8, 6);
1014        const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
1015        let buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
1016
1017        let mut rotated_buffer = RotatedBuffer::new(buffer.clone(), Rotate::Degrees90);
1018        rotated_buffer
1019            .fill_solid(
1020                &Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
1021                BinaryColor::On,
1022            )
1023            .unwrap();
1024
1025        #[rustfmt::skip]
1026        let expected: [u8; 6] = [
1027            0b00001111,
1028            0b00001111,
1029            0b00001111,
1030            0b00001111,
1031            0b00000000,
1032            0b00000000,
1033        ];
1034        assert_eq!(rotated_buffer.inner().data(), &expected);
1035
1036        rotated_buffer = RotatedBuffer::new(buffer.clone(), Rotate::Degrees180);
1037        rotated_buffer
1038            .fill_solid(
1039                &Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
1040                BinaryColor::On,
1041            )
1042            .unwrap();
1043
1044        #[rustfmt::skip]
1045        let expected: [u8; 6] = [
1046            0b00000000,
1047            0b00000000,
1048            0b00001111,
1049            0b00001111,
1050            0b00001111,
1051            0b00001111,
1052        ];
1053        assert_eq!(rotated_buffer.inner().data(), &expected);
1054
1055        rotated_buffer = RotatedBuffer::new(buffer.clone(), Rotate::Degrees270);
1056        rotated_buffer
1057            .fill_solid(
1058                &Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
1059                BinaryColor::On,
1060            )
1061            .unwrap();
1062
1063        #[rustfmt::skip]
1064        let expected: [u8; 6] = [
1065            0b00000000,
1066            0b00000000,
1067            0b11110000,
1068            0b11110000,
1069            0b11110000,
1070            0b11110000,
1071        ];
1072        assert_eq!(rotated_buffer.inner().data(), &expected);
1073    }
1074
1075    #[test]
1076    fn test_rotate_near_corner() {
1077        let mut r = Rotate::Degrees90;
1078        // (1,1) in [10, 20] becomes (18, 1) in [20, 10].
1079        assert_eq!(
1080            Point::new(18, 1),
1081            r.rotate_point(Point::new(1, 1), Size::new(10, 20))
1082        );
1083        r = Rotate::Degrees180;
1084        // (1,1) in [10, 20] becomes (8, 18) in [10, 20].
1085        assert_eq!(
1086            Point::new(8, 18),
1087            r.rotate_point(Point::new(1, 1), Size::new(10, 20))
1088        );
1089        r = Rotate::Degrees270;
1090        // (1,1) in [10, 20] becomes (1, 8) in [20, 10].
1091        assert_eq!(
1092            Point::new(1, 8),
1093            r.rotate_point(Point::new(1, 1), Size::new(10, 20))
1094        );
1095    }
1096
1097    #[test]
1098    fn test_rotate_centre() {
1099        let mut r = Rotate::Degrees90;
1100        assert_eq!(
1101            Point::new(2, 2),
1102            r.rotate_point(Point::new(2, 2), Size::new(5, 5))
1103        );
1104        r = Rotate::Degrees180;
1105        assert_eq!(
1106            Point::new(2, 2),
1107            r.rotate_point(Point::new(2, 2), Size::new(5, 5))
1108        );
1109        r = Rotate::Degrees270;
1110        assert_eq!(
1111            Point::new(2, 2),
1112            r.rotate_point(Point::new(2, 2), Size::new(5, 5))
1113        );
1114    }
1115
1116    #[test]
1117    fn test_rotate_size() {
1118        let mut r = Rotate::Degrees90;
1119        assert_eq!(Size::new(5, 10), r.rotate_size(Size::new(10, 5)));
1120        r = Rotate::Degrees180;
1121        assert_eq!(Size::new(10, 5), r.rotate_size(Size::new(10, 5)));
1122        r = Rotate::Degrees270;
1123        assert_eq!(Size::new(5, 10), r.rotate_size(Size::new(10, 5)));
1124    }
1125
1126    #[test]
1127    fn test_rotate_rectangle() {
1128        let mut r = Rotate::Degrees90;
1129        let rect = Rectangle::new(Point::new(1, 1), Size::new(3, 2));
1130        // Assume we're rotating _into_ an 8x4 destination buffer.
1131        let _dest_bounds = Size::new(8, 4);
1132        let mut source_bounds = Size::new(4, 8);
1133        let rotated = r.rotate_rectangle(rect, source_bounds);
1134        // (1, 1) in [4, 8] becomes (6, 1) in [8, 4].
1135        // The old bottom left is (1, 2), which becomes (5, 1).
1136        assert_eq!(rotated.top_left, Point::new(5, 1));
1137        assert_eq!(rotated.size, Size::new(2, 3));
1138
1139        r = Rotate::Degrees180;
1140        source_bounds = Size::new(8, 4);
1141        let rotated = r.rotate_rectangle(rect, source_bounds);
1142        // (1, 1) in [8, 4] becomes (6, 2) in [8, 4].
1143        // The old bottom right is (3, 2), which becomes (4, 1).
1144        assert_eq!(rotated.top_left, Point::new(4, 1));
1145        assert_eq!(rotated.size, Size::new(3, 2));
1146
1147        r = Rotate::Degrees270;
1148        source_bounds = Size::new(4, 8);
1149        let rotated = r.rotate_rectangle(rect, source_bounds);
1150        // (1, 1) in [4, 8] becomes (1, 2) in [8, 4].
1151        // The old top right is (3, 1), which becomes (1, 0).
1152        assert_eq!(rotated.top_left, Point::new(1, 0));
1153        assert_eq!(rotated.size, Size::new(2, 3));
1154    }
1155}