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