palette/
hsl.rs

1//! Types for the HSL color space.
2
3use core::{any::TypeId, marker::PhantomData, ops::Not};
4
5use crate::{
6    angle::{FromAngle, RealAngle},
7    bool_mask::{BitOps, BoolMask, HasBoolMask, LazySelect, Select},
8    convert::FromColorUnclamped,
9    encoding::Srgb,
10    hues::RgbHueIter,
11    num::{Arithmetics, IsValidDivisor, MinMax, One, PartialCmp, Real, Zero},
12    rgb::{Rgb, RgbSpace, RgbStandard},
13    stimulus::{FromStimulus, Stimulus},
14    Alpha, FromColor, Hsv, RgbHue, Xyz,
15};
16
17/// Linear HSL with an alpha component. See the [`Hsla` implementation in
18/// `Alpha`](crate::Alpha#Hsla).
19pub type Hsla<S = Srgb, T = f32> = Alpha<Hsl<S, T>, T>;
20
21/// HSL color space.
22///
23/// The HSL color space can be seen as a cylindrical version of
24/// [RGB](crate::rgb::Rgb), where the `hue` is the angle around the color
25/// cylinder, the `saturation` is the distance from the center, and the
26/// `lightness` is the height from the bottom. Its composition makes it
27/// especially good for operations like changing green to red, making a color
28/// more gray, or making it darker.
29///
30/// HSL component values are typically real numbers (such as floats), but may
31/// also be converted to and from `u8` for storage and interoperability
32/// purposes. The hue is then within the range `[0, 255]`.
33///
34/// ```
35/// use approx::assert_relative_eq;
36/// use palette::Hsl;
37///
38/// let hsl_u8 = Hsl::new_srgb(128u8, 85, 51);
39/// let hsl_f32 = hsl_u8.into_format::<f32>();
40///
41/// assert_relative_eq!(hsl_f32, Hsl::new(180.0, 1.0 / 3.0, 0.2));
42/// ```
43///
44/// See [HSV](crate::Hsv) for a very similar color space, with brightness
45/// instead of lightness.
46#[derive(Debug, ArrayCast, FromColorUnclamped, WithAlpha)]
47#[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))]
48#[palette(
49    palette_internal,
50    rgb_standard = "S",
51    component = "T",
52    skip_derives(Rgb, Hsv, Hsl)
53)]
54#[repr(C)]
55pub struct Hsl<S = Srgb, T = f32> {
56    /// The hue of the color, in degrees. Decides if it's red, blue, purple,
57    /// etc.
58    #[palette(unsafe_same_layout_as = "T")]
59    pub hue: RgbHue<T>,
60
61    /// The colorfulness of the color. 0.0 gives gray scale colors and 1.0 will
62    /// give absolutely clear colors.
63    pub saturation: T,
64
65    /// Decides how light the color will look. 0.0 will be black, 0.5 will give
66    /// a clear color, and 1.0 will give white.
67    pub lightness: T,
68
69    /// The white point and RGB primaries this color is adapted to. The default
70    /// is the sRGB standard.
71    #[cfg_attr(feature = "serializing", serde(skip))]
72    #[palette(unsafe_zero_sized)]
73    pub standard: PhantomData<S>,
74}
75
76impl<T> Hsl<Srgb, T> {
77    /// Create an sRGB HSL color. This method can be used instead of `Hsl::new`
78    /// to help type inference.
79    pub fn new_srgb<H: Into<RgbHue<T>>>(hue: H, saturation: T, lightness: T) -> Self {
80        Self::new_const(hue.into(), saturation, lightness)
81    }
82
83    /// Create an sRGB HSL color. This is the same as `Hsl::new_srgb` without
84    /// the generic hue type. It's temporary until `const fn` supports traits.
85    pub const fn new_srgb_const(hue: RgbHue<T>, saturation: T, lightness: T) -> Self {
86        Self::new_const(hue, saturation, lightness)
87    }
88}
89
90impl<S, T> Hsl<S, T> {
91    /// Create an HSL color.
92    pub fn new<H: Into<RgbHue<T>>>(hue: H, saturation: T, lightness: T) -> Self {
93        Self::new_const(hue.into(), saturation, lightness)
94    }
95
96    /// Create an HSL color. This is the same as `Hsl::new` without the generic
97    /// hue type. It's temporary until `const fn` supports traits.
98    pub const fn new_const(hue: RgbHue<T>, saturation: T, lightness: T) -> Self {
99        Hsl {
100            hue,
101            saturation,
102            lightness,
103            standard: PhantomData,
104        }
105    }
106
107    /// Convert into another component type.
108    pub fn into_format<U>(self) -> Hsl<S, U>
109    where
110        U: FromStimulus<T> + FromAngle<T>,
111    {
112        Hsl {
113            hue: self.hue.into_format(),
114            saturation: U::from_stimulus(self.saturation),
115            lightness: U::from_stimulus(self.lightness),
116            standard: PhantomData,
117        }
118    }
119
120    /// Convert from another component type.
121    pub fn from_format<U>(color: Hsl<S, U>) -> Self
122    where
123        T: FromStimulus<U> + FromAngle<U>,
124    {
125        color.into_format()
126    }
127
128    /// Convert to a `(hue, saturation, lightness)` tuple.
129    pub fn into_components(self) -> (RgbHue<T>, T, T) {
130        (self.hue, self.saturation, self.lightness)
131    }
132
133    /// Convert from a `(hue, saturation, lightness)` tuple.
134    pub fn from_components<H: Into<RgbHue<T>>>((hue, saturation, lightness): (H, T, T)) -> Self {
135        Self::new(hue, saturation, lightness)
136    }
137
138    #[inline]
139    fn reinterpret_as<St>(self) -> Hsl<St, T> {
140        Hsl {
141            hue: self.hue,
142            saturation: self.saturation,
143            lightness: self.lightness,
144            standard: PhantomData,
145        }
146    }
147}
148
149impl<S, T> Hsl<S, T>
150where
151    T: Stimulus,
152{
153    /// Return the `saturation` value minimum.
154    pub fn min_saturation() -> T {
155        T::zero()
156    }
157
158    /// Return the `saturation` value maximum.
159    pub fn max_saturation() -> T {
160        T::max_intensity()
161    }
162
163    /// Return the `lightness` value minimum.
164    pub fn min_lightness() -> T {
165        T::zero()
166    }
167
168    /// Return the `lightness` value maximum.
169    pub fn max_lightness() -> T {
170        T::max_intensity()
171    }
172}
173
174///<span id="Hsla"></span>[`Hsla`](crate::Hsla) implementations.
175impl<T, A> Alpha<Hsl<Srgb, T>, A> {
176    /// Create an sRGB HSL color with transparency. This method can be used
177    /// instead of `Hsla::new` to help type inference.
178    pub fn new_srgb<H: Into<RgbHue<T>>>(hue: H, saturation: T, lightness: T, alpha: A) -> Self {
179        Self::new_const(hue.into(), saturation, lightness, alpha)
180    }
181
182    /// Create an sRGB HSL color with transparency. This is the same as
183    /// `Hsla::new_srgb` without the generic hue type. It's temporary until
184    /// `const fn` supports traits.
185    pub const fn new_srgb_const(hue: RgbHue<T>, saturation: T, lightness: T, alpha: A) -> Self {
186        Self::new_const(hue, saturation, lightness, alpha)
187    }
188}
189
190///<span id="Hsla"></span>[`Hsla`](crate::Hsla) implementations.
191impl<S, T, A> Alpha<Hsl<S, T>, A> {
192    /// Create an HSL color with transparency.
193    pub fn new<H: Into<RgbHue<T>>>(hue: H, saturation: T, lightness: T, alpha: A) -> Self {
194        Self::new_const(hue.into(), saturation, lightness, alpha)
195    }
196
197    /// Create an HSL color with transparency. This is the same as `Hsla::new`
198    /// without the generic hue type. It's temporary until `const fn` supports
199    /// traits.
200    pub const fn new_const(hue: RgbHue<T>, saturation: T, lightness: T, alpha: A) -> Self {
201        Alpha {
202            color: Hsl::new_const(hue, saturation, lightness),
203            alpha,
204        }
205    }
206    /// Convert into another component type.
207    pub fn into_format<U, B>(self) -> Alpha<Hsl<S, U>, B>
208    where
209        U: FromStimulus<T> + FromAngle<T>,
210        B: FromStimulus<A>,
211    {
212        Alpha {
213            color: self.color.into_format(),
214            alpha: B::from_stimulus(self.alpha),
215        }
216    }
217
218    /// Convert from another component type.
219    pub fn from_format<U, B>(color: Alpha<Hsl<S, U>, B>) -> Self
220    where
221        T: FromStimulus<U> + FromAngle<U>,
222        A: FromStimulus<B>,
223    {
224        color.into_format()
225    }
226
227    /// Convert to a `(hue, saturation, lightness, alpha)` tuple.
228    pub fn into_components(self) -> (RgbHue<T>, T, T, A) {
229        (
230            self.color.hue,
231            self.color.saturation,
232            self.color.lightness,
233            self.alpha,
234        )
235    }
236
237    /// Convert from a `(hue, saturation, lightness, alpha)` tuple.
238    pub fn from_components<H: Into<RgbHue<T>>>(
239        (hue, saturation, lightness, alpha): (H, T, T, A),
240    ) -> Self {
241        Self::new(hue, saturation, lightness, alpha)
242    }
243}
244
245impl_reference_component_methods_hue!(Hsl<S>, [saturation, lightness], standard);
246impl_struct_of_arrays_methods_hue!(Hsl<S>, [saturation, lightness], standard);
247
248impl<S1, S2, T> FromColorUnclamped<Hsl<S1, T>> for Hsl<S2, T>
249where
250    S1: RgbStandard + 'static,
251    S2: RgbStandard + 'static,
252    S1::Space: RgbSpace<WhitePoint = <S2::Space as RgbSpace>::WhitePoint>,
253    Rgb<S1, T>: FromColorUnclamped<Hsl<S1, T>>,
254    Rgb<S2, T>: FromColorUnclamped<Rgb<S1, T>>,
255    Self: FromColorUnclamped<Rgb<S2, T>>,
256{
257    #[inline]
258    fn from_color_unclamped(hsl: Hsl<S1, T>) -> Self {
259        if TypeId::of::<S1>() == TypeId::of::<S2>() {
260            hsl.reinterpret_as()
261        } else {
262            let rgb = Rgb::<S1, T>::from_color_unclamped(hsl);
263            let converted_rgb = Rgb::<S2, T>::from_color_unclamped(rgb);
264            Self::from_color_unclamped(converted_rgb)
265        }
266    }
267}
268
269impl<S, T> FromColorUnclamped<Rgb<S, T>> for Hsl<S, T>
270where
271    T: RealAngle + Zero + One + MinMax + Arithmetics + PartialCmp + Clone,
272    T::Mask: BoolMask + BitOps + LazySelect<T> + Clone + 'static,
273{
274    fn from_color_unclamped(rgb: Rgb<S, T>) -> Self {
275        // Avoid negative numbers
276        let red = rgb.red.max(T::zero());
277        let green = rgb.green.max(T::zero());
278        let blue = rgb.blue.max(T::zero());
279
280        // The SIMD optimized version showed significant slowdown for regular floats.
281        if TypeId::of::<T::Mask>() == TypeId::of::<bool>() {
282            let (max, min, sep, coeff) = {
283                let (max, min, sep, coeff) = if red.gt(&green).is_true() {
284                    (red.clone(), green.clone(), green.clone() - &blue, T::zero())
285                } else {
286                    (
287                        green.clone(),
288                        red.clone(),
289                        blue.clone() - &red,
290                        T::from_f64(2.0),
291                    )
292                };
293                if blue.gt(&max).is_true() {
294                    (blue, min, red - green, T::from_f64(4.0))
295                } else {
296                    let min_val = if blue.lt(&min).is_true() { blue } else { min };
297                    (max, min_val, sep, coeff)
298                }
299            };
300
301            let mut h = T::zero();
302            let mut s = T::zero();
303
304            let sum = max.clone() + &min;
305            let l = sum.clone() / T::from_f64(2.0);
306            if max.neq(&min).is_true() {
307                let d = max - min;
308                s = if sum.gt(&T::one()).is_true() {
309                    d.clone() / (T::from_f64(2.0) - sum)
310                } else {
311                    d.clone() / sum
312                };
313                h = ((sep / d) + coeff) * T::from_f64(60.0);
314            };
315
316            Hsl {
317                hue: h.into(),
318                saturation: s,
319                lightness: l,
320                standard: PhantomData,
321            }
322        } else {
323            // Based on OPTIMIZED RGB TO HSV COLOR CONVERSION USING SSE TECHNOLOGY
324            // by KOBALICEK, Petr & BLIZNAK, Michal
325            //
326            // This implementation assumes less about the underlying mask and number
327            // representation. The hue is also multiplied by 6 to avoid rounding
328            // errors when using degrees.
329
330            let six = T::from_f64(6.0);
331
332            let max = red.clone().max(green.clone()).max(blue.clone());
333            let min = red.clone().min(green.clone()).min(blue.clone());
334
335            let sum = max.clone() + &min;
336            let lightness = T::from_f64(0.5) * &sum;
337
338            let chroma = max.clone() - &min;
339            let saturation = lazy_select! {
340                if min.eq(&max) => T::zero(),
341                else => chroma.clone() /
342                    sum.gt(&T::one()).select(T::from_f64(2.0) - &sum, sum.clone()),
343            };
344
345            // Each of these represents an RGB component. The maximum will be false
346            // while the two other will be true. They are later used for determining
347            // which branch in the hue equation we end up in.
348            let x = max.neq(&red);
349            let y = max.eq(&red) | max.neq(&green);
350            let z = max.eq(&red) | max.eq(&green);
351
352            // The hue base is the `1`, `2/6`, `4/6` or 0 part of the hue equation,
353            // except it's multiplied by 6 here.
354            let hue_base = x.clone().select(
355                z.clone().select(T::from_f64(-4.0), T::from_f64(4.0)),
356                T::zero(),
357            ) + &six;
358
359            // Each of these is a part of `G - B`, `B - R`, `R - G` or 0 from the
360            // hue equation. They become positive, negative or 0, depending on which
361            // branch we should be in. This makes the sum of all three combine as
362            // expected.
363            let red_m = lazy_select! {
364               if x => y.clone().select(red.clone(), -red),
365               else => T::zero(),
366            };
367            let green_m = lazy_select! {
368               if y.clone() => z.clone().select(green.clone(), -green),
369               else => T::zero(),
370            };
371            let blue_m = lazy_select! {
372               if z => y.select(-blue.clone(), blue),
373               else => T::zero(),
374            };
375
376            // This is the hue equation parts combined. The hue base is the constant
377            // and the RGB components are masked so up to two of them are non-zero.
378            // Once again, this is multiplied by 6, so the chroma isn't multiplied
379            // before dividing.
380            //
381            // We also avoid dividing by 0 for non-SIMD values.
382            let hue = lazy_select! {
383                if chroma.eq(&T::zero()) => T::zero(),
384                else => hue_base + (red_m + green_m + blue_m) / &chroma,
385            };
386
387            // hue will always be within [0, 12) (it's multiplied by 6, compared to
388            // the paper), so we can subtract by 6 instead of using % to get it
389            // within [0, 6).
390            let hue_sub = hue.gt_eq(&six).select(six, T::zero());
391            let hue = hue - hue_sub;
392
393            Hsl {
394                hue: RgbHue::from_degrees(hue * T::from_f64(60.0)),
395                saturation,
396                lightness,
397                standard: PhantomData,
398            }
399        }
400    }
401}
402
403impl<S, T> FromColorUnclamped<Hsv<S, T>> for Hsl<S, T>
404where
405    T: Real + Zero + One + IsValidDivisor + Arithmetics + PartialCmp + Clone,
406    T::Mask: LazySelect<T> + Not<Output = T::Mask>,
407{
408    fn from_color_unclamped(hsv: Hsv<S, T>) -> Self {
409        let Hsv {
410            hue,
411            saturation,
412            value,
413            ..
414        } = hsv;
415
416        let x = (T::from_f64(2.0) - &saturation) * &value;
417        let saturation = lazy_select! {
418            if !value.is_valid_divisor() => T::zero(),
419            if x.lt(&T::one()) => {
420                lazy_select!{
421                    if x.is_valid_divisor() => saturation.clone() * &value / &x,
422                    else => T::zero(),
423                }
424            },
425            else => {
426                let denom = T::from_f64(2.0) - &x;
427                lazy_select! {
428                    if denom.is_valid_divisor() => saturation.clone() * &value / denom,
429                    else => T::zero(),
430                }
431            },
432        };
433
434        Hsl {
435            hue,
436            saturation,
437            lightness: x / T::from_f64(2.0),
438            standard: PhantomData,
439        }
440    }
441}
442
443impl_tuple_conversion_hue!(Hsl<S> as (H, T, T), RgbHue);
444
445impl_is_within_bounds! {
446    Hsl<S> {
447        saturation => [Self::min_saturation(), Self::max_saturation()],
448        lightness => [Self::min_lightness(), Self::max_lightness()]
449    }
450    where T: Stimulus
451}
452impl_clamp! {
453    Hsl<S> {
454        saturation => [Self::min_saturation(), Self::max_saturation()],
455        lightness => [Self::min_lightness(), Self::max_lightness()]
456    }
457    other {hue, standard}
458    where T: Stimulus
459}
460
461impl_mix_hue!(Hsl<S> {saturation, lightness} phantom: standard);
462impl_lighten!(Hsl<S> increase {lightness => [Self::min_lightness(), Self::max_lightness()]} other {hue, saturation} phantom: standard where T: Stimulus);
463impl_saturate!(Hsl<S> increase {saturation => [Self::min_saturation(), Self::max_saturation()]} other {hue, lightness} phantom: standard where T: Stimulus);
464impl_hue_ops!(Hsl<S>, RgbHue);
465
466impl<S, T> HasBoolMask for Hsl<S, T>
467where
468    T: HasBoolMask,
469{
470    type Mask = T::Mask;
471}
472
473impl<S, T> Default for Hsl<S, T>
474where
475    T: Stimulus,
476    RgbHue<T>: Default,
477{
478    fn default() -> Hsl<S, T> {
479        Hsl::new(
480            RgbHue::default(),
481            Self::min_saturation(),
482            Self::min_lightness(),
483        )
484    }
485}
486
487impl_color_add!(Hsl<S>, [hue, saturation, lightness], standard);
488impl_color_sub!(Hsl<S>, [hue, saturation, lightness], standard);
489
490impl_array_casts!(Hsl<S, T>, [T; 3]);
491impl_simd_array_conversion_hue!(Hsl<S>, [saturation, lightness], standard);
492impl_struct_of_array_traits_hue!(Hsl<S>, RgbHueIter, [saturation, lightness], standard);
493
494impl_eq_hue!(Hsl<S>, RgbHue, [hue, saturation, lightness]);
495impl_copy_clone!(Hsl<S>, [hue, saturation, lightness], standard);
496
497#[allow(deprecated)]
498impl<S, T> crate::RelativeContrast for Hsl<S, T>
499where
500    T: Real + Arithmetics + PartialCmp,
501    T::Mask: LazySelect<T>,
502    S: RgbStandard,
503    Xyz<<S::Space as RgbSpace>::WhitePoint, T>: FromColor<Self>,
504{
505    type Scalar = T;
506
507    #[inline]
508    fn get_contrast_ratio(self, other: Self) -> T {
509        let xyz1 = Xyz::from_color(self);
510        let xyz2 = Xyz::from_color(other);
511
512        crate::contrast_ratio(xyz1.y, xyz2.y)
513    }
514}
515
516impl_rand_traits_hsl_bicone!(
517    UniformHsl,
518    Hsl<S> {
519        hue: UniformRgbHue => RgbHue,
520        height: lightness,
521        radius: saturation
522    }
523    phantom: standard: PhantomData<S>
524);
525
526#[cfg(feature = "bytemuck")]
527unsafe impl<S, T> bytemuck::Zeroable for Hsl<S, T> where T: bytemuck::Zeroable {}
528
529#[cfg(feature = "bytemuck")]
530unsafe impl<S: 'static, T> bytemuck::Pod for Hsl<S, T> where T: bytemuck::Pod {}
531
532#[cfg(test)]
533mod test {
534    use super::Hsl;
535
536    test_convert_into_from_xyz!(Hsl);
537
538    #[cfg(feature = "approx")]
539    mod conversion {
540        use crate::{FromColor, Hsl, Hsv, Srgb};
541
542        #[test]
543        fn red() {
544            let a = Hsl::from_color(Srgb::new(1.0, 0.0, 0.0));
545            let b = Hsl::new_srgb(0.0, 1.0, 0.5);
546            let c = Hsl::from_color(Hsv::new_srgb(0.0, 1.0, 1.0));
547
548            assert_relative_eq!(a, b);
549            assert_relative_eq!(a, c);
550        }
551
552        #[test]
553        fn orange() {
554            let a = Hsl::from_color(Srgb::new(1.0, 0.5, 0.0));
555            let b = Hsl::new_srgb(30.0, 1.0, 0.5);
556            let c = Hsl::from_color(Hsv::new_srgb(30.0, 1.0, 1.0));
557
558            assert_relative_eq!(a, b);
559            assert_relative_eq!(a, c);
560        }
561
562        #[test]
563        fn green() {
564            let a = Hsl::from_color(Srgb::new(0.0, 1.0, 0.0));
565            let b = Hsl::new_srgb(120.0, 1.0, 0.5);
566            let c = Hsl::from_color(Hsv::new_srgb(120.0, 1.0, 1.0));
567
568            assert_relative_eq!(a, b);
569            assert_relative_eq!(a, c);
570        }
571
572        #[test]
573        fn blue() {
574            let a = Hsl::from_color(Srgb::new(0.0, 0.0, 1.0));
575            let b = Hsl::new_srgb(240.0, 1.0, 0.5);
576            let c = Hsl::from_color(Hsv::new_srgb(240.0, 1.0, 1.0));
577
578            assert_relative_eq!(a, b);
579            assert_relative_eq!(a, c);
580        }
581
582        #[test]
583        fn purple() {
584            let a = Hsl::from_color(Srgb::new(0.5, 0.0, 1.0));
585            let b = Hsl::new_srgb(270.0, 1.0, 0.5);
586            let c = Hsl::from_color(Hsv::new_srgb(270.0, 1.0, 1.0));
587
588            assert_relative_eq!(a, b);
589            assert_relative_eq!(a, c);
590        }
591    }
592
593    #[test]
594    fn ranges() {
595        assert_ranges! {
596            Hsl<crate::encoding::Srgb, f64>;
597            clamped {
598                saturation: 0.0 => 1.0,
599                lightness: 0.0 => 1.0
600            }
601            clamped_min {}
602            unclamped {
603                hue: -360.0 => 360.0
604            }
605        }
606    }
607
608    raw_pixel_conversion_tests!(Hsl<crate::encoding::Srgb>: hue, saturation, lightness);
609    raw_pixel_conversion_fail_tests!(Hsl<crate::encoding::Srgb>: hue, saturation, lightness);
610
611    #[test]
612    fn check_min_max_components() {
613        use crate::encoding::Srgb;
614
615        assert_eq!(Hsl::<Srgb>::min_saturation(), 0.0);
616        assert_eq!(Hsl::<Srgb>::min_lightness(), 0.0);
617        assert_eq!(Hsl::<Srgb>::max_saturation(), 1.0);
618        assert_eq!(Hsl::<Srgb>::max_lightness(), 1.0);
619    }
620
621    struct_of_arrays_tests!(
622        Hsl<crate::encoding::Srgb>[hue, saturation, lightness] phantom: standard,
623        super::Hsla::new(0.1f32, 0.2, 0.3, 0.4),
624        super::Hsla::new(0.2, 0.3, 0.4, 0.5),
625        super::Hsla::new(0.3, 0.4, 0.5, 0.6)
626    );
627
628    #[cfg(feature = "serializing")]
629    #[test]
630    fn serialize() {
631        let serialized = ::serde_json::to_string(&Hsl::new_srgb(0.3, 0.8, 0.1)).unwrap();
632
633        assert_eq!(
634            serialized,
635            r#"{"hue":0.3,"saturation":0.8,"lightness":0.1}"#
636        );
637    }
638
639    #[cfg(feature = "serializing")]
640    #[test]
641    fn deserialize() {
642        let deserialized: Hsl =
643            ::serde_json::from_str(r#"{"hue":0.3,"saturation":0.8,"lightness":0.1}"#).unwrap();
644
645        assert_eq!(deserialized, Hsl::new(0.3, 0.8, 0.1));
646    }
647
648    test_uniform_distribution! {
649        Hsl<crate::encoding::Srgb, f32> as crate::rgb::Rgb {
650            red: (0.0, 1.0),
651            green: (0.0, 1.0),
652            blue: (0.0, 1.0)
653        },
654        min: Hsl::new(0.0f32, 0.0, 0.0),
655        max: Hsl::new(360.0, 1.0, 1.0)
656    }
657
658    /// Sanity check to make sure the test doesn't start accepting known
659    /// non-uniform distributions.
660    #[cfg(feature = "random")]
661    #[test]
662    #[should_panic(expected = "is not uniform enough")]
663    fn uniform_distribution_fail() {
664        use rand::Rng;
665
666        const BINS: usize = crate::random_sampling::test_utils::BINS;
667        const SAMPLES: usize = crate::random_sampling::test_utils::SAMPLES;
668
669        let mut red = [0; BINS];
670        let mut green = [0; BINS];
671        let mut blue = [0; BINS];
672
673        let mut rng = rand_mt::Mt::new(1234); // We want the same seed on every run to avoid random fails
674
675        for _ in 0..SAMPLES {
676            let color = Hsl::<crate::encoding::Srgb, f32>::new(
677                rng.gen::<f32>() * 360.0,
678                rng.gen(),
679                rng.gen(),
680            );
681            let color: crate::rgb::Rgb = crate::IntoColor::into_color(color);
682            red[((color.red * BINS as f32) as usize).min(9)] += 1;
683            green[((color.green * BINS as f32) as usize).min(9)] += 1;
684            blue[((color.blue * BINS as f32) as usize).min(9)] += 1;
685        }
686
687        assert_uniform_distribution!(red);
688        assert_uniform_distribution!(green);
689        assert_uniform_distribution!(blue);
690    }
691}