use crate::color::Color;
use crate::spaces::{
Dlab, Dlch, Itp, Jab, Lab, Lab65, LinearRgb, Luv, Oklab, ProphotoRgb, Rec2020, Rgb, Xyz50, A98,
P3,
};
use crate::traits::ColorSpace;
pub fn convert<A: ColorSpace, B: ColorSpace>(c: A) -> B {
B::from_xyz65(c.to_xyz65())
}
pub fn convert_culori<A, B>(c: A) -> B
where
A: ColorSpace + Into<Color>,
B: ColorSpace,
B: TryFrom<Color>,
<B as TryFrom<Color>>::Error: core::fmt::Debug,
{
let color: Color = c.into();
let result = color
.convert_to(B::MODE)
.expect("known target mode for ColorSpace");
B::try_from(result).expect("convert_to returns the requested mode")
}
pub fn converter(mode: &'static str) -> Option<impl Fn(&Color) -> Color> {
if !is_known_mode(mode) {
return None;
}
Some(move |c: &Color| {
c.convert_to(mode)
.expect("mode validated at converter() call")
})
}
#[inline]
fn via_xyz65<S, T>(c: S) -> T
where
S: ColorSpace,
T: ColorSpace,
{
T::from_xyz65(c.to_xyz65())
}
#[inline]
fn rgb_from_xyz50(xyz50: Xyz50) -> Rgb {
Rgb::from_xyz65(xyz50.to_xyz65())
}
#[inline]
fn via_xyz50<S>(c: S) -> Rgb
where
S: ColorSpace,
Xyz50: From<S>,
{
rgb_from_xyz50(Xyz50::from(c))
}
#[inline]
fn to_rgb_via_culori(c: Color) -> Rgb {
match c {
Color::Rgb(c) => c,
Color::LinearRgb(c) => c.into(),
Color::Hsl(c) => c.into(),
Color::Hsv(c) => c.into(),
Color::Hwb(c) => c.into(),
Color::Lab(c) => rgb_from_xyz50(Xyz50::from(c)),
Color::Lab65(c) => via_xyz65(c),
Color::Lch(c) => rgb_from_xyz50(Xyz50::from(Lab::from(c))),
Color::Lch65(c) => via_xyz65(Lab65::from(c)),
Color::Oklab(c) => LinearRgb::from(c).into(),
Color::Oklch(c) => LinearRgb::from(Oklab::from(c)).into(),
Color::Xyz50(c) => rgb_from_xyz50(c),
Color::Xyz65(c) => via_xyz65(c),
Color::P3(c) => via_xyz65(c),
Color::Rec2020(c) => via_xyz65(c),
Color::A98(c) => via_xyz65(c),
Color::ProphotoRgb(c) => rgb_from_xyz50(c.to_xyz50()),
Color::Cubehelix(c) => c.into(),
Color::Dlab(c) => via_xyz65(dlab_to_lab65(c)),
Color::Dlch(c) => via_xyz65(dlch_to_lab65(c)),
Color::Jab(c) => via_xyz65(c),
Color::Jch(c) => via_xyz65(Jab::from(c)),
Color::Yiq(c) => c.into(),
Color::Hsi(c) => c.into(),
Color::Hsluv(c) => c.into(),
Color::Hpluv(c) => c.into(),
Color::Okhsl(c) => LinearRgb::from(Oklab::from(c)).into(),
Color::Okhsv(c) => LinearRgb::from(Oklab::from(c)).into(),
Color::Itp(c) => via_xyz65(c),
Color::Xyb(c) => c.into(),
Color::Luv(c) => rgb_from_xyz50(Xyz50::from(c)),
Color::Lchuv(c) => rgb_from_xyz50(Xyz50::from(Luv::from(c))),
Color::Prismatic(c) => c.into(),
}
}
fn lab65_to_dlab(c: Lab65) -> Dlab {
Dlab::from_xyz65(c.to_xyz65())
}
fn lab65_to_dlch(c: Lab65) -> Dlch {
Dlch::from_xyz65(c.to_xyz65())
}
fn dlab_to_lab65(c: Dlab) -> Lab65 {
Lab65::from_xyz65(c.to_xyz65())
}
fn dlch_to_lab65(c: Dlch) -> Lab65 {
Lab65::from_xyz65(c.to_xyz65())
}
fn dlab_to_dlch(c: Dlab) -> Dlch {
Dlch::from_xyz65(c.to_xyz65())
}
fn dlch_to_dlab(c: Dlch) -> Dlab {
Dlab::from_xyz65(c.to_xyz65())
}
impl Color {
pub fn convert_to(&self, target_mode: &str) -> Option<Color> {
if !is_known_mode(target_mode) {
return None;
}
if self.mode() == target_mode {
return Some(*self);
}
Some(dispatch_convert(*self, target_mode))
}
}
fn is_known_mode(m: &str) -> bool {
matches!(
m,
"rgb"
| "lrgb"
| "hsl"
| "hsv"
| "hwb"
| "lab"
| "lab65"
| "lch"
| "lch65"
| "oklab"
| "oklch"
| "xyz50"
| "xyz65"
| "p3"
| "rec2020"
| "a98"
| "prophoto"
| "cubehelix"
| "dlab"
| "dlch"
| "jab"
| "jch"
| "yiq"
| "hsi"
| "hsluv"
| "hpluv"
| "okhsl"
| "okhsv"
| "itp"
| "xyb"
| "luv"
| "lchuv"
| "prismatic"
)
}
fn dispatch_convert(c: Color, to: &str) -> Color {
if let Some(direct) = direct_edge(c, to) {
return direct;
}
let rgb = to_rgb_via_culori(c);
if to == "rgb" {
return Color::Rgb(rgb);
}
rgb_to(rgb, to).expect("every known mode has a direct edge from rgb")
}
fn direct_edge(c: Color, to: &str) -> Option<Color> {
use Color as C;
Some(match (c, to) {
(C::Rgb(c), to) => return rgb_to(c, to),
(C::A98(c), "xyz65") => Color::Xyz65(c.to_xyz65()),
(C::A98(c), "rgb") => Color::Rgb(via_xyz65(c)),
(C::P3(c), "xyz65") => Color::Xyz65(c.to_xyz65()),
(C::P3(c), "rgb") => Color::Rgb(via_xyz65(c)),
(C::Rec2020(c), "xyz65") => Color::Xyz65(c.to_xyz65()),
(C::Rec2020(c), "rgb") => Color::Rgb(via_xyz65(c)),
(C::ProphotoRgb(c), "xyz50") => Color::Xyz50(c.to_xyz50()),
(C::ProphotoRgb(c), "rgb") => Color::Rgb(rgb_from_xyz50(c.to_xyz50())),
(C::Itp(c), "xyz65") => Color::Xyz65(c.to_xyz65()),
(C::Itp(c), "rgb") => Color::Rgb(via_xyz65(c)),
(C::Jab(c), "jch") => Color::Jch(c.into()),
(C::Jab(c), "xyz65") => Color::Xyz65(c.to_xyz65()),
(C::Jab(c), "rgb") => Color::Rgb(via_xyz65(c)),
(C::Jch(c), "jab") => Color::Jab(c.into()),
(C::Jch(c), "rgb") => Color::Rgb(via_xyz65(Jab::from(c))),
(C::Lab(c), "lch") => Color::Lch(c.into()),
(C::Lab(c), "xyz50") => Color::Xyz50(c.into()),
(C::Lab(c), "rgb") => Color::Rgb(rgb_from_xyz50(Xyz50::from(c))),
(C::Lab65(c), "lch65") => Color::Lch65(c.into()),
(C::Lab65(c), "dlab") => Color::Dlab(lab65_to_dlab(c)),
(C::Lab65(c), "dlch") => Color::Dlch(lab65_to_dlch(c)),
(C::Lab65(c), "xyz65") => Color::Xyz65(c.to_xyz65()),
(C::Lab65(c), "rgb") => Color::Rgb(via_xyz65(c)),
(C::Lch(c), "lab") => Color::Lab(c.into()),
(C::Lch(c), "rgb") => Color::Rgb(rgb_from_xyz50(Xyz50::from(Lab::from(c)))),
(C::Lch65(c), "lab65") => Color::Lab65(c.into()),
(C::Lch65(c), "rgb") => Color::Rgb(via_xyz65(Lab65::from(c))),
(C::Lchuv(c), "luv") => Color::Luv(c.into()),
(C::Lchuv(c), "rgb") => Color::Rgb(via_xyz50(Luv::from(c))),
(C::LinearRgb(c), "oklab") => Color::Oklab(c.into()),
(C::LinearRgb(c), "rgb") => Color::Rgb(c.into()),
(C::Luv(c), "lchuv") => Color::Lchuv(c.into()),
(C::Luv(c), "xyz50") => Color::Xyz50(c.into()),
(C::Luv(c), "rgb") => Color::Rgb(via_xyz50(c)),
(C::Okhsl(c), "oklab") => Color::Oklab(c.into()),
(C::Okhsl(c), "rgb") => Color::Rgb(LinearRgb::from(Oklab::from(c)).into()),
(C::Okhsv(c), "oklab") => Color::Oklab(c.into()),
(C::Okhsv(c), "rgb") => Color::Rgb(LinearRgb::from(Oklab::from(c)).into()),
(C::Oklab(c), "lrgb") => Color::LinearRgb(c.into()),
(C::Oklab(c), "okhsl") => Color::Okhsl(c.into()),
(C::Oklab(c), "okhsv") => Color::Okhsv(c.into()),
(C::Oklab(c), "oklch") => Color::Oklch(c.into()),
(C::Oklab(c), "rgb") => Color::Rgb(LinearRgb::from(c).into()),
(C::Oklch(c), "oklab") => Color::Oklab(c.into()),
(C::Oklch(c), "rgb") => Color::Rgb(LinearRgb::from(Oklab::from(c)).into()),
(C::Xyz50(c), "lab") => Color::Lab(c.into()),
(C::Xyz50(c), "luv") => Color::Luv(c.into()),
(C::Xyz50(c), "prophoto") => Color::ProphotoRgb(ProphotoRgb::from_xyz50(c)),
(C::Xyz50(c), "xyz65") => Color::Xyz65(c.to_xyz65()),
(C::Xyz50(c), "rgb") => Color::Rgb(via_xyz50(c)),
(C::Xyz65(c), "a98") => Color::A98(A98::from_xyz65(c)),
(C::Xyz65(c), "itp") => Color::Itp(Itp::from_xyz65(c)),
(C::Xyz65(c), "jab") => Color::Jab(Jab::from_xyz65(c)),
(C::Xyz65(c), "lab65") => Color::Lab65(Lab65::from_xyz65(c)),
(C::Xyz65(c), "p3") => Color::P3(P3::from_xyz65(c)),
(C::Xyz65(c), "rec2020") => Color::Rec2020(Rec2020::from_xyz65(c)),
(C::Xyz65(c), "xyz50") => Color::Xyz50(Xyz50::from_xyz65(c)),
(C::Xyz65(c), "rgb") => Color::Rgb(Rgb::from_xyz65(c)),
(C::Dlab(c), "dlch") => Color::Dlch(dlab_to_dlch(c)),
(C::Dlab(c), "lab65") => Color::Lab65(dlab_to_lab65(c)),
(C::Dlab(c), "rgb") => Color::Rgb(via_xyz65(dlab_to_lab65(c))),
(C::Dlch(c), "dlab") => Color::Dlab(dlch_to_dlab(c)),
(C::Dlch(c), "lab65") => Color::Lab65(dlch_to_lab65(c)),
(C::Dlch(c), "rgb") => Color::Rgb(via_xyz65(dlch_to_lab65(c))),
(C::Cubehelix(c), "rgb") => Color::Rgb(c.into()),
(C::Hsi(c), "rgb") => Color::Rgb(c.into()),
(C::Hsl(c), "rgb") => Color::Rgb(c.into()),
(C::Hsv(c), "rgb") => Color::Rgb(c.into()),
(C::Hwb(c), "rgb") => Color::Rgb(c.into()),
(C::Xyb(c), "rgb") => Color::Rgb(c.into()),
(C::Yiq(c), "rgb") => Color::Rgb(c.into()),
(C::Hsluv(c), "rgb") => Color::Rgb(c.into()),
(C::Hpluv(c), "rgb") => Color::Rgb(c.into()),
(C::Prismatic(c), "rgb") => Color::Rgb(c.into()),
_ => return None,
})
}
fn rgb_to(c: Rgb, to: &str) -> Option<Color> {
Some(match to {
"rgb" => Color::Rgb(c),
"lrgb" => Color::LinearRgb(c.into()),
"hsl" => Color::Hsl(c.into()),
"hsv" => Color::Hsv(c.into()),
"hwb" => Color::Hwb(c.into()),
"lab" => Color::Lab(c.into()),
"lab65" => Color::Lab65(c.into()),
"lch" => Color::Lch(c.into()),
"lch65" => Color::Lch65(c.into()),
"oklab" => Color::Oklab(c.into()),
"oklch" => Color::Oklch(c.into()),
"xyz50" => Color::Xyz50(Xyz50::from_xyz65(c.to_xyz65())),
"xyz65" => Color::Xyz65(c.to_xyz65()),
"p3" => Color::P3(P3::from_xyz65(c.to_xyz65())),
"rec2020" => Color::Rec2020(Rec2020::from_xyz65(c.to_xyz65())),
"a98" => Color::A98(A98::from_xyz65(c.to_xyz65())),
"prophoto" => Color::ProphotoRgb(ProphotoRgb::from_xyz50(Xyz50::from_xyz65(c.to_xyz65()))),
"cubehelix" => Color::Cubehelix(c.into()),
"dlab" => Color::Dlab(c.into()),
"dlch" => Color::Dlch(c.into()),
"jab" => Color::Jab(c.into()),
"jch" => Color::Jch(c.into()),
"yiq" => Color::Yiq(c.into()),
"hsi" => Color::Hsi(c.into()),
"hsluv" => Color::Hsluv(c.into()),
"hpluv" => Color::Hpluv(c.into()),
"okhsl" => Color::Okhsl(c.into()),
"okhsv" => Color::Okhsv(c.into()),
"itp" => Color::Itp(Itp::from_xyz65(c.to_xyz65())),
"xyb" => Color::Xyb(c.into()),
"luv" => Color::Luv(c.into()),
"lchuv" => Color::Lchuv(c.into()),
"prismatic" => Color::Prismatic(c.into()),
_ => return None,
})
}