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