Skip to main content

rand_hsv/
lib.rs

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}