palette/
angle.rs

1//! Traits for working with angular values, such as for in hues.
2
3use crate::{
4    bool_mask::HasBoolMask,
5    num::{Real, Round},
6};
7
8#[cfg(feature = "wide")]
9mod wide;
10
11/// Represents types that can express half of a rotation (i.e. 180 degrees).
12pub trait HalfRotation {
13    /// Return a value that represents half of a rotation (i.e. 180 degrees).
14    #[must_use]
15    fn half_rotation() -> Self;
16}
17
18/// Represents types that can express a full rotation (i.e. 360 degrees).
19pub trait FullRotation {
20    /// Return a value that represents a full rotation (i.e. 360 degrees).
21    #[must_use]
22    fn full_rotation() -> Self;
23}
24
25/// Angle values that are real numbers and can represent both radians and
26/// degrees.
27pub trait RealAngle: Real {
28    /// Consider `self` to be radians and convert it to degrees.
29    #[must_use]
30    fn radians_to_degrees(self) -> Self;
31
32    /// Consider `self` to be degrees and convert it to radians.
33    #[must_use]
34    fn degrees_to_radians(self) -> Self;
35}
36
37/// Angular equality, where 0 degrees and 360 degrees are equal.
38pub trait AngleEq: HasBoolMask {
39    /// Check if `self` and `other` represent the same angle on a circle.
40    #[must_use]
41    fn angle_eq(&self, other: &Self) -> Self::Mask;
42}
43
44/// Angle types that can represent the full circle using positive and negative
45/// values.
46pub trait SignedAngle {
47    /// Normalize `self` to a range corresponding to -180 to 180 degrees.
48    #[must_use]
49    fn normalize_signed_angle(self) -> Self;
50}
51
52/// Angle types that can represent the full circle as positive values.
53pub trait UnsignedAngle {
54    /// Normalize `self` to a range corresponding to 0 to 360 degrees.
55    #[must_use]
56    fn normalize_unsigned_angle(self) -> Self;
57}
58
59/// Performs value-to-value conversion between angle types. See also [`IntoAngle`].
60pub trait FromAngle<T> {
61    /// Performs a conversion from `angle`.
62    fn from_angle(angle: T) -> Self;
63}
64
65impl<T> FromAngle<T> for T {
66    #[inline]
67    fn from_angle(angle: Self) -> Self {
68        angle
69    }
70}
71
72/// Performs value-to-value conversion between angle types. See also [`IntoAngle`].
73pub trait IntoAngle<T> {
74    /// Performs a conversion into `T`.
75    fn into_angle(self) -> T;
76}
77
78impl<T, U> IntoAngle<U> for T
79where
80    U: FromAngle<T>,
81{
82    #[inline]
83    fn into_angle(self) -> U {
84        U::from_angle(self)
85    }
86}
87
88macro_rules! impl_angle_float {
89    ($($ty: ident),+) => {
90        $(
91            impl HalfRotation for $ty {
92                #[inline]
93                fn half_rotation() -> Self {
94                    180.0
95                }
96            }
97
98            impl FullRotation for $ty {
99                #[inline]
100                fn full_rotation() -> Self {
101                    360.0
102                }
103            }
104
105            impl RealAngle for $ty {
106                #[inline]
107                fn degrees_to_radians(self) -> Self {
108                    self.to_radians()
109                }
110
111                #[inline]
112                fn radians_to_degrees(self) -> Self {
113                    self.to_degrees()
114                }
115            }
116
117            impl AngleEq for $ty {
118                #[inline]
119                fn angle_eq(&self, other: &Self) -> bool {
120                    self.normalize_unsigned_angle() == other.normalize_unsigned_angle()
121                }
122            }
123
124            impl SignedAngle for $ty {
125                #[inline]
126                fn normalize_signed_angle(self) -> Self {
127                    self - Round::ceil(((self + 180.0) / 360.0) - 1.0) * 360.0
128                }
129            }
130
131            impl UnsignedAngle for $ty {
132                #[inline]
133                fn normalize_unsigned_angle(self) -> Self {
134                    self - (Round::floor(self / 360.0) * 360.0)
135                }
136            }
137        )+
138    };
139}
140
141macro_rules! impl_from_angle_float {
142    ($ty: ident to $other_ty: ident) => {
143        impl FromAngle<$other_ty> for $ty {
144            #[inline]
145            fn from_angle(angle: $other_ty) -> Self {
146                angle as $ty
147            }
148        }
149    };
150}
151
152macro_rules! impl_from_angle_u8 {
153    ($($float_ty: ident),*) => {
154        $(
155            impl FromAngle<u8> for $float_ty {
156                #[inline]
157                fn from_angle(angle: u8) -> Self {
158                    (angle as $float_ty / 256.0) * Self::full_rotation()
159                }
160            }
161
162            impl FromAngle<$float_ty> for u8 {
163                #[inline]
164                fn from_angle(angle: $float_ty) -> Self {
165                    let normalized = angle.normalize_unsigned_angle() / $float_ty::full_rotation();
166                    let rounded = (normalized * 256.0).round();
167
168                    if rounded > 255.5 {
169                        0
170                    } else {
171                        rounded as u8
172                    }
173                }
174            }
175        )*
176    };
177}
178
179impl_angle_float!(f32, f64);
180impl_from_angle_float!(f32 to f64);
181impl_from_angle_float!(f64 to f32);
182impl_from_angle_u8!(f32, f64);
183
184impl HalfRotation for u8 {
185    #[inline]
186    fn half_rotation() -> Self {
187        128
188    }
189}
190
191impl AngleEq for u8 {
192    #[inline]
193    fn angle_eq(&self, other: &Self) -> bool {
194        self == other
195    }
196}
197
198impl UnsignedAngle for u8 {
199    #[inline]
200    fn normalize_unsigned_angle(self) -> Self {
201        self
202    }
203}
204
205#[cfg(test)]
206mod test {
207    use crate::RgbHue;
208
209    #[test]
210    fn f32_to_u8() {
211        let hue_f32 = RgbHue::new(180.0f32);
212        let hue_u8 = hue_f32.into_format::<u8>();
213        assert_eq!(hue_u8, RgbHue::new(128u8));
214    }
215
216    #[test]
217    fn u8_to_f32() {
218        let hue_f32 = RgbHue::new(128u8);
219        let hue_u8 = hue_f32.into_format::<f32>();
220        assert_eq!(hue_u8, RgbHue::new(180.0f32));
221    }
222}