Skip to main content

lcd_async/options/
orientation.rs

1/// Display rotation.
2#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3#[cfg_attr(feature = "defmt", derive(defmt::Format))]
4pub enum Rotation {
5    /// No rotation.
6    Deg0,
7    /// 90° clockwise rotation.
8    Deg90,
9    /// 180° clockwise rotation.
10    Deg180,
11    /// 270° clockwise rotation.
12    Deg270,
13}
14
15impl Rotation {
16    /// Returns the rotation in degrees.
17    pub const fn degree(self) -> i32 {
18        match self {
19            Self::Deg0 => 0,
20            Self::Deg90 => 90,
21            Self::Deg180 => 180,
22            Self::Deg270 => 270,
23        }
24    }
25
26    /// Converts an angle into a rotation.
27    ///
28    /// Returns an error if the angle isn't an integer multiple of 90°.
29    pub const fn try_from_degree(mut angle: i32) -> Result<Self, InvalidAngleError> {
30        if angle < 0 || angle > 270 {
31            angle = angle.rem_euclid(360)
32        }
33
34        Ok(match angle {
35            0 => Self::Deg0,
36            90 => Self::Deg90,
37            180 => Self::Deg180,
38            270 => Self::Deg270,
39            _ => return Err(InvalidAngleError),
40        })
41    }
42
43    /// Rotates one rotation by another rotation.
44    #[must_use]
45    pub const fn rotate(self, other: Rotation) -> Self {
46        match Self::try_from_degree(self.degree() + other.degree()) {
47            Ok(r) => r,
48            Err(_) => unreachable!(),
49        }
50    }
51
52    /// Returns `true` if the rotation is horizontal (0° or 180°).
53    pub const fn is_horizontal(self) -> bool {
54        matches!(self, Self::Deg0 | Self::Deg180)
55    }
56
57    /// Returns `true` if the rotation is vertical (90° or 270°).
58    pub const fn is_vertical(self) -> bool {
59        matches!(self, Self::Deg90 | Self::Deg270)
60    }
61}
62
63/// Invalid angle error.
64///
65/// The error type returned by [`Rotation::try_from_degree`].
66#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
67#[cfg_attr(feature = "defmt", derive(defmt::Format))]
68pub struct InvalidAngleError;
69
70/// Display orientation.
71///
72/// A display orientation describes how the display content is oriented relative
73/// to the default orientation of the display.
74///
75/// # Examples
76///
77/// ```
78/// use lcd_async::options::{Orientation, Rotation};
79///
80/// // Rotate display content by 90 degree clockwise.
81/// let rotated = Orientation::new().rotate(Rotation::Deg90);
82///
83/// // Flip display content horizontally.
84/// let flipped = Orientation::new().flip_horizontal();
85/// ```
86///
87/// Multiple transformations can be combined to build more complex orientations:
88///
89/// ```
90/// use lcd_async::options::{Orientation, Rotation};
91///
92/// let orientation = Orientation::new().rotate(Rotation::Deg270).flip_vertical();
93///
94/// // Note that the order of operations is important:
95/// assert_ne!(orientation, Orientation::new().flip_vertical().rotate(Rotation::Deg270));
96/// assert_eq!(orientation, Orientation::new().flip_vertical().rotate(Rotation::Deg90));
97/// ```
98///
99#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
100#[cfg_attr(feature = "defmt", derive(defmt::Format))]
101pub struct Orientation {
102    /// Rotation.
103    pub rotation: Rotation,
104    /// Mirrored.
105    pub mirrored: bool,
106}
107
108impl Orientation {
109    /// Creates a default orientation.
110    pub const fn new() -> Self {
111        Self {
112            rotation: Rotation::Deg0,
113            mirrored: false,
114        }
115    }
116
117    /// Rotates the orientation.
118    #[must_use]
119    pub const fn rotate(self, rotation: Rotation) -> Self {
120        Self {
121            rotation: self.rotation.rotate(rotation),
122            mirrored: self.mirrored,
123        }
124    }
125
126    /// Flips the orientation across the horizontal display axis.
127    #[must_use]
128    const fn flip_horizontal_absolute(self) -> Self {
129        Self {
130            rotation: self.rotation,
131            mirrored: !self.mirrored,
132        }
133    }
134
135    /// Flips the orientation across the vertical display axis.
136    #[must_use]
137    const fn flip_vertical_absolute(self) -> Self {
138        Self {
139            rotation: self.rotation.rotate(Rotation::Deg180),
140            mirrored: !self.mirrored,
141        }
142    }
143
144    /// Flips the orientation across the horizontal axis.
145    #[must_use]
146    pub const fn flip_horizontal(self) -> Self {
147        if self.rotation.is_vertical() {
148            self.flip_vertical_absolute()
149        } else {
150            self.flip_horizontal_absolute()
151        }
152    }
153
154    /// Flips the orientation across the vertical axis.
155    #[must_use]
156    pub const fn flip_vertical(self) -> Self {
157        if self.rotation.is_vertical() {
158            self.flip_horizontal_absolute()
159        } else {
160            self.flip_vertical_absolute()
161        }
162    }
163}
164
165impl Default for Orientation {
166    fn default() -> Self {
167        Self::new()
168    }
169}
170
171/// Memory mapping.
172///
173/// A memory mapping describes how a framebuffer is mapped to the physical
174/// row and columns of a display.
175#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
176#[cfg_attr(feature = "defmt", derive(defmt::Format))]
177pub struct MemoryMapping {
178    /// Rows and columns are swapped.
179    pub swap_rows_and_columns: bool,
180
181    /// Rows are reversed.
182    pub reverse_rows: bool,
183    /// Columns are reversed.
184    pub reverse_columns: bool,
185}
186
187impl MemoryMapping {
188    /// `const` variant of `From<Orientation>` impl.
189    pub const fn from_orientation(orientation: Orientation) -> Self {
190        let (reverse_rows, reverse_columns) = match orientation.rotation {
191            Rotation::Deg0 => (false, false),
192            Rotation::Deg90 => (false, true),
193            Rotation::Deg180 => (true, true),
194            Rotation::Deg270 => (true, false),
195        };
196
197        Self {
198            reverse_rows,
199            reverse_columns: reverse_columns ^ orientation.mirrored,
200            swap_rows_and_columns: orientation.rotation.is_vertical(),
201        }
202    }
203}
204
205impl From<Orientation> for MemoryMapping {
206    fn from(orientation: Orientation) -> Self {
207        Self::from_orientation(orientation)
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use super::*;
214
215    #[test]
216    fn try_from_degree() {
217        let mut expected = [
218            Rotation::Deg0,
219            Rotation::Deg90,
220            Rotation::Deg180,
221            Rotation::Deg270,
222        ]
223        .iter()
224        .copied()
225        .cycle();
226
227        for angle in (-720..=720).step_by(90) {
228            assert_eq!(
229                Rotation::try_from_degree(angle).unwrap(),
230                expected.next().unwrap(),
231                "{angle}"
232            );
233        }
234    }
235
236    #[test]
237    fn try_from_degree_error() {
238        assert_eq!(Rotation::try_from_degree(1), Err(InvalidAngleError));
239        assert_eq!(Rotation::try_from_degree(-1), Err(InvalidAngleError));
240        assert_eq!(Rotation::try_from_degree(i32::MIN), Err(InvalidAngleError));
241        assert_eq!(Rotation::try_from_degree(i32::MAX), Err(InvalidAngleError));
242    }
243
244    /// Abbreviated constructor for orientations.
245    const fn orientation(rotation: Rotation, mirrored: bool) -> Orientation {
246        Orientation { rotation, mirrored }
247    }
248
249    #[test]
250    fn flip_horizontal() {
251        use Rotation::*;
252
253        for ((rotation, mirrored), (expected_rotation, expected_mirrored)) in [
254            ((Deg0, false), (Deg0, true)),
255            ((Deg90, false), (Deg270, true)),
256            ((Deg180, false), (Deg180, true)),
257            ((Deg270, false), (Deg90, true)),
258            ((Deg0, true), (Deg0, false)),
259            ((Deg90, true), (Deg270, false)),
260            ((Deg180, true), (Deg180, false)),
261            ((Deg270, true), (Deg90, false)),
262        ]
263        .iter()
264        .copied()
265        {
266            assert_eq!(
267                orientation(rotation, mirrored).flip_horizontal(),
268                orientation(expected_rotation, expected_mirrored)
269            );
270        }
271    }
272
273    #[test]
274    fn flip_vertical() {
275        use Rotation::*;
276
277        for ((rotation, mirrored), (expected_rotation, expected_mirrored)) in [
278            ((Deg0, false), (Deg180, true)),
279            ((Deg90, false), (Deg90, true)),
280            ((Deg180, false), (Deg0, true)),
281            ((Deg270, false), (Deg270, true)),
282            ((Deg0, true), (Deg180, false)),
283            ((Deg90, true), (Deg90, false)),
284            ((Deg180, true), (Deg0, false)),
285            ((Deg270, true), (Deg270, false)),
286        ]
287        .iter()
288        .copied()
289        {
290            assert_eq!(
291                orientation(rotation, mirrored).flip_vertical(),
292                orientation(expected_rotation, expected_mirrored)
293            );
294        }
295    }
296
297    fn draw_memory_mapping(order: MemoryMapping) -> [[u8; 3]; 3] {
298        let mut buffer = [[0u8; 3]; 3];
299
300        let (max_x, max_y) = if order.swap_rows_and_columns {
301            (1, 2)
302        } else {
303            (2, 1)
304        };
305
306        let mut i = 1..;
307        for y in 0..2 {
308            for x in 0..3 {
309                let (x, y) = if order.swap_rows_and_columns {
310                    (y, x)
311                } else {
312                    (x, y)
313                };
314                let x = if order.reverse_columns { max_x - x } else { x };
315                let y = if order.reverse_rows { max_y - y } else { y };
316
317                buffer[y as usize][x as usize] = i.next().unwrap();
318            }
319        }
320
321        buffer
322    }
323
324    #[test]
325    fn test_draw_memory_mapping() {
326        assert_eq!(
327            &draw_memory_mapping(MemoryMapping {
328                reverse_rows: false,
329                reverse_columns: false,
330                swap_rows_and_columns: false,
331            }),
332            &[
333                [1, 2, 3], //
334                [4, 5, 6], //
335                [0, 0, 0], //
336            ]
337        );
338
339        assert_eq!(
340            &draw_memory_mapping(MemoryMapping {
341                reverse_rows: true,
342                reverse_columns: false,
343                swap_rows_and_columns: false,
344            }),
345            &[
346                [4, 5, 6], //
347                [1, 2, 3], //
348                [0, 0, 0], //
349            ]
350        );
351
352        assert_eq!(
353            &draw_memory_mapping(MemoryMapping {
354                reverse_rows: false,
355                reverse_columns: true,
356                swap_rows_and_columns: false,
357            }),
358            &[
359                [3, 2, 1], //
360                [6, 5, 4], //
361                [0, 0, 0], //
362            ]
363        );
364
365        assert_eq!(
366            &draw_memory_mapping(MemoryMapping {
367                reverse_rows: true,
368                reverse_columns: true,
369                swap_rows_and_columns: false,
370            }),
371            &[
372                [6, 5, 4], //
373                [3, 2, 1], //
374                [0, 0, 0], //
375            ]
376        );
377
378        assert_eq!(
379            &draw_memory_mapping(MemoryMapping {
380                reverse_rows: false,
381                reverse_columns: false,
382                swap_rows_and_columns: true,
383            }),
384            &[
385                [1, 4, 0], //
386                [2, 5, 0], //
387                [3, 6, 0], //
388            ]
389        );
390
391        assert_eq!(
392            &draw_memory_mapping(MemoryMapping {
393                reverse_rows: true,
394                reverse_columns: false,
395                swap_rows_and_columns: true,
396            }),
397            &[
398                [3, 6, 0], //
399                [2, 5, 0], //
400                [1, 4, 0], //
401            ]
402        );
403
404        assert_eq!(
405            &draw_memory_mapping(MemoryMapping {
406                reverse_rows: false,
407                reverse_columns: true,
408                swap_rows_and_columns: true,
409            }),
410            &[
411                [4, 1, 0], //
412                [5, 2, 0], //
413                [6, 3, 0], //
414            ]
415        );
416
417        assert_eq!(
418            &draw_memory_mapping(MemoryMapping {
419                reverse_rows: true,
420                reverse_columns: true,
421                swap_rows_and_columns: true,
422            }),
423            &[
424                [6, 3, 0], //
425                [5, 2, 0], //
426                [4, 1, 0], //
427            ]
428        );
429    }
430
431    #[test]
432    fn into_memory_order_not_mirrored() {
433        assert_eq!(
434            &draw_memory_mapping(orientation(Rotation::Deg0, false).into()),
435            &[
436                [1, 2, 3], //
437                [4, 5, 6], //
438                [0, 0, 0], //
439            ]
440        );
441
442        assert_eq!(
443            &draw_memory_mapping(orientation(Rotation::Deg90, false).into()),
444            &[
445                [4, 1, 0], //
446                [5, 2, 0], //
447                [6, 3, 0], //
448            ]
449        );
450
451        assert_eq!(
452            &draw_memory_mapping(orientation(Rotation::Deg180, false).into()),
453            &[
454                [6, 5, 4], //
455                [3, 2, 1], //
456                [0, 0, 0], //
457            ]
458        );
459
460        assert_eq!(
461            &draw_memory_mapping(orientation(Rotation::Deg270, false).into()),
462            &[
463                [3, 6, 0], //
464                [2, 5, 0], //
465                [1, 4, 0], //
466            ]
467        );
468    }
469
470    #[test]
471    fn into_memory_order_mirrored() {
472        assert_eq!(
473            &draw_memory_mapping(orientation(Rotation::Deg0, true).into()),
474            &[
475                [3, 2, 1], //
476                [6, 5, 4], //
477                [0, 0, 0], //
478            ]
479        );
480
481        assert_eq!(
482            &draw_memory_mapping(orientation(Rotation::Deg90, true).into()),
483            &[
484                [1, 4, 0], //
485                [2, 5, 0], //
486                [3, 6, 0], //
487            ]
488        );
489
490        assert_eq!(
491            &draw_memory_mapping(orientation(Rotation::Deg180, true).into()),
492            &[
493                [4, 5, 6], //
494                [1, 2, 3], //
495                [0, 0, 0], //
496            ]
497        );
498
499        assert_eq!(
500            &draw_memory_mapping(orientation(Rotation::Deg270, true).into()),
501            &[
502                [6, 3, 0], //
503                [5, 2, 0], //
504                [4, 1, 0], //
505            ]
506        );
507    }
508
509    #[test]
510    fn equivalent_orientations() {
511        let o1 = Orientation::new().rotate(Rotation::Deg270).flip_vertical();
512        let o2 = Orientation::new().rotate(Rotation::Deg90).flip_horizontal();
513        let o3 = Orientation::new()
514            .flip_horizontal()
515            .rotate(Rotation::Deg270);
516        let o4 = Orientation::new().flip_vertical().rotate(Rotation::Deg90);
517
518        assert_eq!(o1, o2);
519        assert_eq!(o1, o3);
520        assert_eq!(o1, o4);
521    }
522}