1#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3#[cfg_attr(feature = "defmt", derive(defmt::Format))]
4pub enum Rotation {
5 Deg0,
7 Deg90,
9 Deg180,
11 Deg270,
13}
14
15impl Rotation {
16 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 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 #[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 pub const fn is_horizontal(self) -> bool {
54 matches!(self, Self::Deg0 | Self::Deg180)
55 }
56
57 pub const fn is_vertical(self) -> bool {
59 matches!(self, Self::Deg90 | Self::Deg270)
60 }
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
67#[cfg_attr(feature = "defmt", derive(defmt::Format))]
68pub struct InvalidAngleError;
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
100#[cfg_attr(feature = "defmt", derive(defmt::Format))]
101pub struct Orientation {
102 pub rotation: Rotation,
104 pub mirrored: bool,
106}
107
108impl Orientation {
109 pub const fn new() -> Self {
111 Self {
112 rotation: Rotation::Deg0,
113 mirrored: false,
114 }
115 }
116
117 #[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 #[must_use]
128 const fn flip_horizontal_absolute(self) -> Self {
129 Self {
130 rotation: self.rotation,
131 mirrored: !self.mirrored,
132 }
133 }
134
135 #[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 #[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 #[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#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
176#[cfg_attr(feature = "defmt", derive(defmt::Format))]
177pub struct MemoryMapping {
178 pub swap_rows_and_columns: bool,
180
181 pub reverse_rows: bool,
183 pub reverse_columns: bool,
185}
186
187impl MemoryMapping {
188 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 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], [4, 5, 6], [0, 0, 0], ]
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], [1, 2, 3], [0, 0, 0], ]
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], [6, 5, 4], [0, 0, 0], ]
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], [3, 2, 1], [0, 0, 0], ]
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], [2, 5, 0], [3, 6, 0], ]
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], [2, 5, 0], [1, 4, 0], ]
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], [5, 2, 0], [6, 3, 0], ]
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], [5, 2, 0], [4, 1, 0], ]
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], [4, 5, 6], [0, 0, 0], ]
440 );
441
442 assert_eq!(
443 &draw_memory_mapping(orientation(Rotation::Deg90, false).into()),
444 &[
445 [4, 1, 0], [5, 2, 0], [6, 3, 0], ]
449 );
450
451 assert_eq!(
452 &draw_memory_mapping(orientation(Rotation::Deg180, false).into()),
453 &[
454 [6, 5, 4], [3, 2, 1], [0, 0, 0], ]
458 );
459
460 assert_eq!(
461 &draw_memory_mapping(orientation(Rotation::Deg270, false).into()),
462 &[
463 [3, 6, 0], [2, 5, 0], [1, 4, 0], ]
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], [6, 5, 4], [0, 0, 0], ]
479 );
480
481 assert_eq!(
482 &draw_memory_mapping(orientation(Rotation::Deg90, true).into()),
483 &[
484 [1, 4, 0], [2, 5, 0], [3, 6, 0], ]
488 );
489
490 assert_eq!(
491 &draw_memory_mapping(orientation(Rotation::Deg180, true).into()),
492 &[
493 [4, 5, 6], [1, 2, 3], [0, 0, 0], ]
497 );
498
499 assert_eq!(
500 &draw_memory_mapping(orientation(Rotation::Deg270, true).into()),
501 &[
502 [6, 3, 0], [5, 2, 0], [4, 1, 0], ]
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}