epd_waveshare_async/
buffer.rs

1use core::{
2    cmp::{max, min},
3    convert::Infallible,
4};
5
6use embedded_graphics::{
7    pixelcolor::BinaryColor,
8    prelude::{Dimensions, DrawTarget, Point, Size},
9    primitives::Rectangle,
10    Pixel,
11};
12
13/// A compact buffer for storing binary coloured display data.
14///
15/// This buffer packs the data such that each byte represents 8 pixels.
16#[derive(Clone)]
17pub struct BinaryBuffer<const L: usize> {
18    size: Size,
19    bytes_per_row: usize,
20    // Data rounds the length of each row up to the next whole byte.
21    data: [u8; L],
22}
23
24/// Computes the correct size for the binary buffer based on the given dimensions.
25pub const fn binary_buffer_length(size: Size) -> usize {
26    (size.width as usize / 8) * size.height as usize
27}
28
29impl<const L: usize> BinaryBuffer<L> {
30    /// Creates a new [BinaryBuffer] with all pixels set to `BinaryColor::Off`.
31    ///
32    /// The dimensions must match the buffer length `L`, and the width must be a multiple of 8.
33    ///
34    /// ```
35    /// use embedded_graphics::prelude::Size;
36    /// use epd_waveshare_async::buffer::{binary_buffer_length, BinaryBuffer};
37    ///
38    /// const DIMENSIONS: Size = Size::new(8, 8);
39    /// let buffer = BinaryBuffer::<{binary_buffer_length(DIMENSIONS)}>::new(DIMENSIONS);
40    /// ```
41    pub fn new(dimensions: Size) -> Self {
42        #[cfg(feature = "defmt")]
43        defmt::debug_assert_eq!(
44            dimensions.width % 8,
45            0,
46            "Width must be a multiple of 8 for binary packing."
47        );
48        #[cfg(not(feature = "defmt"))]
49        debug_assert_eq!(
50            dimensions.width % 8,
51            0,
52            "Width must be a multiple of 8 for binary packing."
53        );
54        #[cfg(feature = "defmt")]
55        defmt::debug_assert_eq!(
56            binary_buffer_length(dimensions),
57            L,
58            "Size must match given dimensions"
59        );
60        #[cfg(not(feature = "defmt"))]
61        debug_assert_eq!(
62            binary_buffer_length(dimensions),
63            L,
64            "Size must match given dimensions"
65        );
66
67        Self {
68            bytes_per_row: dimensions.width as usize / 8,
69            size: dimensions,
70            data: [0; L],
71        }
72    }
73
74    /// Access the packed buffer data.
75    pub fn data(&self) -> &[u8] {
76        &self.data
77    }
78}
79
80impl<const L: usize> Dimensions for BinaryBuffer<L> {
81    fn bounding_box(&self) -> Rectangle {
82        Rectangle::new(Point::zero(), self.size)
83    }
84}
85
86impl<const L: usize> DrawTarget for BinaryBuffer<L> {
87    type Color = BinaryColor;
88
89    type Error = Infallible;
90
91    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
92    where
93        I: IntoIterator<Item = Pixel<Self::Color>>,
94    {
95        // Benchmarking: 60ms for checker pattern in epd2in9 sample program.
96        for Pixel(point, color) in pixels.into_iter() {
97            if point.x < 0
98                || point.x >= self.size.width as i32
99                || point.y < 0
100                || point.y >= self.size.height as i32
101            {
102                continue; // Skip out-of-bounds pixels
103            }
104
105            let byte_index = (point.x as usize) / 8 + (point.y as usize * self.bytes_per_row);
106            let bit_index = (point.x as usize) % 8;
107
108            if color == BinaryColor::On {
109                self.data[byte_index] |= 0x80 >> bit_index;
110            } else {
111                self.data[byte_index] &= !(0x80 >> bit_index);
112            }
113        }
114        Ok(())
115    }
116
117    fn fill_contiguous<I>(&mut self, area: &Rectangle, colors: I) -> Result<(), Self::Error>
118    where
119        I: IntoIterator<Item = Self::Color>,
120    {
121        // Benchmarking: 39ms for checker pattern in epd2in9 sample program.
122        {
123            let drawable_area = self.bounding_box().intersection(area);
124            if drawable_area.size.width == 0 || drawable_area.size.height == 0 {
125                return Ok(()); // Nothing to fill
126            }
127        }
128
129        let y_start = area.top_left.y;
130        let y_end = area.top_left.y + area.size.height as i32;
131        let x_start = area.top_left.x;
132        let x_end = area.top_left.x + area.size.width as i32;
133
134        let mut colors_iter = colors.into_iter();
135        let mut byte_index = max(y_start, 0) as usize * self.bytes_per_row;
136        let row_start_byte_offset = max(x_start, 0) as usize / 8;
137        let row_end_byte_offset =
138            self.bytes_per_row - (min(x_end, self.size.width as i32) as usize / 8);
139        for y in y_start..y_end {
140            if y < 0 || y >= self.size.height as i32 {
141                // Skip out-of-bounds rows
142                for _ in x_start..x_end {
143                    colors_iter.next();
144                }
145                continue;
146            }
147
148            byte_index += row_start_byte_offset;
149            let mut bit_index = (max(x_start, 0) as usize) % 8;
150
151            // Y is within bounds, check X.
152            for x in x_start..x_end {
153                if x < 0 || x >= self.size.width as i32 {
154                    // Skip out-of-bounds pixels
155                    colors_iter.next();
156                    continue;
157                }
158
159                // Exit if there are no more colors to apply.
160                let Some(color) = colors_iter.next() else {
161                    return Ok(());
162                };
163
164                if color == BinaryColor::On {
165                    self.data[byte_index] |= 0x80 >> bit_index;
166                } else {
167                    self.data[byte_index] &= !(0x80 >> bit_index);
168                }
169
170                bit_index += 1;
171                if bit_index == 8 {
172                    // Move to the next byte after every 8 pixels
173                    byte_index += 1;
174                    bit_index = 0;
175                }
176            }
177
178            byte_index += row_end_byte_offset;
179        }
180
181        Ok(())
182    }
183
184    fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> {
185        // Benchmarking: 3ms for checker pattern in epd2in9 sample program.
186        let drawable_area = self.bounding_box().intersection(area);
187        if drawable_area.size.width == 0 || drawable_area.size.height == 0 {
188            return Ok(()); // Nothing to fill
189        }
190
191        let y_start = drawable_area.top_left.y;
192        let y_end = drawable_area.top_left.y + drawable_area.size.height as i32;
193        let x_start = drawable_area.top_left.x;
194        let x_end = drawable_area.top_left.x + drawable_area.size.width as i32;
195
196        let x_full_bytes_start = min(x_start + x_start % 8, x_end);
197        let x_full_bytes_end = max(x_end - (x_end % 8), x_start);
198        let num_full_bytes_per_row = (x_full_bytes_end - x_full_bytes_start) / 8;
199
200        let mut byte_index = y_start as usize * self.bytes_per_row;
201        let row_start_byte_offset = x_start as usize / 8;
202        let row_end_byte_offset = self.bytes_per_row - (x_end as usize / 8);
203        for _y in y_start..y_end {
204            byte_index += row_start_byte_offset;
205            let mut bit_index = (x_start as usize) % 8;
206
207            /// Sets the next bit from `color` and advances `bit_index` and `byte_index`
208            /// appropriately.
209            macro_rules! set_next_bit {
210                () => {
211                    if color == BinaryColor::On {
212                        self.data[byte_index] |= 0x80 >> bit_index;
213                    } else {
214                        self.data[byte_index] &= !(0x80 >> bit_index);
215                    }
216                    bit_index += 1;
217                    if bit_index == 8 {
218                        // Move to the next byte after every 8 pixels
219                        byte_index += 1;
220                        bit_index = 0;
221                    }
222                };
223            }
224
225            if num_full_bytes_per_row == 0 {
226                // There are no full bytes in this row, so just set colors bitwise.
227                for _x in x_start..x_end {
228                    set_next_bit!();
229                }
230            } else {
231                // Set colors bitwise in the first byte if it's not byte-aligned.
232                for _x in x_start..x_full_bytes_start {
233                    set_next_bit!();
234                }
235
236                // Fast fill for any fully covered bytes in the row.
237                for _ in 0..num_full_bytes_per_row {
238                    if color == BinaryColor::On {
239                        self.data[byte_index] = 0xFF;
240                    } else {
241                        self.data[byte_index] = 0x00;
242                    }
243                    byte_index += 1;
244                }
245
246                // Set the partially covered byte at the end of the row, if any.
247                bit_index = x_full_bytes_end as usize % 8;
248                for _x in x_full_bytes_end..x_end {
249                    set_next_bit!();
250                }
251            }
252
253            byte_index += row_end_byte_offset;
254        }
255
256        Ok(())
257    }
258}
259
260pub trait Rotation {
261    /// Returns the inverse rotation that reverses this rotation's effect.
262    fn inverse(&self) -> Self;
263
264    /// Rotates the given size according to this rotation type.
265    fn rotate_size(&self, size: Size) -> Size;
266
267    /// Rotates a point according to this rotation type, within overall source bounds of the given size.
268    ///
269    /// For example, if the given `point` is (1,2) from a 10x20 space, then [Rotate::Degrees90] would
270    /// return (17, 1) in a 20x10 space. `bounds` should be the source dimensions of 10x20.
271    ///
272    /// ```rust
273    /// # use embedded_graphics::prelude::{Point, Size};
274    /// # use epd_waveshare_async::buffer::{Rotate, Rotation};
275    ///
276    /// let r = Rotate::Degrees90;
277    /// assert_eq!(r.rotate_point(Point::new(1, 2), Size::new(10, 20)), Point::new(17, 1));
278    /// ```
279    fn rotate_point(&self, point: Point, bounds: Size) -> Point;
280
281    /// Rotates a rectangle according to this rotation type, within overall source bounds of the given size.
282    fn rotate_rectangle(&self, rectangle: Rectangle, bounds: Size) -> Rectangle;
283}
284
285/// Represents a 90, 180, or 270 degree clockwise rotation of a point within a given size.
286#[derive(Clone, Copy, Debug, PartialEq, Eq)]
287pub enum Rotate {
288    Degrees90,
289    Degrees180,
290    Degrees270,
291}
292
293impl Rotation for Rotate {
294    fn inverse(&self) -> Self {
295        match self {
296            Rotate::Degrees90 => Rotate::Degrees270,
297            Rotate::Degrees180 => Rotate::Degrees180,
298            Rotate::Degrees270 => Rotate::Degrees90,
299        }
300    }
301
302    fn rotate_size(&self, size: Size) -> Size {
303        match self {
304            Rotate::Degrees90 | Rotate::Degrees270 => Size::new(size.height, size.width),
305            Rotate::Degrees180 => size,
306        }
307    }
308
309    fn rotate_point(&self, point: Point, source_bounds: Size) -> Point {
310        match self {
311            Rotate::Degrees90 => Point::new(source_bounds.height as i32 - point.y - 1, point.x),
312            Rotate::Degrees180 => Point::new(
313                source_bounds.width as i32 - point.x - 1,
314                source_bounds.height as i32 - point.y - 1,
315            ),
316            Rotate::Degrees270 => Point::new(point.y, source_bounds.width as i32 - point.x - 1),
317        }
318    }
319
320    fn rotate_rectangle(&self, rectangle: Rectangle, source_bounds: Size) -> Rectangle {
321        match self {
322            Rotate::Degrees90 => {
323                let old_bottom_left =
324                    rectangle.top_left + Point::new(0, rectangle.size.height as i32 - 1);
325                let new_top_left = self.rotate_point(old_bottom_left, source_bounds);
326                Rectangle::new(new_top_left, self.rotate_size(rectangle.size))
327            }
328            Rotate::Degrees180 => {
329                let old_bottom_right = rectangle.top_left + rectangle.size - Point::new(1, 1);
330                let new_top_left = self.rotate_point(old_bottom_right, source_bounds);
331                Rectangle::new(new_top_left, self.rotate_size(rectangle.size))
332            }
333            Rotate::Degrees270 => {
334                let old_top_right =
335                    rectangle.top_left + Point::new(rectangle.size.width as i32 - 1, 0);
336                let new_top_left = self.rotate_point(old_top_right, source_bounds);
337                Rectangle::new(new_top_left, self.rotate_size(rectangle.size))
338            }
339        }
340    }
341}
342
343/// Enables arbitrarily rotating an underlying [DrawTarget] buffer. This is useful if the default display
344/// orientation does not match the desired orientation of the content.
345///
346/// ```text
347/// let mut default_buffer = epd.new_buffer();
348/// // If the default buffer is portrait, this would rotate it so you can draw to it as if it's in landscape mode.
349/// let rotated_buffer = RotatedBuffer::new(&mut default_buffer, Rotate::Degrees90);
350///
351/// // ... Use the buffer here
352///
353/// epd.display_buffer(&mut spi, rotated_buffer.inner()).await?;
354/// ```
355pub struct RotatedBuffer<B: DrawTarget, R: Rotation> {
356    bounds: Rectangle,
357    buffer: B,
358    rotation: R,
359}
360
361impl<B: DrawTarget, R: Rotation> RotatedBuffer<B, R> {
362    pub fn new(buffer: B, rotation: R) -> Self {
363        let inverse_rotation = rotation.inverse();
364        let inner_bounds = buffer.bounding_box();
365        let bounds = inverse_rotation.rotate_rectangle(inner_bounds, inner_bounds.size);
366        Self {
367            bounds,
368            buffer,
369            rotation,
370        }
371    }
372
373    /// Provides read-only access to the inner buffer.
374    pub fn inner(&mut self) -> &B {
375        &self.buffer
376    }
377
378    /// Drops this rotated buffer wrapper and takes out the inner buffer.
379    pub fn take_inner(self) -> B {
380        self.buffer
381    }
382}
383
384impl<B: DrawTarget, R: Rotation> Dimensions for RotatedBuffer<B, R> {
385    fn bounding_box(&self) -> Rectangle {
386        self.bounds
387    }
388}
389
390impl<B: DrawTarget, R: Rotation> DrawTarget for RotatedBuffer<B, R> {
391    type Color = B::Color;
392    type Error = B::Error;
393
394    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
395    where
396        I: IntoIterator<Item = Pixel<Self::Color>>,
397    {
398        let rotated_pixels = pixels.into_iter().map(|Pixel(point, color)| {
399            let rotated_point = self.rotation.rotate_point(point, self.bounds.size);
400            Pixel(rotated_point, color)
401        });
402        self.buffer.draw_iter(rotated_pixels)
403    }
404}
405
406#[cfg(test)]
407mod tests {
408    use super::*;
409    use embedded_graphics::pixelcolor::BinaryColor;
410
411    #[test]
412    fn test_binary_buffer_draw_iter_singles() {
413        const SIZE: Size = Size::new(16, 4);
414        const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
415        let mut buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
416
417        // Draw a pixel at the beginning.
418        buffer
419            .draw_iter([Pixel(Point::new(0, 0), BinaryColor::On)])
420            .unwrap();
421        assert_eq!(buffer.data[0], 0b10000000);
422
423        // Draw a pixel in the center.
424        buffer
425            .draw_iter([Pixel(Point::new(10, 2), BinaryColor::On)])
426            .unwrap();
427        assert_eq!(buffer.data[5], 0b00100000);
428
429        // Draw a pixel at the end.
430        buffer
431            .draw_iter([Pixel(Point::new(15, 3), BinaryColor::On)])
432            .unwrap();
433        assert_eq!(buffer.data[7], 0b1);
434    }
435
436    #[test]
437    fn test_binary_buffer_draw_iter_multiple() {
438        const SIZE: Size = Size::new(16, 4);
439        const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
440        let mut buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
441
442        // Draw several pixels in a row.
443        buffer
444            .draw_iter([
445                Pixel(Point::new(1, 0), BinaryColor::On),
446                Pixel(Point::new(2, 0), BinaryColor::On),
447                Pixel(Point::new(3, 0), BinaryColor::On),
448                Pixel(Point::new(2, 0), BinaryColor::Off),
449                Pixel(Point::new(1, 1), BinaryColor::On),
450            ])
451            .unwrap();
452
453        assert_eq!(buffer.data[0], 0b01010000);
454        assert_eq!(buffer.data[2], 0b01000000);
455    }
456
457    #[test]
458    fn test_binary_buffer_draw_iter_out_of_bounds() {
459        const SIZE: Size = Size::new(16, 4);
460        const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
461        let mut buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
462        let previous_data = buffer.data;
463
464        // Draw several pixels in a row.
465        buffer
466            .draw_iter([
467                Pixel(Point::new(-1, 0), BinaryColor::On),
468                Pixel(Point::new(0, -1), BinaryColor::On),
469                Pixel(Point::new(16, 0), BinaryColor::On),
470                Pixel(Point::new(0, 4), BinaryColor::On),
471            ])
472            .unwrap();
473
474        assert_eq!(
475            buffer.data, previous_data,
476            "Data should not change when drawing out-of-bounds pixels."
477        );
478    }
479
480    #[cfg(debug_assertions)]
481    #[test]
482    #[should_panic]
483    fn test_binary_buffer_must_have_aligned_width() {
484        let _ = BinaryBuffer::<16>::new(Size::new(10, 10));
485    }
486
487    #[cfg(debug_assertions)]
488    #[test]
489    #[should_panic]
490    fn test_binary_buffer_size_must_match_dimensions() {
491        let _ = BinaryBuffer::<16>::new(Size::new(16, 10));
492    }
493
494    #[test]
495    fn test_binary_buffer_fill_continguous() {
496        // 8 rows, 1 byte each.
497        const SIZE: Size = Size::new(24, 8);
498        const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
499        let mut buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
500
501        // Draw diagonal squares.
502        buffer
503            .fill_contiguous(
504                &Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
505                [BinaryColor::On; 8 * 8],
506            )
507            .unwrap();
508        buffer
509            .fill_contiguous(
510                // Go out of bounds to ensure it doesn't panic.
511                &Rectangle::new(Point::new(6, 2), Size::new(12, 4)),
512                [BinaryColor::On; 12 * 4],
513            )
514            .unwrap();
515        buffer
516            .fill_contiguous(
517                // Go out of bounds to ensure it doesn't panic.
518                &Rectangle::new(Point::new(20, 4), Size::new(8, 8)),
519                [BinaryColor::On; 8 * 8],
520            )
521            .unwrap();
522
523        #[rustfmt::skip]
524        let expected: [u8; 3 * 8] = [
525            0b11110000, 0b00000000, 0b00000000,
526            0b11110000, 0b00000000, 0b00000000,
527            0b11110011, 0b11111111, 0b11000000,
528            0b11110011, 0b11111111, 0b11000000,
529            0b00000011, 0b11111111, 0b11001111,
530            0b00000011, 0b11111111, 0b11001111,
531            0b00000000, 0b00000000, 0b00001111,
532            0b00000000, 0b00000000, 0b00001111,
533        ];
534        assert_eq!(buffer.data(), &expected);
535    }
536
537    #[test]
538    fn test_binary_buffer_fill_solid() {
539        // 8 rows, 1 byte each.
540        const SIZE: Size = Size::new(24, 8);
541        const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
542        let mut buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
543
544        // Draw diagonal squares.
545        buffer
546            .fill_solid(
547                &Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
548                BinaryColor::On,
549            )
550            .unwrap();
551        buffer
552            .fill_solid(
553                // Go out of bounds to ensure it doesn't panic.
554                &Rectangle::new(Point::new(6, 2), Size::new(12, 4)),
555                BinaryColor::On,
556            )
557            .unwrap();
558        buffer
559            .fill_solid(
560                // Go out of bounds to ensure it doesn't panic.
561                &Rectangle::new(Point::new(20, 4), Size::new(8, 8)),
562                BinaryColor::On,
563            )
564            .unwrap();
565
566        #[rustfmt::skip]
567        let expected: [u8; 3 * 8] = [
568            0b11110000, 0b00000000, 0b00000000,
569            0b11110000, 0b00000000, 0b00000000,
570            0b11110011, 0b11111111, 0b11000000,
571            0b11110011, 0b11111111, 0b11000000,
572            0b00000011, 0b11111111, 0b11001111,
573            0b00000011, 0b11111111, 0b11001111,
574            0b00000000, 0b00000000, 0b00001111,
575            0b00000000, 0b00000000, 0b00001111,
576        ];
577        assert_eq!(buffer.data(), &expected);
578    }
579
580    #[test]
581    fn test_rotated_buffer_bounds() {
582        const SIZE: Size = Size::new(8, 24);
583        const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
584
585        let mut rotated_buffer = RotatedBuffer::new(
586            BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE),
587            Rotate::Degrees90,
588        );
589        assert_eq!(
590            rotated_buffer.bounding_box(),
591            Rectangle::new(Point::new(0, 0), Size::new(24, 8))
592        );
593
594        rotated_buffer = RotatedBuffer::new(
595            BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE),
596            Rotate::Degrees180,
597        );
598        assert_eq!(
599            rotated_buffer.bounding_box(),
600            Rectangle::new(Point::new(0, 0), Size::new(8, 24))
601        );
602
603        rotated_buffer = RotatedBuffer::new(
604            BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE),
605            Rotate::Degrees270,
606        );
607        assert_eq!(
608            rotated_buffer.bounding_box(),
609            Rectangle::new(Point::new(0, 0), Size::new(24, 8))
610        );
611    }
612
613    #[test]
614    fn test_rotated_buffer_draw_iter() {
615        const SIZE: Size = Size::new(8, 4);
616        const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
617
618        let mut rotated_buffer = RotatedBuffer::new(
619            BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE),
620            Rotate::Degrees90,
621        );
622        rotated_buffer
623            .draw_iter([
624                Pixel(Point::new(-1, -1), BinaryColor::On), // Should be ignored.
625                Pixel(Point::new(0, 0), BinaryColor::On),
626                Pixel(Point::new(1, 1), BinaryColor::On),
627                Pixel(Point::new(2, 2), BinaryColor::On),
628            ])
629            .unwrap();
630        #[rustfmt::skip]
631        let expected: [u8; 4] = [
632                0b00000001,
633                0b00000010,
634                0b00000100,
635                0b00000000,
636            ];
637        assert_eq!(rotated_buffer.inner().data(), &expected);
638
639        rotated_buffer = RotatedBuffer::new(
640            BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE),
641            Rotate::Degrees180,
642        );
643        rotated_buffer
644            .draw_iter([
645                Pixel(Point::new(-1, -1), BinaryColor::On), // Should be ignored.
646                Pixel(Point::new(0, 0), BinaryColor::On),
647                Pixel(Point::new(1, 1), BinaryColor::On),
648                Pixel(Point::new(2, 2), BinaryColor::On),
649            ])
650            .unwrap();
651        #[rustfmt::skip]
652        let expected: [u8; 4] = [
653                0b00000000,
654                0b00000100,
655                0b00000010,
656                0b00000001,
657            ];
658        assert_eq!(rotated_buffer.inner().data(), &expected);
659
660        rotated_buffer = RotatedBuffer::new(
661            BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE),
662            Rotate::Degrees270,
663        );
664        rotated_buffer
665            .draw_iter([
666                Pixel(Point::new(-1, -1), BinaryColor::On), // Should be ignored.
667                Pixel(Point::new(0, 0), BinaryColor::On),
668                Pixel(Point::new(1, 1), BinaryColor::On),
669                Pixel(Point::new(2, 2), BinaryColor::On),
670            ])
671            .unwrap();
672        #[rustfmt::skip]
673        let expected: [u8; 4] = [
674                0b00000000,
675                0b00100000,
676                0b01000000,
677                0b10000000,
678            ];
679        assert_eq!(rotated_buffer.inner().data(), &expected);
680    }
681
682    #[test]
683    fn test_rotated_buffer_fill_contiguous() {
684        // 8 rows, 1 byte each.
685        const SIZE: Size = Size::new(8, 6);
686        const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
687        let buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
688
689        let mut rotated_buffer = RotatedBuffer::new(buffer.clone(), Rotate::Degrees90);
690        rotated_buffer
691            .fill_contiguous(
692                &Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
693                [BinaryColor::On; 8 * 8],
694            )
695            .unwrap();
696
697        #[rustfmt::skip]
698        let expected: [u8; 6] = [
699            0b00001111,
700            0b00001111,
701            0b00001111,
702            0b00001111,
703            0b00000000,
704            0b00000000,
705        ];
706        assert_eq!(rotated_buffer.inner().data(), &expected);
707
708        rotated_buffer = RotatedBuffer::new(buffer.clone(), Rotate::Degrees180);
709        rotated_buffer
710            .fill_contiguous(
711                &Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
712                [BinaryColor::On; 8 * 8],
713            )
714            .unwrap();
715
716        #[rustfmt::skip]
717        let expected: [u8; 6] = [
718            0b00000000,
719            0b00000000,
720            0b00001111,
721            0b00001111,
722            0b00001111,
723            0b00001111,
724        ];
725        assert_eq!(rotated_buffer.inner().data(), &expected);
726
727        rotated_buffer = RotatedBuffer::new(buffer.clone(), Rotate::Degrees270);
728        rotated_buffer
729            .fill_contiguous(
730                &Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
731                [BinaryColor::On; 8 * 8],
732            )
733            .unwrap();
734
735        #[rustfmt::skip]
736        let expected: [u8; 6] = [
737            0b00000000,
738            0b00000000,
739            0b11110000,
740            0b11110000,
741            0b11110000,
742            0b11110000,
743        ];
744        assert_eq!(rotated_buffer.inner().data(), &expected);
745    }
746
747    #[test]
748    fn test_rotated_buffer_fill_solid() {
749        // 8 rows, 1 byte each.
750        const SIZE: Size = Size::new(8, 6);
751        const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
752        let buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
753
754        let mut rotated_buffer = RotatedBuffer::new(buffer.clone(), Rotate::Degrees90);
755        rotated_buffer
756            .fill_solid(
757                &Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
758                BinaryColor::On,
759            )
760            .unwrap();
761
762        #[rustfmt::skip]
763        let expected: [u8; 6] = [
764            0b00001111,
765            0b00001111,
766            0b00001111,
767            0b00001111,
768            0b00000000,
769            0b00000000,
770        ];
771        assert_eq!(rotated_buffer.inner().data(), &expected);
772
773        rotated_buffer = RotatedBuffer::new(buffer.clone(), Rotate::Degrees180);
774        rotated_buffer
775            .fill_solid(
776                &Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
777                BinaryColor::On,
778            )
779            .unwrap();
780
781        #[rustfmt::skip]
782        let expected: [u8; 6] = [
783            0b00000000,
784            0b00000000,
785            0b00001111,
786            0b00001111,
787            0b00001111,
788            0b00001111,
789        ];
790        assert_eq!(rotated_buffer.inner().data(), &expected);
791
792        rotated_buffer = RotatedBuffer::new(buffer.clone(), Rotate::Degrees270);
793        rotated_buffer
794            .fill_solid(
795                &Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
796                BinaryColor::On,
797            )
798            .unwrap();
799
800        #[rustfmt::skip]
801        let expected: [u8; 6] = [
802            0b00000000,
803            0b00000000,
804            0b11110000,
805            0b11110000,
806            0b11110000,
807            0b11110000,
808        ];
809        assert_eq!(rotated_buffer.inner().data(), &expected);
810    }
811
812    #[test]
813    fn test_rotate_near_corner() {
814        let mut r = Rotate::Degrees90;
815        // (1,1) in [10, 20] becomes (18, 1) in [20, 10].
816        assert_eq!(
817            Point::new(18, 1),
818            r.rotate_point(Point::new(1, 1), Size::new(10, 20))
819        );
820        r = Rotate::Degrees180;
821        // (1,1) in [10, 20] becomes (8, 18) in [10, 20].
822        assert_eq!(
823            Point::new(8, 18),
824            r.rotate_point(Point::new(1, 1), Size::new(10, 20))
825        );
826        r = Rotate::Degrees270;
827        // (1,1) in [10, 20] becomes (1, 8) in [20, 10].
828        assert_eq!(
829            Point::new(1, 8),
830            r.rotate_point(Point::new(1, 1), Size::new(10, 20))
831        );
832    }
833
834    #[test]
835    fn test_rotate_centre() {
836        let mut r = Rotate::Degrees90;
837        assert_eq!(
838            Point::new(2, 2),
839            r.rotate_point(Point::new(2, 2), Size::new(5, 5))
840        );
841        r = Rotate::Degrees180;
842        assert_eq!(
843            Point::new(2, 2),
844            r.rotate_point(Point::new(2, 2), Size::new(5, 5))
845        );
846        r = Rotate::Degrees270;
847        assert_eq!(
848            Point::new(2, 2),
849            r.rotate_point(Point::new(2, 2), Size::new(5, 5))
850        );
851    }
852
853    #[test]
854    fn test_rotate_size() {
855        let mut r = Rotate::Degrees90;
856        assert_eq!(Size::new(5, 10), r.rotate_size(Size::new(10, 5)));
857        r = Rotate::Degrees180;
858        assert_eq!(Size::new(10, 5), r.rotate_size(Size::new(10, 5)));
859        r = Rotate::Degrees270;
860        assert_eq!(Size::new(5, 10), r.rotate_size(Size::new(10, 5)));
861    }
862
863    #[test]
864    fn test_rotate_rectangle() {
865        let mut r = Rotate::Degrees90;
866        let rect = Rectangle::new(Point::new(1, 1), Size::new(3, 2));
867        // Assume we're rotating _into_ an 8x4 destination buffer.
868        let _dest_bounds = Size::new(8, 4);
869        let mut source_bounds = Size::new(4, 8);
870        let rotated = r.rotate_rectangle(rect, source_bounds);
871        // (1, 1) in [4, 8] becomes (6, 1) in [8, 4].
872        // The old bottom left is (1, 2), which becomes (5, 1).
873        assert_eq!(rotated.top_left, Point::new(5, 1));
874        assert_eq!(rotated.size, Size::new(2, 3));
875
876        r = Rotate::Degrees180;
877        source_bounds = Size::new(8, 4);
878        let rotated = r.rotate_rectangle(rect, source_bounds);
879        // (1, 1) in [8, 4] becomes (6, 2) in [8, 4].
880        // The old bottom right is (3, 2), which becomes (4, 1).
881        assert_eq!(rotated.top_left, Point::new(4, 1));
882        assert_eq!(rotated.size, Size::new(3, 2));
883
884        r = Rotate::Degrees270;
885        source_bounds = Size::new(4, 8);
886        let rotated = r.rotate_rectangle(rect, source_bounds);
887        // (1, 1) in [4, 8] becomes (1, 2) in [8, 4].
888        // The old top right is (3, 1), which becomes (1, 0).
889        assert_eq!(rotated.top_left, Point::new(1, 0));
890        assert_eq!(rotated.size, Size::new(2, 3));
891    }
892}