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