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 Hwb {}
impl Hwb {
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 whiteness<P>(p: P) -> P::Chan
where
P: Pixel<Model = Self>,
{
p.get::<1>()
}
pub fn whiteness_mut<P>(p: &mut P) -> &mut P::Chan
where
P: Pixel<Model = Self>,
{
p.get_mut::<1>()
}
pub fn blackness<P>(p: P) -> P::Chan
where
P: Pixel<Model = Self>,
{
p.get::<2>()
}
pub fn blackness_mut<P>(p: &mut P) -> &mut P::Chan
where
P: Pixel<Model = Self>,
{
p.get_mut::<2>()
}
fn whiteness_blackness<P>(p: P) -> (P::Chan, P::Chan)
where
P: Pixel<Model = Self>,
{
let whiteness = Hwb::whiteness(p);
let blackness = Hwb::blackness(p);
if whiteness > P::Chan::MAX - blackness {
let (w, b) = (whiteness.to_f32(), blackness.to_f32());
let ratio = 1.0 / (w + b);
(P::Chan::from(w * ratio), P::Chan::from(b * ratio))
} else {
(whiteness, blackness)
}
}
}
impl ColorModel for Hwb {
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 (whiteness, blackness) = Self::whiteness_blackness(p);
let v = P::Chan::MAX - blackness;
let chroma = v - whiteness;
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 = v - chroma;
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 sat_v = chroma / val;
let whiteness = (P::Chan::MAX - sat_v) * val;
let blackness = P::Chan::MAX - val;
P::from_channels(&[hue, whiteness, blackness, alpha])
}
}
pub type Hwb8 = Pix<3, Ch8, Hwb, Straight, Linear>;
pub type Hwb16 = Pix<3, Ch16, Hwb, Straight, Linear>;
pub type Hwb32 = Pix<3, Ch32, Hwb, Straight, Linear>;
pub type Hwba8 = Pix<4, Ch8, Hwb, Straight, Linear>;
pub type Hwba16 = Pix<4, Ch16, Hwb, Straight, Linear>;
pub type Hwba32 = Pix<4, Ch32, Hwb, Straight, Linear>;
pub type Hwba8p = Pix<4, Ch8, Hwb, Premultiplied, Linear>;
pub type Hwba16p = Pix<4, Ch16, Hwb, Premultiplied, Linear>;
pub type Hwba32p = Pix<4, Ch32, Hwb, Premultiplied, Linear>;
pub type SHwb8 = Pix<3, Ch8, Hwb, Straight, Srgb>;
pub type SHwb16 = Pix<3, Ch16, Hwb, Straight, Srgb>;
pub type SHwb32 = Pix<3, Ch32, Hwb, Straight, Srgb>;
pub type SHwba8 = Pix<4, Ch8, Hwb, Straight, Srgb>;
pub type SHwba16 = Pix<4, Ch16, Hwb, Straight, Srgb>;
pub type SHwba32 = Pix<4, Ch32, Hwb, Straight, Srgb>;
pub type SHwba8p = Pix<4, Ch8, Hwb, Premultiplied, Srgb>;
pub type SHwba16p = Pix<4, Ch16, Hwb, Premultiplied, Srgb>;
pub type SHwba32p = Pix<4, Ch32, Hwb, Premultiplied, Srgb>;
#[cfg(test)]
mod test {
use crate::el::Pixel;
use crate::hwb::*;
use crate::rgb::*;
#[test]
fn hwb_to_rgb() {
assert_eq!(Rgb8::new(127, 127, 127), Hwb8::new(0, 128, 128).convert());
assert_eq!(Rgb8::new(127, 127, 127), Hwb8::new(0, 255, 255).convert());
assert_eq!(Rgb8::new(85, 85, 85), Hwb8::new(0, 128, 255).convert());
assert_eq!(Rgb8::new(255, 0, 0), Hwb8::new(0, 0, 0).convert());
assert_eq!(
Rgb8::new(255, 255, 128),
Hwb32::new(60.0 / 360.0, 0.5, 0.0).convert(),
);
assert_eq!(Rgb8::new(0, 127, 0), Hwb16::new(21845, 0, 32768).convert());
assert_eq!(
Rgb8::new(128, 255, 255),
Hwb32::new(0.5, 0.5, 0.0).convert(),
);
assert_eq!(
Rgb8::new(0, 0, 128),
Hwb32::new(240.0 / 360.0, 0.0, 0.5).convert(),
);
assert_eq!(
Rgb8::new(255, 128, 255),
Hwb32::new(300.0 / 360.0, 0.5, 0.0).convert(),
);
}
#[test]
fn rgb_to_hwb() {
assert_eq!(Hwb8::new(0, 0, 0), Rgb8::new(255, 0, 0).convert());
assert_eq!(Hwb8::new(0, 64, 0), Rgb8::new(255, 64, 64).convert());
assert_eq!(
Hwb32::new(60.0 / 360.0, 0.0, 0.50196075),
Rgb8::new(127, 127, 0).convert(),
);
assert_eq!(
Hwb16::new(21845, 8224, 0),
Rgb8::new(32, 255, 32).convert(),
);
assert_eq!(
Hwb32::new(0.5, 0.0, 0.7490196),
Rgb8::new(0, 64, 64).convert(),
);
assert_eq!(
Hwb32::new(240.0 / 360.0, 0.7529412, 0.0),
Rgb8::new(192, 192, 255).convert(),
);
assert_eq!(
Hwb32::new(300.0 / 360.0, 0.0, 0.0),
Rgb8::new(255, 0, 255).convert(),
);
}
}