colours 0.1.1

Color types for different color models with conversions between it.
Documentation
use super::{IsColorChannel, IsColor, HasAlpha, HasntAlpha, Rgba, Hsl, Hsv};

/// Generic RGB color
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Rgb<T> {
    pub red: T,
    pub green: T,
    pub blue: T,
}

impl<T: IsColorChannel> IsColor for Rgb<T> {
    type Channel = T;
}

impl<T: IsColorChannel> HasntAlpha for Rgb<T> {
    type Alphaful = Rgba<T>;

    fn with_alpha(self, alpha: Self::Channel) -> Self::Alphaful {
        let Rgb { red, green, blue } = self;
        Rgba { red, green, blue, alpha }
    }
}

impl<T> Rgb<T> {
    pub fn new(red: T, green: T, blue: T) -> Self {
        Self { red, green, blue }
    }
}

impl<T: IsColorChannel> Default for Rgb<T> {
    fn default() -> Self {
        Self::new(T::MIN, T::MIN, T::MIN)
    }
}

impl<T: IsColorChannel> From<Rgba<T>> for Rgb<T> {
    fn from(rgba: Rgba<T>) -> Self {
        rgba.without_alpha()
    }
}

impl From<Rgb<u8>> for Rgb<f32> {
    fn from(Rgb { red, green, blue }: Rgb<u8>) -> Self {
        Self::new(
            red as f32 / 255.0,
            green as f32 / 255.0,
            blue as f32 / 255.0,
        )
    }
}

impl From<Rgb<f32>> for Rgb<u8> {
    fn from(Rgb { red, green, blue }: Rgb<f32>) -> Self {
        Self::new(
            (red.clamp_channel() * 255.0).round() as u8,
            (green.clamp_channel() * 255.0).round() as u8,
            (blue.clamp_channel() * 255.0).round() as u8,
        )
    }
}

impl From<Hsl<f32>> for Rgb<f32> {
    fn from(Hsl { hue, saturation, lightness }: Hsl<f32>) -> Self {
        let hue = hue.unwind_channel();
        let saturation = saturation.clamp_channel();
        let lightness = lightness.clamp_channel();

        let c = (1.0 - (2.0 * lightness - 1.0).abs()) * saturation;
        let x = c * (1.0 - ((hue * 6.0) % 2.0 - 1.0).abs());
        let m = lightness - c / 2.0;

        hue_cxm_to_rgb(hue, c, x, m)
    }
}

impl From<Hsv<f32>> for Rgb<f32> {
    fn from(Hsv { hue, saturation, value }: Hsv<f32>) -> Self {
        let hue = hue.unwind_channel();
        let saturation = saturation.clamp_channel();
        let value = value.clamp_channel();

        let c = value * saturation;
        let x = c * (1.0 - ((hue * 6.0) % 2.0 - 1.0).abs());
        let m = value - c;

        hue_cxm_to_rgb(hue, c, x, m)
    }
}

fn hue_cxm_to_rgb(hue: f32, c: f32, x: f32, m: f32) -> Rgb<f32> {
    let mut red = 0.0;
    let mut green = 0.0;
    let mut blue = 0.0;

    if (hue >= 0.0 && hue < 1.0/6.0) || hue >= 1.0 {
        red = c;
        green = x;
    } else if hue >= 1.0/6.0 && hue < 1.0/3.0 {
        red = x;
        green = c;
    } else if hue >= 1.0/3.0 && hue < 0.5 {
        green = c;
        blue = x;
    } else if hue >= 0.5 && hue < 2.0/3.0 {
        green = x;
        blue = c;
    } else if hue >= 2.0/3.0 && hue < 5.0/6.0 {
        red = x;
        blue = c;
    } else if hue >= 5.0/6.0 && hue < 1.0 {
        red = c;
        blue = x;
    }

    red += m;
    green += m;
    blue += m;

    Rgb::new(red, green, blue)
}

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

    #[test]
    fn from_u8() {
        assert_eq!(Rgb::<f32>::from(Rgb::<u8>::new(84, 37, 181)),
                   Rgb::new(84.0/255.0, 37.0/255.0, 181.0/255.0));
    }

    #[test]
    fn from_f32() {
        assert_eq!(Rgb::<u8>::from(Rgb::<f32>::new(84.0/255.0, 37.0/255.0, 181.0/255.0)),
                   Rgb::new(84, 37, 181));
    }

    #[test]
    fn from_hsl() {
        assert_eq!(Rgb::<u8>::from(Rgb::from(Hsl::<f32>::from(Hsl::new(184u8, 168, 109)))),
                   Rgb::new(84u8, 37, 181));
    }

    #[test]
    fn from_hsv() {
        assert_eq!(Rgb::<u8>::from(Rgb::from(Hsv::<f32>::from(Hsv::new(12u8, 107, 66)))),
                   Rgb::new(66u8, 46, 38));
    }
}