1#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3pub enum Rotation {
4 Deg0,
6 Deg90,
8 Deg180,
10 Deg270,
12}
13
14impl Rotation {
15 pub const fn degree(self) -> i32 {
17 match self {
18 Self::Deg0 => 0,
19 Self::Deg90 => 90,
20 Self::Deg180 => 180,
21 Self::Deg270 => 270,
22 }
23 }
24
25 pub const fn try_from_degree(mut angle: i32) -> Result<Self, InvalidAngleError> {
29 if angle < 0 || angle > 270 {
30 angle = angle.rem_euclid(360)
31 }
32
33 Ok(match angle {
34 0 => Self::Deg0,
35 90 => Self::Deg90,
36 180 => Self::Deg180,
37 270 => Self::Deg270,
38 _ => return Err(InvalidAngleError),
39 })
40 }
41
42 #[must_use]
44 pub const fn rotate(self, other: Rotation) -> Self {
45 match Self::try_from_degree(self.degree() + other.degree()) {
46 Ok(r) => r,
47 Err(_) => unreachable!(),
48 }
49 }
50
51 pub const fn is_horizontal(self) -> bool {
53 matches!(self, Self::Deg0 | Self::Deg180)
54 }
55
56 pub const fn is_vertical(self) -> bool {
58 matches!(self, Self::Deg90 | Self::Deg270)
59 }
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
66pub struct InvalidAngleError;
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
98pub struct Orientation {
99 pub rotation: Rotation,
101 pub mirrored: bool,
103}
104
105impl Orientation {
106 pub const fn new() -> Self {
108 Self {
109 rotation: Rotation::Deg0,
110 mirrored: false,
111 }
112 }
113
114 #[must_use]
116 pub const fn rotate(self, rotation: Rotation) -> Self {
117 Self {
118 rotation: self.rotation.rotate(rotation),
119 mirrored: self.mirrored,
120 }
121 }
122
123 #[must_use]
125 const fn flip_horizontal_absolute(self) -> Self {
126 Self {
127 rotation: self.rotation,
128 mirrored: !self.mirrored,
129 }
130 }
131
132 #[must_use]
134 const fn flip_vertical_absolute(self) -> Self {
135 Self {
136 rotation: self.rotation.rotate(Rotation::Deg180),
137 mirrored: !self.mirrored,
138 }
139 }
140
141 #[must_use]
143 pub const fn flip_horizontal(self) -> Self {
144 if self.rotation.is_vertical() {
145 self.flip_vertical_absolute()
146 } else {
147 self.flip_horizontal_absolute()
148 }
149 }
150
151 #[must_use]
153 pub const fn flip_vertical(self) -> Self {
154 if self.rotation.is_vertical() {
155 self.flip_horizontal_absolute()
156 } else {
157 self.flip_vertical_absolute()
158 }
159 }
160}
161
162impl Default for Orientation {
163 fn default() -> Self {
164 Self::new()
165 }
166}
167
168#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
173pub struct MemoryMapping {
174 pub swap_rows_and_columns: bool,
176
177 pub reverse_rows: bool,
179 pub reverse_columns: bool,
181}
182
183impl MemoryMapping {
184 pub const fn from_orientation(orientation: Orientation) -> Self {
186 let (reverse_rows, reverse_columns) = match orientation.rotation {
187 Rotation::Deg0 => (false, false),
188 Rotation::Deg90 => (false, true),
189 Rotation::Deg180 => (true, true),
190 Rotation::Deg270 => (true, false),
191 };
192
193 Self {
194 reverse_rows,
195 reverse_columns: reverse_columns ^ orientation.mirrored,
196 swap_rows_and_columns: orientation.rotation.is_vertical(),
197 }
198 }
199}
200
201impl From<Orientation> for MemoryMapping {
202 fn from(orientation: Orientation) -> Self {
203 Self::from_orientation(orientation)
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn try_from_degree() {
213 let mut expected = [
214 Rotation::Deg0,
215 Rotation::Deg90,
216 Rotation::Deg180,
217 Rotation::Deg270,
218 ]
219 .iter()
220 .copied()
221 .cycle();
222
223 for angle in (-720..=720).step_by(90) {
224 assert_eq!(
225 Rotation::try_from_degree(angle).unwrap(),
226 expected.next().unwrap(),
227 "{angle}"
228 );
229 }
230 }
231
232 #[test]
233 fn try_from_degree_error() {
234 assert_eq!(Rotation::try_from_degree(1), Err(InvalidAngleError));
235 assert_eq!(Rotation::try_from_degree(-1), Err(InvalidAngleError));
236 assert_eq!(Rotation::try_from_degree(i32::MIN), Err(InvalidAngleError));
237 assert_eq!(Rotation::try_from_degree(i32::MAX), Err(InvalidAngleError));
238 }
239
240 const fn orientation(rotation: Rotation, mirrored: bool) -> Orientation {
242 Orientation { rotation, mirrored }
243 }
244
245 #[test]
246 fn flip_horizontal() {
247 use Rotation::*;
248
249 for ((rotation, mirrored), (expected_rotation, expected_mirrored)) in [
250 ((Deg0, false), (Deg0, true)),
251 ((Deg90, false), (Deg270, true)),
252 ((Deg180, false), (Deg180, true)),
253 ((Deg270, false), (Deg90, true)),
254 ((Deg0, true), (Deg0, false)),
255 ((Deg90, true), (Deg270, false)),
256 ((Deg180, true), (Deg180, false)),
257 ((Deg270, true), (Deg90, false)),
258 ]
259 .iter()
260 .copied()
261 {
262 assert_eq!(
263 orientation(rotation, mirrored).flip_horizontal(),
264 orientation(expected_rotation, expected_mirrored)
265 );
266 }
267 }
268
269 #[test]
270 fn flip_vertical() {
271 use Rotation::*;
272
273 for ((rotation, mirrored), (expected_rotation, expected_mirrored)) in [
274 ((Deg0, false), (Deg180, true)),
275 ((Deg90, false), (Deg90, true)),
276 ((Deg180, false), (Deg0, true)),
277 ((Deg270, false), (Deg270, true)),
278 ((Deg0, true), (Deg180, false)),
279 ((Deg90, true), (Deg90, false)),
280 ((Deg180, true), (Deg0, false)),
281 ((Deg270, true), (Deg270, false)),
282 ]
283 .iter()
284 .copied()
285 {
286 assert_eq!(
287 orientation(rotation, mirrored).flip_vertical(),
288 orientation(expected_rotation, expected_mirrored)
289 );
290 }
291 }
292
293 fn draw_memory_mapping(order: MemoryMapping) -> [[u8; 3]; 3] {
294 let mut buffer = [[0u8; 3]; 3];
295
296 let (max_x, max_y) = if order.swap_rows_and_columns {
297 (1, 2)
298 } else {
299 (2, 1)
300 };
301
302 let mut i = 1..;
303 for y in 0..2 {
304 for x in 0..3 {
305 let (x, y) = if order.swap_rows_and_columns {
306 (y, x)
307 } else {
308 (x, y)
309 };
310 let x = if order.reverse_columns { max_x - x } else { x };
311 let y = if order.reverse_rows { max_y - y } else { y };
312
313 buffer[y as usize][x as usize] = i.next().unwrap();
314 }
315 }
316
317 buffer
318 }
319
320 #[test]
321 fn test_draw_memory_mapping() {
322 assert_eq!(
323 &draw_memory_mapping(MemoryMapping {
324 reverse_rows: false,
325 reverse_columns: false,
326 swap_rows_and_columns: false,
327 }),
328 &[
329 [1, 2, 3], [4, 5, 6], [0, 0, 0], ]
333 );
334
335 assert_eq!(
336 &draw_memory_mapping(MemoryMapping {
337 reverse_rows: true,
338 reverse_columns: false,
339 swap_rows_and_columns: false,
340 }),
341 &[
342 [4, 5, 6], [1, 2, 3], [0, 0, 0], ]
346 );
347
348 assert_eq!(
349 &draw_memory_mapping(MemoryMapping {
350 reverse_rows: false,
351 reverse_columns: true,
352 swap_rows_and_columns: false,
353 }),
354 &[
355 [3, 2, 1], [6, 5, 4], [0, 0, 0], ]
359 );
360
361 assert_eq!(
362 &draw_memory_mapping(MemoryMapping {
363 reverse_rows: true,
364 reverse_columns: true,
365 swap_rows_and_columns: false,
366 }),
367 &[
368 [6, 5, 4], [3, 2, 1], [0, 0, 0], ]
372 );
373
374 assert_eq!(
375 &draw_memory_mapping(MemoryMapping {
376 reverse_rows: false,
377 reverse_columns: false,
378 swap_rows_and_columns: true,
379 }),
380 &[
381 [1, 4, 0], [2, 5, 0], [3, 6, 0], ]
385 );
386
387 assert_eq!(
388 &draw_memory_mapping(MemoryMapping {
389 reverse_rows: true,
390 reverse_columns: false,
391 swap_rows_and_columns: true,
392 }),
393 &[
394 [3, 6, 0], [2, 5, 0], [1, 4, 0], ]
398 );
399
400 assert_eq!(
401 &draw_memory_mapping(MemoryMapping {
402 reverse_rows: false,
403 reverse_columns: true,
404 swap_rows_and_columns: true,
405 }),
406 &[
407 [4, 1, 0], [5, 2, 0], [6, 3, 0], ]
411 );
412
413 assert_eq!(
414 &draw_memory_mapping(MemoryMapping {
415 reverse_rows: true,
416 reverse_columns: true,
417 swap_rows_and_columns: true,
418 }),
419 &[
420 [6, 3, 0], [5, 2, 0], [4, 1, 0], ]
424 );
425 }
426
427 #[test]
428 fn into_memory_order_not_mirrored() {
429 assert_eq!(
430 &draw_memory_mapping(orientation(Rotation::Deg0, false).into()),
431 &[
432 [1, 2, 3], [4, 5, 6], [0, 0, 0], ]
436 );
437
438 assert_eq!(
439 &draw_memory_mapping(orientation(Rotation::Deg90, false).into()),
440 &[
441 [4, 1, 0], [5, 2, 0], [6, 3, 0], ]
445 );
446
447 assert_eq!(
448 &draw_memory_mapping(orientation(Rotation::Deg180, false).into()),
449 &[
450 [6, 5, 4], [3, 2, 1], [0, 0, 0], ]
454 );
455
456 assert_eq!(
457 &draw_memory_mapping(orientation(Rotation::Deg270, false).into()),
458 &[
459 [3, 6, 0], [2, 5, 0], [1, 4, 0], ]
463 );
464 }
465
466 #[test]
467 fn into_memory_order_mirrored() {
468 assert_eq!(
469 &draw_memory_mapping(orientation(Rotation::Deg0, true).into()),
470 &[
471 [3, 2, 1], [6, 5, 4], [0, 0, 0], ]
475 );
476
477 assert_eq!(
478 &draw_memory_mapping(orientation(Rotation::Deg90, true).into()),
479 &[
480 [1, 4, 0], [2, 5, 0], [3, 6, 0], ]
484 );
485
486 assert_eq!(
487 &draw_memory_mapping(orientation(Rotation::Deg180, true).into()),
488 &[
489 [4, 5, 6], [1, 2, 3], [0, 0, 0], ]
493 );
494
495 assert_eq!(
496 &draw_memory_mapping(orientation(Rotation::Deg270, true).into()),
497 &[
498 [6, 3, 0], [5, 2, 0], [4, 1, 0], ]
502 );
503 }
504
505 #[test]
506 fn equivalent_orientations() {
507 let o1 = Orientation::new().rotate(Rotation::Deg270).flip_vertical();
508 let o2 = Orientation::new().rotate(Rotation::Deg90).flip_horizontal();
509 let o3 = Orientation::new()
510 .flip_horizontal()
511 .rotate(Rotation::Deg270);
512 let o4 = Orientation::new().flip_vertical().rotate(Rotation::Deg90);
513
514 assert_eq!(o1, o2);
515 assert_eq!(o1, o3);
516 assert_eq!(o1, o4);
517 }
518}