gufo_common/
orientation.rs

1use std::ops::{Add, Sub};
2
3crate::utils::maybe_convertible_enum!(
4    #[repr(u16)]
5    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
6    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
7    /// Operations that have to be applied to orient the image correctly
8    ///
9    /// Rotations are counter-clockwise
10    pub enum Orientation {
11        Id = 1,
12        Rotation90 = 8,
13        Rotation180 = 3,
14        Rotation270 = 6,
15        Mirrored = 2,
16        MirroredRotation90 = 5,
17        MirroredRotation180 = 4,
18        MirroredRotation270 = 7,
19    }
20);
21
22impl Orientation {
23    pub fn new(mirrored: bool, rotation: Rotation) -> Self {
24        match (mirrored, rotation) {
25            (false, Rotation::_0) => Self::Id,
26            (false, Rotation::_90) => Self::Rotation90,
27            (false, Rotation::_180) => Self::Rotation180,
28            (false, Rotation::_270) => Self::Rotation270,
29            (true, Rotation::_0) => Self::Mirrored,
30            (true, Rotation::_90) => Self::MirroredRotation90,
31            (true, Rotation::_180) => Self::MirroredRotation180,
32            (true, Rotation::_270) => Self::MirroredRotation270,
33        }
34    }
35
36    /// Combine two orientation changes to one
37    ///
38    /// The `orientation` is applied after `self`
39    #[must_use]
40    pub fn combine(self, orientation: Orientation) -> Orientation {
41        let mut new_orientation = self;
42        if orientation.mirror() {
43            new_orientation = new_orientation.add_mirror_horizontally()
44        }
45        new_orientation = new_orientation.add_rotation(orientation.rotate());
46
47        new_orientation
48    }
49
50    #[must_use]
51    pub fn add_mirror_horizontally(self) -> Orientation {
52        Self::new(!self.mirror(), Rotation::_0 - self.rotate())
53    }
54
55    #[must_use]
56    pub fn add_mirror_vertically(self) -> Orientation {
57        Self::new(!self.mirror(), self.rotate() + Rotation::_180)
58    }
59
60    #[must_use]
61    pub fn add_rotation(self, rotation: Rotation) -> Orientation {
62        Self::new(self.mirror(), self.rotate() + rotation)
63    }
64}
65
66#[derive(Debug)]
67pub struct UnknownOrientation;
68
69#[allow(dead_code)]
70#[derive(Debug)]
71/// Rotation was not given in multiples of 90
72pub struct InvalidRotation(f64);
73
74/// Counter-clockwise rotation
75#[derive(Clone, Copy, Debug, PartialEq, Eq)]
76#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
77pub enum Rotation {
78    _0,
79    _90,
80    _180,
81    _270,
82}
83
84impl Rotation {
85    pub fn degrees(self) -> u16 {
86        match self {
87            Rotation::_0 => 0,
88            Rotation::_90 => 90,
89            Rotation::_180 => 180,
90            Rotation::_270 => 270,
91        }
92    }
93}
94
95impl Add for Rotation {
96    type Output = Self;
97    fn add(self, rhs: Self) -> Self::Output {
98        Rotation::try_from((self.degrees().checked_add(rhs.degrees()).unwrap()) as f64).unwrap()
99    }
100}
101
102impl Sub for Rotation {
103    type Output = Self;
104    fn sub(self, rhs: Self) -> Self::Output {
105        Rotation::try_from((self.degrees() as f64) - (rhs.degrees() as f64)).unwrap()
106    }
107}
108
109/// Get rotation from multiples of 90 deg
110///
111/// The given value is rounded to an integer number
112///
113/// ```
114/// # use gufo_common::orientation::Rotation;
115/// assert_eq!(Rotation::try_from(90.).unwrap(), Rotation::_90);
116/// assert_eq!(Rotation::try_from(-90.).unwrap(), Rotation::_270);
117/// assert!(Rotation::try_from(1.).is_err());
118/// ```
119impl TryFrom<f64> for Rotation {
120    type Error = InvalidRotation;
121    fn try_from(value: f64) -> Result<Self, Self::Error> {
122        let rotation = value.round().rem_euclid(360.);
123
124        if rotation == 0. {
125            Ok(Self::_0)
126        } else if rotation == 90. {
127            Ok(Self::_90)
128        } else if rotation == 180. {
129            Ok(Self::_180)
130        } else if rotation == 270. {
131            Ok(Self::_270)
132        } else {
133            Err(InvalidRotation(rotation))
134        }
135    }
136}
137
138impl Orientation {
139    pub fn mirror(self) -> bool {
140        matches!(
141            self,
142            Self::Mirrored
143                | Self::MirroredRotation90
144                | Self::MirroredRotation180
145                | Self::MirroredRotation270
146        )
147    }
148
149    pub fn rotate(self) -> Rotation {
150        match self {
151            Self::Id | Self::Mirrored => Rotation::_0,
152            Self::Rotation90 | Self::MirroredRotation90 => Rotation::_90,
153            Self::Rotation180 | Self::MirroredRotation180 => Rotation::_180,
154            Self::Rotation270 | Self::MirroredRotation270 => Rotation::_270,
155        }
156    }
157}