use crate::convert::convert;
use crate::spaces::{
Hsl, Hsv, Hwb, Lab, Lab65, Lch, Lch65, LinearRgb, Oklab, Oklch, Rgb, Xyz50, Xyz65, Yiq,
};
use crate::traits::ColorSpace;
use crate::Color;
#[derive(Clone, Copy)]
pub(crate) struct ChannelInfo {
pub(crate) is_hue: bool,
}
#[derive(Clone, Copy)]
pub(crate) struct ModeShape {
pub(crate) channels: [ChannelInfo; 3],
pub(crate) hue_diff: Option<HueDiffKind>,
}
#[derive(Clone, Copy)]
pub(crate) enum HueDiffKind {
Chroma,
Saturation,
Naive,
}
const fn linear() -> [ChannelInfo; 3] {
[
ChannelInfo { is_hue: false },
ChannelInfo { is_hue: false },
ChannelInfo { is_hue: false },
]
}
const fn hsl_like() -> [ChannelInfo; 3] {
[
ChannelInfo { is_hue: true },
ChannelInfo { is_hue: false },
ChannelInfo { is_hue: false },
]
}
const fn lch_like() -> [ChannelInfo; 3] {
[
ChannelInfo { is_hue: false },
ChannelInfo { is_hue: false },
ChannelInfo { is_hue: true },
]
}
pub(crate) fn mode_shape(mode: &str) -> ModeShape {
match mode {
"rgb" | "lrgb" | "lab" | "lab65" | "oklab" | "xyz50" | "xyz65" | "jab" | "itp" | "yiq" => {
ModeShape {
channels: linear(),
hue_diff: None,
}
}
"lch" | "lch65" | "oklch" => ModeShape {
channels: lch_like(),
hue_diff: Some(HueDiffKind::Chroma),
},
"hsl" | "hsv" => ModeShape {
channels: hsl_like(),
hue_diff: Some(HueDiffKind::Saturation),
},
"hwb" => ModeShape {
channels: hsl_like(),
hue_diff: Some(HueDiffKind::Naive),
},
other => panic!("difference: unknown mode '{other}'"),
}
}
pub(crate) fn extract(c: Color, mode: &str) -> [f64; 3] {
match mode {
"rgb" => {
let v: Rgb = match c {
Color::Rgb(x) => x,
Color::LinearRgb(x) => x.into(),
Color::Hsl(x) => x.into(),
Color::Hsv(x) => x.into(),
Color::Hwb(x) => Hsv::from(x).into(),
other => convert::<Xyz65, Rgb>(to_xyz65(other)),
};
[v.r, v.g, v.b]
}
"lrgb" => {
let v: LinearRgb = match c {
Color::Rgb(x) => x.into(),
Color::LinearRgb(x) => x,
other => convert::<Xyz65, LinearRgb>(to_xyz65(other)),
};
[v.r, v.g, v.b]
}
"hsl" => {
let v: Hsl = match c {
Color::Hsl(x) => x,
Color::Rgb(x) => x.into(),
Color::LinearRgb(x) => Rgb::from(x).into(),
Color::Hsv(x) => Rgb::from(x).into(),
Color::Hwb(x) => Rgb::from(Hsv::from(x)).into(),
other => convert::<Xyz65, Rgb>(to_xyz65(other)).into(),
};
[v.h, v.s, v.l]
}
"hsv" => {
let v: Hsv = match c {
Color::Hsv(x) => x,
Color::Hwb(x) => x.into(),
Color::Rgb(x) => x.into(),
Color::LinearRgb(x) => Rgb::from(x).into(),
Color::Hsl(x) => Rgb::from(x).into(),
other => convert::<Xyz65, Rgb>(to_xyz65(other)).into(),
};
[v.h, v.s, v.v]
}
"hwb" => {
let v: Hwb = match c {
Color::Hwb(x) => x,
Color::Hsv(x) => x.into(),
Color::Rgb(x) => Hsv::from(x).into(),
Color::LinearRgb(x) => Hsv::from(Rgb::from(x)).into(),
Color::Hsl(x) => Hsv::from(Rgb::from(x)).into(),
other => Hsv::from(convert::<Xyz65, Rgb>(to_xyz65(other))).into(),
};
[v.h, v.w, v.b]
}
"lab" => {
let v: Lab = match c {
Color::Lab(x) => x,
Color::Lch(x) => x.into(),
Color::Xyz50(x) => x.into(),
Color::Rgb(x) => x.into(),
other => convert::<Xyz65, Lab>(to_xyz65(other)),
};
[v.l, v.a, v.b]
}
"lab65" => {
let v: Lab65 = match c {
Color::Lab65(x) => x,
Color::Lch65(x) => x.into(),
Color::Rgb(x) => x.into(),
other => Lab65::from(to_xyz65(other)),
};
[v.l, v.a, v.b]
}
"lch" => {
let v: Lch = match c {
Color::Lch(x) => x,
Color::Lab(x) => x.into(),
Color::Rgb(x) => x.into(),
other => convert::<Xyz65, Lab>(to_xyz65(other)).into(),
};
[v.l, v.c, v.h]
}
"lch65" => {
let v: Lch65 = match c {
Color::Lch65(x) => x,
Color::Lab65(x) => x.into(),
Color::Rgb(x) => x.into(),
other => Lab65::from(to_xyz65(other)).into(),
};
[v.l, v.c, v.h]
}
"oklab" => {
let v: Oklab = match c {
Color::Oklab(x) => x,
Color::Oklch(x) => x.into(),
Color::Rgb(x) => x.into(),
Color::LinearRgb(x) => x.into(),
other => convert::<Xyz65, Oklab>(to_xyz65(other)),
};
[v.l, v.a, v.b]
}
"oklch" => {
let v: Oklch = match c {
Color::Oklch(x) => x,
Color::Oklab(x) => x.into(),
Color::Rgb(x) => x.into(),
Color::LinearRgb(x) => Oklab::from(x).into(),
other => convert::<Xyz65, Oklab>(to_xyz65(other)).into(),
};
[v.l, v.c, v.h]
}
"xyz50" => {
let v: Xyz50 = match c {
Color::Xyz50(x) => x,
Color::Lab(x) => x.into(),
other => convert::<Xyz65, Xyz50>(to_xyz65(other)),
};
[v.x, v.y, v.z]
}
"xyz65" => {
let v: Xyz65 = to_xyz65(c);
[v.x, v.y, v.z]
}
"jab" => {
let v: crate::spaces::Jab = match c {
Color::Jab(x) => x,
Color::Rgb(x) => x.into(),
other => convert::<Xyz65, crate::spaces::Jab>(to_xyz65(other)),
};
[v.j, v.a, v.b]
}
"itp" => {
let v: crate::spaces::Itp = convert::<Xyz65, crate::spaces::Itp>(to_xyz65(c));
[v.i, v.t, v.p]
}
"yiq" => {
let v: Yiq = match c {
Color::Yiq(x) => x,
Color::Rgb(x) => x.into(),
Color::LinearRgb(x) => Rgb::from(x).into(),
other => convert::<Xyz65, Rgb>(to_xyz65(other)).into(),
};
[v.y, v.i, v.q]
}
other => panic!("difference: unknown mode '{other}'"),
}
}
pub(crate) fn to_lab65(c: Color) -> (f64, f64, f64) {
let xyz = to_xyz65(c);
xyz65_to_lab65(xyz.x, xyz.y, xyz.z)
}
pub(crate) fn to_xyz65(c: Color) -> Xyz65 {
match c {
Color::Rgb(x) => x.to_xyz65(),
Color::LinearRgb(x) => x.to_xyz65(),
Color::Hsl(x) => x.to_xyz65(),
Color::Hsv(x) => x.to_xyz65(),
Color::Hwb(x) => x.to_xyz65(),
Color::Lab(x) => x.to_xyz65(),
Color::Lab65(x) => x.to_xyz65(),
Color::Lch(x) => x.to_xyz65(),
Color::Lch65(x) => x.to_xyz65(),
Color::Oklab(x) => x.to_xyz65(),
Color::Oklch(x) => x.to_xyz65(),
Color::Xyz50(x) => x.to_xyz65(),
Color::Xyz65(x) => x,
Color::P3(x) => x.to_xyz65(),
Color::Rec2020(x) => x.to_xyz65(),
Color::A98(x) => x.to_xyz65(),
Color::ProphotoRgb(x) => x.to_xyz65(),
Color::Cubehelix(x) => x.to_xyz65(),
Color::Dlab(x) => x.to_xyz65(),
Color::Dlch(x) => x.to_xyz65(),
Color::Jab(x) => x.to_xyz65(),
Color::Jch(x) => x.to_xyz65(),
Color::Yiq(x) => x.to_xyz65(),
Color::Hsi(x) => x.to_xyz65(),
Color::Hsluv(x) => x.to_xyz65(),
Color::Hpluv(x) => x.to_xyz65(),
Color::Okhsl(x) => x.to_xyz65(),
Color::Okhsv(x) => x.to_xyz65(),
Color::Itp(x) => x.to_xyz65(),
Color::Xyb(x) => x.to_xyz65(),
Color::Luv(x) => x.to_xyz65(),
Color::Lchuv(x) => x.to_xyz65(),
Color::Prismatic(x) => x.to_xyz65(),
}
}
const D65_X: f64 = 0.3127 / 0.329;
const D65_Y: f64 = 1.0;
const D65_Z: f64 = (1.0 - 0.3127 - 0.329) / 0.329;
const K: f64 = 24389.0 / 27.0;
const E: f64 = 216.0 / 24389.0;
#[inline]
fn f_forward(value: f64) -> f64 {
if value > E {
value.cbrt()
} else {
(K * value + 16.0) / 116.0
}
}
fn xyz65_to_lab65(x: f64, y: f64, z: f64) -> (f64, f64, f64) {
let f0 = f_forward(x / D65_X);
let f1 = f_forward(y / D65_Y);
let f2 = f_forward(z / D65_Z);
let l = 116.0 * f1 - 16.0;
let a = 500.0 * (f0 - f1);
let b = 200.0 * (f1 - f2);
(l, a, b)
}
#[inline]
pub(crate) fn normalize_hue(h: f64) -> f64 {
let h = h % 360.0;
if h < 0.0 {
h + 360.0
} else {
h
}
}