1#[cfg(test)]
2mod tests;
3
4use rand::Rng;
5use std::error::Error;
6use std::fmt;
7
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9#[derive(Clone, Copy, Debug, PartialEq)]
10pub struct HsvColor {
11 pub hue: f32,
12 pub saturation: f32,
13 pub value: f32,
14 pub alpha: f32,
15}
16
17impl HsvColor {
18 pub fn to_hsva_string(&self) -> String {
19 self.to_string()
20 }
21}
22
23impl fmt::Display for HsvColor {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 write!(
26 f,
27 "hsva({:.1}, {:.1}%, {:.1}%, {:.2})",
28 self.hue, self.saturation, self.value, self.alpha
29 )
30 }
31}
32
33#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
34#[derive(Clone, Debug, PartialEq)]
35pub struct HsvRange {
36 pub hue: (f32, f32),
37 pub saturation: (f32, f32),
38 pub value: (f32, f32),
39 pub alpha: (f32, f32),
40}
41
42impl Default for HsvRange {
43 fn default() -> Self {
44 Self {
45 hue: (0.0, 360.0),
46 saturation: (0.0, 100.0),
47 value: (0.0, 100.0),
48 alpha: (0.0, 1.0),
49 }
50 }
51}
52
53impl HsvRange {
54 #[allow(clippy::too_many_arguments)]
55 pub fn new(
56 min_hue: f32,
57 max_hue: f32,
58 min_saturation: f32,
59 max_saturation: f32,
60 min_value: f32,
61 max_value: f32,
62 min_alpha: f32,
63 max_alpha: f32,
64 ) -> Result<Self, HsvError> {
65 let range = Self {
66 hue: (min_hue, max_hue),
67 saturation: (min_saturation, max_saturation),
68 value: (min_value, max_value),
69 alpha: (min_alpha, max_alpha),
70 };
71 validate_range(&range)?;
72 Ok(range)
73 }
74}
75
76#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
77#[derive(Clone, Copy, Debug, Eq, PartialEq)]
78pub enum HsvError {
79 InvalidHueRange,
80 InvalidSaturationRange,
81 InvalidValueRange,
82 InvalidAlphaRange,
83 ComponentOutOfBounds,
84 NonFiniteValue,
85}
86
87impl fmt::Display for HsvError {
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 let message = match self {
90 Self::InvalidHueRange => "hue min value must be <= max",
91 Self::InvalidSaturationRange => "saturation min value must be <= max",
92 Self::InvalidValueRange => "value min value must be <= max",
93 Self::InvalidAlphaRange => "alpha min value must be <= max",
94 Self::ComponentOutOfBounds => {
95 "hue must be 0..=360, saturation/value 0..=100, alpha 0..=1"
96 }
97 Self::NonFiniteValue => "all HSV components must be finite",
98 };
99 f.write_str(message)
100 }
101}
102
103impl Error for HsvError {}
104
105pub fn random_hsv() -> HsvColor {
106 let mut rng = rand::thread_rng();
107 random_hsv_with_rng(&mut rng)
108}
109
110pub fn random_hsv_with_rng<R: Rng + ?Sized>(rng: &mut R) -> HsvColor {
111 random_hsv_in_with_rng(HsvRange::default(), rng).expect("default hsv range should be valid")
112}
113
114pub fn random_hsv_in(range: HsvRange) -> Result<HsvColor, HsvError> {
115 let mut rng = rand::thread_rng();
116 random_hsv_in_with_rng(range, &mut rng)
117}
118
119pub fn random_hsv_in_with_rng<R: Rng + ?Sized>(
120 range: HsvRange,
121 rng: &mut R,
122) -> Result<HsvColor, HsvError> {
123 validate_range(&range)?;
124 Ok(HsvColor {
125 hue: rng.gen_range(range.hue.0..=range.hue.1),
126 saturation: rng.gen_range(range.saturation.0..=range.saturation.1),
127 value: rng.gen_range(range.value.0..=range.value.1),
128 alpha: rng.gen_range(range.alpha.0..=range.alpha.1),
129 })
130}
131
132fn validate_range(range: &HsvRange) -> Result<(), HsvError> {
133 let numbers = [
134 range.hue.0,
135 range.hue.1,
136 range.saturation.0,
137 range.saturation.1,
138 range.value.0,
139 range.value.1,
140 range.alpha.0,
141 range.alpha.1,
142 ];
143 if numbers.iter().any(|value| !value.is_finite()) {
144 return Err(HsvError::NonFiniteValue);
145 }
146 if range.hue.0 > range.hue.1 {
147 return Err(HsvError::InvalidHueRange);
148 }
149 if range.saturation.0 > range.saturation.1 {
150 return Err(HsvError::InvalidSaturationRange);
151 }
152 if range.value.0 > range.value.1 {
153 return Err(HsvError::InvalidValueRange);
154 }
155 if range.alpha.0 > range.alpha.1 {
156 return Err(HsvError::InvalidAlphaRange);
157 }
158 if range.hue.0 < 0.0
159 || range.hue.1 > 360.0
160 || range.saturation.0 < 0.0
161 || range.saturation.1 > 100.0
162 || range.value.0 < 0.0
163 || range.value.1 > 100.0
164 || range.alpha.0 < 0.0
165 || range.alpha.1 > 1.0
166 {
167 return Err(HsvError::ComponentOutOfBounds);
168 }
169 Ok(())
170}