use crate::ColorModel;
use crate::chan::{
Ch8, Ch16, Ch32, Channel, Linear, Premultiplied, Srgb, Straight,
};
use crate::el::{Pix, PixRgba, Pixel};
use crate::hue::{Hexcone, rgb_to_hue_chroma_value};
use std::ops::Range;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Hsl {}
impl Hsl {
pub fn hue<P>(p: P) -> P::Chan
where
P: Pixel<Model = Self>,
{
p.get::<0>()
}
pub fn hue_mut<P>(p: &mut P) -> &mut P::Chan
where
P: Pixel<Model = Self>,
{
p.get_mut::<0>()
}
pub fn saturation<P>(p: P) -> P::Chan
where
P: Pixel<Model = Self>,
{
p.get::<1>()
}
pub fn saturation_mut<P>(p: &mut P) -> &mut P::Chan
where
P: Pixel<Model = Self>,
{
p.get_mut::<1>()
}
pub fn lightness<P>(p: P) -> P::Chan
where
P: Pixel<Model = Self>,
{
p.get::<2>()
}
pub fn lightness_mut<P>(p: &mut P) -> &mut P::Chan
where
P: Pixel<Model = Self>,
{
p.get_mut::<2>()
}
}
impl ColorModel for Hsl {
const CIRCULAR: Range<usize> = 0..1;
const LINEAR: Range<usize> = 1..3;
const ALPHA: usize = 3;
fn into_rgba<P>(p: P) -> PixRgba<P>
where
P: Pixel<Model = Self>,
{
let vl = 1.0 - (2.0 * Self::lightness(p).to_f32() - 1.0).abs();
let chroma = P::Chan::from(vl) * Self::saturation(p);
let hp = Self::hue(p).to_f32() * 6.0; let hc = Hexcone::from_hue_prime(hp);
let (red, green, blue) = hc.rgb(chroma);
let m = Self::lightness(p) - chroma * P::Chan::from(0.5);
PixRgba::<P>::new::<P::Chan>(red + m, green + m, blue + m, p.alpha())
}
fn from_rgba<P>(rgba: PixRgba<P>) -> P
where
P: Pixel<Model = Self>,
{
let chan = rgba.channels();
let red = chan[0];
let green = chan[1];
let blue = chan[2];
let alpha = chan[3];
let (hue, chroma, val) = rgb_to_hue_chroma_value(red, green, blue);
let lightness = val - chroma * P::Chan::from(0.5);
let min_l = lightness.min(P::Chan::MAX - lightness);
let sat_l = (val - lightness) / min_l;
P::from_channels(&[hue, sat_l, lightness, alpha])
}
}
pub type Hsl8 = Pix<3, Ch8, Hsl, Straight, Linear>;
pub type Hsl16 = Pix<3, Ch16, Hsl, Straight, Linear>;
pub type Hsl32 = Pix<3, Ch32, Hsl, Straight, Linear>;
pub type Hsla8 = Pix<4, Ch8, Hsl, Straight, Linear>;
pub type Hsla16 = Pix<4, Ch16, Hsl, Straight, Linear>;
pub type Hsla32 = Pix<4, Ch32, Hsl, Straight, Linear>;
pub type Hsla8p = Pix<4, Ch8, Hsl, Premultiplied, Linear>;
pub type Hsla16p = Pix<4, Ch16, Hsl, Premultiplied, Linear>;
pub type Hsla32p = Pix<4, Ch32, Hsl, Premultiplied, Linear>;
pub type SHsl8 = Pix<3, Ch8, Hsl, Straight, Srgb>;
pub type SHsl16 = Pix<3, Ch16, Hsl, Straight, Srgb>;
pub type SHsl32 = Pix<3, Ch32, Hsl, Straight, Srgb>;
pub type SHsla8 = Pix<4, Ch8, Hsl, Straight, Srgb>;
pub type SHsla16 = Pix<4, Ch16, Hsl, Straight, Srgb>;
pub type SHsla32 = Pix<4, Ch32, Hsl, Straight, Srgb>;
pub type SHsla8p = Pix<4, Ch8, Hsl, Premultiplied, Srgb>;
pub type SHsla16p = Pix<4, Ch16, Hsl, Premultiplied, Srgb>;
pub type SHsla32p = Pix<4, Ch32, Hsl, Premultiplied, Srgb>;
#[cfg(test)]
mod test {
use crate::el::Pixel;
use crate::hsl::*;
use crate::rgb::*;
#[test]
fn hsl_to_rgb() {
assert_eq!(Rgb8::new(255, 1, 1), Hsl8::new(0, 255, 128).convert());
assert_eq!(
Rgb8::new(255, 255, 0),
Hsl32::new(60.0 / 360.0, 1.0, 0.5).convert(),
);
assert_eq!(
Rgb8::new(0, 255, 0),
Hsl16::new(21845, 65535, 32768).convert(),
);
assert_eq!(Rgb8::new(0, 255, 255), Hsl32::new(0.5, 1.0, 0.5).convert());
assert_eq!(
Rgb8::new(0, 0, 255),
Hsl32::new(240.0 / 360.0, 1.0, 0.5).convert(),
);
assert_eq!(
Rgb8::new(255, 0, 255),
Hsl32::new(300.0 / 360.0, 1.0, 0.5).convert(),
);
}
#[test]
fn rgb_to_hsl() {
assert_eq!(Hsl8::new(0, 255, 127), Rgb8::new(255, 0, 0).convert());
assert_eq!(
Hsl32::new(60.0 / 360.0, 1.0, 0.5),
Rgb8::new(255, 255, 0).convert(),
);
assert_eq!(
Hsl16::new(21845, 65535, 32767),
Rgb8::new(0, 255, 0).convert(),
);
assert_eq!(Hsl32::new(0.5, 1.0, 0.5), Rgb8::new(0, 255, 255).convert());
assert_eq!(
Hsl32::new(240.0 / 360.0, 1.0, 0.5),
Rgb8::new(0, 0, 255).convert(),
);
assert_eq!(
Hsl32::new(300.0 / 360.0, 1.0, 0.5),
Rgb8::new(255, 0, 255).convert(),
);
}
}