ux-primitives 0.2.2

Graphics Primitives for Angular Rust
Documentation
use super::{utils, Color, ColorError, Float};
use std::fmt;

/// Hsl color representation
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct HslColor {
    /// Hue component
    pub hue: Float,
    /// Saturation component
    pub saturation: Float,
    /// Lightness component
    pub lightness: Float,
}

impl HslColor {
    /// Create new Hsl color with parameters
    pub fn new(h: Float, s: Float, l: Float) -> Self {
        Self {
            hue: h % 360.0,
            saturation: if s > 100.0 { 100.0 } else { s },
            lightness: if s > 100.0 { 100.0 } else { l },
        }
    }
}

impl fmt::Display for HslColor {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "hsl({}°, {}%, {}%)",
            self.hue, self.saturation, self.lightness
        )
    }
}

// HSL -> RGB
impl From<HslColor> for Color {
    fn from(hsl: HslColor) -> Self {
        let HslColor {
            hue,
            saturation,
            lightness,
        } = hsl;
        let c = (1. - ((2. * (lightness as Float / 100.)) - 1.).abs()) * (saturation as Float / 100.);
        let x = c * (1. - ((((hue as Float) / 60.) % 2.) - 1.).abs());
        let m = (lightness as Float / 100.) - (c / 2.);

        let (r_prime, g_prime, b_prime) = {
            if (0.0..60.0).contains(&hue) {
                (c, x, 0.)
            } else if (60.0..120.0).contains(&hue) {
                (x, c, 0.)
            } else if (120.0..180.0).contains(&hue) {
                (0., c, x)
            } else if (180.0..240.0).contains(&hue) {
                (0., x, c)
            } else if (240.0..300.0).contains(&hue) {
                (x, 0., c)
            } else if (300.0..360.0).contains(&hue) {
                (c, 0., x)
            } else {
                unreachable!("{}", ColorError::DegreeOverflow)
            }
        };
        Color {
            red: r_prime + m,
            green: g_prime + m,
            blue: b_prime + m,
            alpha: 1.,
        }
    }
}

// RGB -> HSL
impl From<Color> for HslColor {
    fn from(rgb: Color) -> Self {
        let Color {
            red,
            green,
            blue,
            alpha: _,
        } = rgb;

        let (c_min, c_max) = utils::min_max_tuple([red, green, blue].iter());
        let delta = c_max - c_min;

        let hue = if (delta - 0.).abs() < Float::EPSILON {
            0.
        } else {
            match c_max {
                x if (x - red).abs() < Float::MIN_POSITIVE => 60. * (((green - blue) / delta) % 6.),
                x if (x - green).abs() < Float::MIN_POSITIVE => 60. * (((blue - red) / delta) + 2.),
                x if (x - blue).abs() < Float::MIN_POSITIVE => 60. * (((red - green) / delta) + 4.),
                _ => unreachable!("Invalid hue calculation!"),
            }
        };

        let lightness = (c_max + c_min) / 2.;

        let saturation = if (delta - 0.).abs() < Float::EPSILON {
            0.
        } else {
            delta / (1. - ((2. * lightness) - 1.).abs()) * 100.
        };

        HslColor {
            hue,
            saturation,
            lightness: lightness * 100.,
        }
    }
}

#[cfg(test)]
mod test {
    use super::super::test_utils;
    use super::super::*;

    #[test]
    fn to_rgb() {
        test_utils::test_to_rgb_conversion(test_utils::RGB_HSL.iter())
    }

    #[test]
    fn rgb_to_hsv() {
        test_utils::test_conversion(test_utils::RGB_HSL.iter(), |actual_color, expected_hsl| {
            let actual_rgb: RgbColor = (*actual_color).into();
            let actual_hsl: HslColor = actual_rgb.into();
            let HslColor {
                hue: actual_h,
                saturation: actual_s,
                lightness: actual_l,
            } = actual_hsl;
            let (actual_h, actual_s, actual_l) =
                (actual_h.round(), actual_s.round(), actual_l.round());
            let HslColor {
                hue: expected_h,
                saturation: expected_s,
                lightness: expected_l,
            } = *expected_hsl;
            assert!(
                test_utils::diff_less_than_f64(actual_h, expected_h, 1.),
                "wrong hue: {} -> {} != {}",
                actual_rgb,
                actual_hsl,
                expected_hsl
            );
            assert!(
                test_utils::diff_less_than_f64(actual_s, expected_s, 1.),
                "wrong saturation: {} -> {} != {}",
                actual_rgb,
                actual_hsl,
                expected_hsl
            );
            assert!(
                test_utils::diff_less_than_f64(actual_l, expected_l, 1.),
                "wrong lightness: {} -> {} != {}",
                actual_rgb,
                actual_hsl,
                expected_hsl
            );
        })
    }
}