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