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 type.
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 bounds of the given size.
248    fn rotate_point(&self, point: Point, bounds: Size) -> Point;
249
250    /// Rotates a rectangle according to this rotation type, within overall bounds of the given size.
251    fn rotate_rectangle(&self, rectangle: Rectangle, bounds: Size) -> Rectangle;
252}
253
254/// Represents a 90, 180, or 270 degree rotation of a point within a given size.
255#[derive(Clone, Copy, Debug, PartialEq, Eq)]
256pub enum Rotate {
257    R90,
258    R180,
259    R270,
260}
261
262impl Rotation for Rotate {
263    fn inverse(&self) -> Self {
264        match self {
265            Rotate::R90 => Rotate::R270,
266            Rotate::R180 => Rotate::R180,
267            Rotate::R270 => Rotate::R90,
268        }
269    }
270
271    fn rotate_size(&self, size: Size) -> Size {
272        match self {
273            Rotate::R90 | Rotate::R270 => Size::new(size.height, size.width),
274            Rotate::R180 => size,
275        }
276    }
277
278    fn rotate_point(&self, point: Point, source_bounds: Size) -> Point {
279        match self {
280            Rotate::R90 => Point::new(source_bounds.height as i32 - point.y - 1, point.x),
281            Rotate::R180 => Point::new(
282                source_bounds.width as i32 - point.x - 1,
283                source_bounds.height as i32 - point.y - 1,
284            ),
285            Rotate::R270 => Point::new(point.y, source_bounds.width as i32 - point.x - 1),
286        }
287    }
288
289    fn rotate_rectangle(&self, rectangle: Rectangle, source_bounds: Size) -> Rectangle {
290        match self {
291            Rotate::R90 => {
292                let old_bottom_left =
293                    rectangle.top_left + Point::new(0, rectangle.size.height as i32 - 1);
294                let new_top_left = self.rotate_point(old_bottom_left, source_bounds);
295                Rectangle::new(new_top_left, self.rotate_size(rectangle.size))
296            }
297            Rotate::R180 => {
298                let old_bottom_right = rectangle.top_left + rectangle.size - Point::new(1, 1);
299                let new_top_left = self.rotate_point(old_bottom_right, source_bounds);
300                Rectangle::new(new_top_left, self.rotate_size(rectangle.size))
301            }
302            Rotate::R270 => {
303                let old_top_right =
304                    rectangle.top_left + Point::new(rectangle.size.width as i32 - 1, 0);
305                let new_top_left = self.rotate_point(old_top_right, source_bounds);
306                Rectangle::new(new_top_left, self.rotate_size(rectangle.size))
307            }
308        }
309    }
310}
311
312#[derive(Clone)]
313/// Enables arbitrarily rotating an underlying [DrawTarget] buffer. This is useful if the default display
314/// orientation does not match the desired orientation of the content.
315///
316/// ```text
317/// let default_buffer = epd.new_buffer();
318/// // If the default buffer is portrait, this would rotate it so you can draw to it as if it's in landscape mode.
319/// let rotated_buffer = RotatedBuffer::new(default_buffer, Rotate::R90);
320///
321/// // ... Use the buffer here
322///
323/// epd.display_buffer(&mut spi, &rotated_buffer.inner()).await?;
324/// ```
325pub struct RotatedBuffer<B: DrawTarget, R: Rotation> {
326    buffer: B,
327    rotation: R,
328}
329
330impl<B: DrawTarget, R: Rotation> RotatedBuffer<B, R> {
331    pub fn new(buffer: B, rotation: R) -> Self {
332        Self { buffer, rotation }
333    }
334
335    /// Access the inner buffer.
336    pub fn inner(&self) -> &B {
337        &self.buffer
338    }
339}
340
341impl<B: DrawTarget, R: Rotation> Dimensions for RotatedBuffer<B, R> {
342    fn bounding_box(&self) -> Rectangle {
343        let internal = self.buffer.bounding_box();
344        self.rotation
345            .inverse()
346            .rotate_rectangle(internal, internal.size)
347    }
348}
349
350impl<B: DrawTarget, R: Rotation> DrawTarget for RotatedBuffer<B, R> {
351    type Color = B::Color;
352    type Error = B::Error;
353
354    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
355    where
356        I: IntoIterator<Item = Pixel<Self::Color>>,
357    {
358        let source_bounds = self
359            .rotation
360            .inverse()
361            .rotate_size(self.buffer.bounding_box().size);
362        let rotated_pixels = pixels.into_iter().map(|Pixel(point, color)| {
363            let rotated_point = self.rotation.rotate_point(point, source_bounds);
364            Pixel(rotated_point, color)
365        });
366        self.buffer.draw_iter(rotated_pixels)
367    }
368}
369
370#[cfg(test)]
371mod tests {
372    use super::*;
373    use embedded_graphics::pixelcolor::BinaryColor;
374
375    #[test]
376    fn test_binary_buffer_draw_iter_singles() {
377        const SIZE: Size = Size::new(16, 4);
378        const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
379        let mut buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
380
381        // Draw a pixel at the beginning.
382        buffer
383            .draw_iter([Pixel(Point::new(0, 0), BinaryColor::On)])
384            .unwrap();
385        assert_eq!(buffer.data[0], 0b10000000);
386
387        // Draw a pixel in the center.
388        buffer
389            .draw_iter([Pixel(Point::new(10, 2), BinaryColor::On)])
390            .unwrap();
391        assert_eq!(buffer.data[5], 0b00100000);
392
393        // Draw a pixel at the end.
394        buffer
395            .draw_iter([Pixel(Point::new(15, 3), BinaryColor::On)])
396            .unwrap();
397        assert_eq!(buffer.data[7], 0b1);
398    }
399
400    #[test]
401    fn test_binary_buffer_draw_iter_multiple() {
402        const SIZE: Size = Size::new(16, 4);
403        const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
404        let mut buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
405
406        // Draw several pixels in a row.
407        buffer
408            .draw_iter([
409                Pixel(Point::new(1, 0), BinaryColor::On),
410                Pixel(Point::new(2, 0), BinaryColor::On),
411                Pixel(Point::new(3, 0), BinaryColor::On),
412                Pixel(Point::new(2, 0), BinaryColor::Off),
413                Pixel(Point::new(1, 1), BinaryColor::On),
414            ])
415            .unwrap();
416
417        assert_eq!(buffer.data[0], 0b01010000);
418        assert_eq!(buffer.data[2], 0b01000000);
419    }
420
421    #[test]
422    fn test_binary_buffer_draw_iter_out_of_bounds() {
423        const SIZE: Size = Size::new(16, 4);
424        const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
425        let mut buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
426        let previous_data = buffer.data;
427
428        // Draw several pixels in a row.
429        buffer
430            .draw_iter([
431                Pixel(Point::new(-1, 0), BinaryColor::On),
432                Pixel(Point::new(0, -1), BinaryColor::On),
433                Pixel(Point::new(16, 0), BinaryColor::On),
434                Pixel(Point::new(0, 4), BinaryColor::On),
435            ])
436            .unwrap();
437
438        assert_eq!(
439            buffer.data, previous_data,
440            "Data should not change when drawing out-of-bounds pixels."
441        );
442    }
443
444    #[cfg(debug_assertions)]
445    #[test]
446    #[should_panic]
447    fn test_binary_buffer_must_have_aligned_width() {
448        let _ = BinaryBuffer::<16>::new(Size::new(10, 10));
449    }
450
451    #[cfg(debug_assertions)]
452    #[test]
453    #[should_panic]
454    fn test_binary_buffer_size_must_match_dimensions() {
455        let _ = BinaryBuffer::<16>::new(Size::new(16, 10));
456    }
457
458    #[test]
459    fn test_binary_buffer_fill_continguous() {
460        // 8 rows, 1 byte each.
461        const SIZE: Size = Size::new(24, 8);
462        const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
463        let mut buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
464
465        // Draw diagonal squares.
466        buffer
467            .fill_contiguous(
468                &Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
469                [BinaryColor::On; 8 * 8],
470            )
471            .unwrap();
472        buffer
473            .fill_contiguous(
474                // Go out of bounds to ensure it doesn't panic.
475                &Rectangle::new(Point::new(6, 2), Size::new(12, 4)),
476                [BinaryColor::On; 12 * 4],
477            )
478            .unwrap();
479        buffer
480            .fill_contiguous(
481                // Go out of bounds to ensure it doesn't panic.
482                &Rectangle::new(Point::new(20, 4), Size::new(8, 8)),
483                [BinaryColor::On; 8 * 8],
484            )
485            .unwrap();
486
487        #[rustfmt::skip]
488        let expected: [u8; 3 * 8] = [
489            0b11110000, 0b00000000, 0b00000000,
490            0b11110000, 0b00000000, 0b00000000,
491            0b11110011, 0b11111111, 0b11000000,
492            0b11110011, 0b11111111, 0b11000000,
493            0b00000011, 0b11111111, 0b11001111,
494            0b00000011, 0b11111111, 0b11001111,
495            0b00000000, 0b00000000, 0b00001111,
496            0b00000000, 0b00000000, 0b00001111,
497        ];
498        assert_eq!(buffer.data(), &expected);
499    }
500
501    #[test]
502    fn test_binary_buffer_fill_solid() {
503        // 8 rows, 1 byte each.
504        const SIZE: Size = Size::new(24, 8);
505        const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
506        let mut buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
507
508        // Draw diagonal squares.
509        buffer
510            .fill_solid(
511                &Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
512                BinaryColor::On,
513            )
514            .unwrap();
515        buffer
516            .fill_solid(
517                // Go out of bounds to ensure it doesn't panic.
518                &Rectangle::new(Point::new(6, 2), Size::new(12, 4)),
519                BinaryColor::On,
520            )
521            .unwrap();
522        buffer
523            .fill_solid(
524                // Go out of bounds to ensure it doesn't panic.
525                &Rectangle::new(Point::new(20, 4), Size::new(8, 8)),
526                BinaryColor::On,
527            )
528            .unwrap();
529
530        #[rustfmt::skip]
531        let expected: [u8; 3 * 8] = [
532            0b11110000, 0b00000000, 0b00000000,
533            0b11110000, 0b00000000, 0b00000000,
534            0b11110011, 0b11111111, 0b11000000,
535            0b11110011, 0b11111111, 0b11000000,
536            0b00000011, 0b11111111, 0b11001111,
537            0b00000011, 0b11111111, 0b11001111,
538            0b00000000, 0b00000000, 0b00001111,
539            0b00000000, 0b00000000, 0b00001111,
540        ];
541        assert_eq!(buffer.data(), &expected);
542    }
543
544    #[test]
545    fn test_rotated_buffer_bounds() {
546        const SIZE: Size = Size::new(8, 24);
547        const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
548
549        let mut rotated_buffer =
550            RotatedBuffer::new(BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE), Rotate::R90);
551        assert_eq!(
552            rotated_buffer.bounding_box(),
553            Rectangle::new(Point::new(0, 0), Size::new(24, 8))
554        );
555
556        rotated_buffer =
557            RotatedBuffer::new(BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE), Rotate::R180);
558        assert_eq!(
559            rotated_buffer.bounding_box(),
560            Rectangle::new(Point::new(0, 0), Size::new(8, 24))
561        );
562
563        rotated_buffer =
564            RotatedBuffer::new(BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE), Rotate::R270);
565        assert_eq!(
566            rotated_buffer.bounding_box(),
567            Rectangle::new(Point::new(0, 0), Size::new(24, 8))
568        );
569    }
570
571    #[test]
572    fn test_rotated_buffer_draw_iter() {
573        const SIZE: Size = Size::new(8, 4);
574        const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
575
576        let mut rotated_buffer =
577            RotatedBuffer::new(BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE), Rotate::R90);
578        rotated_buffer
579            .draw_iter([
580                Pixel(Point::new(-1, -1), BinaryColor::On), // Should be ignored.
581                Pixel(Point::new(0, 0), BinaryColor::On),
582                Pixel(Point::new(1, 1), BinaryColor::On),
583                Pixel(Point::new(2, 2), BinaryColor::On),
584            ])
585            .unwrap();
586        #[rustfmt::skip]
587        let expected: [u8; 4] = [
588                0b00000001,
589                0b00000010,
590                0b00000100,
591                0b00000000,
592            ];
593        assert_eq!(rotated_buffer.inner().data(), &expected);
594
595        rotated_buffer =
596            RotatedBuffer::new(BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE), Rotate::R180);
597        rotated_buffer
598            .draw_iter([
599                Pixel(Point::new(-1, -1), BinaryColor::On), // Should be ignored.
600                Pixel(Point::new(0, 0), BinaryColor::On),
601                Pixel(Point::new(1, 1), BinaryColor::On),
602                Pixel(Point::new(2, 2), BinaryColor::On),
603            ])
604            .unwrap();
605        #[rustfmt::skip]
606        let expected: [u8; 4] = [
607                0b00000000,
608                0b00000100,
609                0b00000010,
610                0b00000001,
611            ];
612        assert_eq!(rotated_buffer.inner().data(), &expected);
613
614        rotated_buffer =
615            RotatedBuffer::new(BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE), Rotate::R270);
616        rotated_buffer
617            .draw_iter([
618                Pixel(Point::new(-1, -1), BinaryColor::On), // Should be ignored.
619                Pixel(Point::new(0, 0), BinaryColor::On),
620                Pixel(Point::new(1, 1), BinaryColor::On),
621                Pixel(Point::new(2, 2), BinaryColor::On),
622            ])
623            .unwrap();
624        #[rustfmt::skip]
625        let expected: [u8; 4] = [
626                0b00000000,
627                0b00100000,
628                0b01000000,
629                0b10000000,
630            ];
631        assert_eq!(rotated_buffer.inner().data(), &expected);
632    }
633
634    #[test]
635    fn test_rotated_buffer_fill_contiguous() {
636        // 8 rows, 1 byte each.
637        const SIZE: Size = Size::new(8, 6);
638        const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
639        let buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
640
641        let mut rotated_buffer = RotatedBuffer::new(buffer.clone(), Rotate::R90);
642        rotated_buffer
643            .fill_contiguous(
644                &Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
645                [BinaryColor::On; 8 * 8],
646            )
647            .unwrap();
648
649        #[rustfmt::skip]
650        let expected: [u8; 6] = [
651            0b00001111,
652            0b00001111,
653            0b00001111,
654            0b00001111,
655            0b00000000,
656            0b00000000,
657        ];
658        assert_eq!(rotated_buffer.inner().data(), &expected);
659
660        rotated_buffer = RotatedBuffer::new(buffer.clone(), Rotate::R180);
661        rotated_buffer
662            .fill_contiguous(
663                &Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
664                [BinaryColor::On; 8 * 8],
665            )
666            .unwrap();
667
668        #[rustfmt::skip]
669        let expected: [u8; 6] = [
670            0b00000000,
671            0b00000000,
672            0b00001111,
673            0b00001111,
674            0b00001111,
675            0b00001111,
676        ];
677        assert_eq!(rotated_buffer.inner().data(), &expected);
678
679        rotated_buffer = RotatedBuffer::new(buffer.clone(), Rotate::R270);
680        rotated_buffer
681            .fill_contiguous(
682                &Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
683                [BinaryColor::On; 8 * 8],
684            )
685            .unwrap();
686
687        #[rustfmt::skip]
688        let expected: [u8; 6] = [
689            0b00000000,
690            0b00000000,
691            0b11110000,
692            0b11110000,
693            0b11110000,
694            0b11110000,
695        ];
696        assert_eq!(rotated_buffer.inner().data(), &expected);
697    }
698
699    #[test]
700    fn test_rotated_buffer_fill_solid() {
701        // 8 rows, 1 byte each.
702        const SIZE: Size = Size::new(8, 6);
703        const BUFFER_LENGTH: usize = binary_buffer_length(SIZE);
704        let buffer = BinaryBuffer::<{ BUFFER_LENGTH }>::new(SIZE);
705
706        let mut rotated_buffer = RotatedBuffer::new(buffer.clone(), Rotate::R90);
707        rotated_buffer
708            .fill_solid(
709                &Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
710                BinaryColor::On,
711            )
712            .unwrap();
713
714        #[rustfmt::skip]
715        let expected: [u8; 6] = [
716            0b00001111,
717            0b00001111,
718            0b00001111,
719            0b00001111,
720            0b00000000,
721            0b00000000,
722        ];
723        assert_eq!(rotated_buffer.inner().data(), &expected);
724
725        rotated_buffer = RotatedBuffer::new(buffer.clone(), Rotate::R180);
726        rotated_buffer
727            .fill_solid(
728                &Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
729                BinaryColor::On,
730            )
731            .unwrap();
732
733        #[rustfmt::skip]
734        let expected: [u8; 6] = [
735            0b00000000,
736            0b00000000,
737            0b00001111,
738            0b00001111,
739            0b00001111,
740            0b00001111,
741        ];
742        assert_eq!(rotated_buffer.inner().data(), &expected);
743
744        rotated_buffer = RotatedBuffer::new(buffer.clone(), Rotate::R270);
745        rotated_buffer
746            .fill_solid(
747                &Rectangle::new(Point::new(-4, -4), Size::new(8, 8)),
748                BinaryColor::On,
749            )
750            .unwrap();
751
752        #[rustfmt::skip]
753        let expected: [u8; 6] = [
754            0b00000000,
755            0b00000000,
756            0b11110000,
757            0b11110000,
758            0b11110000,
759            0b11110000,
760        ];
761        assert_eq!(rotated_buffer.inner().data(), &expected);
762    }
763
764    #[test]
765    fn test_rotate_near_corner() {
766        let mut r = Rotate::R90;
767        // (1,1) in [10, 20] becomes (18, 1) in [20, 10].
768        assert_eq!(
769            Point::new(18, 1),
770            r.rotate_point(Point::new(1, 1), Size::new(10, 20))
771        );
772        r = Rotate::R180;
773        // (1,1) in [10, 20] becomes (8, 18) in [10, 20].
774        assert_eq!(
775            Point::new(8, 18),
776            r.rotate_point(Point::new(1, 1), Size::new(10, 20))
777        );
778        r = Rotate::R270;
779        // (1,1) in [10, 20] becomes (1, 8) in [20, 10].
780        assert_eq!(
781            Point::new(1, 8),
782            r.rotate_point(Point::new(1, 1), Size::new(10, 20))
783        );
784    }
785
786    #[test]
787    fn test_rotate_centre() {
788        let mut r = Rotate::R90;
789        assert_eq!(
790            Point::new(2, 2),
791            r.rotate_point(Point::new(2, 2), Size::new(5, 5))
792        );
793        r = Rotate::R180;
794        assert_eq!(
795            Point::new(2, 2),
796            r.rotate_point(Point::new(2, 2), Size::new(5, 5))
797        );
798        r = Rotate::R270;
799        assert_eq!(
800            Point::new(2, 2),
801            r.rotate_point(Point::new(2, 2), Size::new(5, 5))
802        );
803    }
804
805    #[test]
806    fn test_rotate_size() {
807        let mut r = Rotate::R90;
808        assert_eq!(Size::new(5, 10), r.rotate_size(Size::new(10, 5)));
809        r = Rotate::R180;
810        assert_eq!(Size::new(10, 5), r.rotate_size(Size::new(10, 5)));
811        r = Rotate::R270;
812        assert_eq!(Size::new(5, 10), r.rotate_size(Size::new(10, 5)));
813    }
814
815    #[test]
816    fn test_rotate_rectangle() {
817        let mut r = Rotate::R90;
818        let rect = Rectangle::new(Point::new(1, 1), Size::new(3, 2));
819        // Assume we're rotating _into_ an 8x4 destination buffer.
820        let _dest_bounds = Size::new(8, 4);
821        let mut source_bounds = Size::new(4, 8);
822        let rotated = r.rotate_rectangle(rect, source_bounds);
823        // (1, 1) in [4, 8] becomes (6, 1) in [8, 4].
824        // The old bottom left is (1, 2), which becomes (5, 1).
825        assert_eq!(rotated.top_left, Point::new(5, 1));
826        assert_eq!(rotated.size, Size::new(2, 3));
827
828        r = Rotate::R180;
829        source_bounds = Size::new(8, 4);
830        let rotated = r.rotate_rectangle(rect, source_bounds);
831        // (1, 1) in [8, 4] becomes (6, 2) in [8, 4].
832        // The old bottom right is (3, 2), which becomes (4, 1).
833        assert_eq!(rotated.top_left, Point::new(4, 1));
834        assert_eq!(rotated.size, Size::new(3, 2));
835
836        r = Rotate::R270;
837        source_bounds = Size::new(4, 8);
838        let rotated = r.rotate_rectangle(rect, source_bounds);
839        // (1, 1) in [4, 8] becomes (1, 2) in [8, 4].
840        // The old top right is (3, 1), which becomes (1, 0).
841        assert_eq!(rotated.top_left, Point::new(1, 0));
842        assert_eq!(rotated.size, Size::new(2, 3));
843    }
844}