use crate::gamut::in_gamut::{
color_to_a98, color_to_p3, color_to_prophoto, color_to_rec2020, color_to_rgb, in_gamut,
};
use crate::spaces::{Hsl, Hsv, Hwb, Lch, LinearRgb, Oklch, ProphotoRgb, Rec2020, Rgb, A98, P3};
use crate::Color;
pub fn clamp_gamut(color: Color, mode: &str) -> Color {
match mode {
"rgb" | "hsl" | "hsv" | "hwb" | "hsi" | "okhsl" | "okhsv" | "hsluv" | "hpluv"
| "prismatic" => {
if in_gamut(&color, mode) {
return color;
}
let clamped_rgb = clamp_rgb_channels(color_to_rgb(color));
convert_rgb_back_to_source_mode(clamped_rgb, color)
}
"lrgb" => {
if in_gamut(&color, mode) {
return color;
}
let v = match color {
Color::LinearRgb(x) => x,
other => crate::convert::convert::<crate::spaces::Xyz65, crate::spaces::LinearRgb>(
to_xyz65(other),
),
};
let clamped = crate::spaces::LinearRgb {
r: clamp01(v.r),
g: clamp01(v.g),
b: clamp01(v.b),
alpha: v.alpha,
};
if let Color::LinearRgb(_) = color {
Color::LinearRgb(clamped)
} else {
let xyz = to_xyz65(Color::LinearRgb(clamped));
from_xyz65_in_mode_of(xyz, color)
}
}
"p3" => clamp_wide_gamut(color, mode, color_to_p3, |v| {
Color::P3(P3 {
r: clamp01(v.r),
g: clamp01(v.g),
b: clamp01(v.b),
alpha: v.alpha,
})
}),
"rec2020" => clamp_wide_gamut(color, mode, color_to_rec2020, |v| {
Color::Rec2020(Rec2020 {
r: clamp01(v.r),
g: clamp01(v.g),
b: clamp01(v.b),
alpha: v.alpha,
})
}),
"a98" => clamp_wide_gamut(color, mode, color_to_a98, |v| {
Color::A98(A98 {
r: clamp01(v.r),
g: clamp01(v.g),
b: clamp01(v.b),
alpha: v.alpha,
})
}),
"prophoto" => clamp_wide_gamut(color, mode, color_to_prophoto, |v| {
Color::ProphotoRgb(ProphotoRgb {
r: clamp01(v.r),
g: clamp01(v.g),
b: clamp01(v.b),
alpha: v.alpha,
})
}),
"lab" | "lab65" | "lch" | "lch65" | "oklab" | "oklch" | "xyz50" | "xyz65" | "jab"
| "jch" | "dlab" | "dlch" | "itp" | "xyb" | "luv" | "lchuv" | "cubehelix" | "yiq" => color,
_ => {
if in_gamut(&color, "rgb") {
return color;
}
let clamped_rgb = clamp_rgb_channels(color_to_rgb(color));
convert_rgb_back_to_source_mode(clamped_rgb, color)
}
}
}
fn clamp_wide_gamut<T, F, C>(color: Color, mode: &str, to_dest: F, clamp_channels: C) -> Color
where
F: Fn(Color) -> T,
C: Fn(T) -> Color,
{
if in_gamut(&color, mode) {
return color;
}
let dest_color = clamp_channels(to_dest(color));
convert_color_back_to_source_mode(dest_color, color)
}
fn convert_color_back_to_source_mode(dest_color: Color, template: Color) -> Color {
if std::mem::discriminant(&dest_color) == std::mem::discriminant(&template) {
return dest_color;
}
let xyz = to_xyz65(dest_color);
crate::gamut::clamp::from_xyz65_in_mode_of(xyz, template)
}
fn clamp_rgb_channels(c: Rgb) -> Rgb {
Rgb {
r: clamp01(c.r),
g: clamp01(c.g),
b: clamp01(c.b),
alpha: c.alpha,
}
}
fn clamp01(v: f64) -> f64 {
if v.is_nan() {
0.0
} else {
v.clamp(0.0, 1.0)
}
}
fn convert_rgb_back_to_source_mode(rgb: Rgb, template: Color) -> Color {
match template {
Color::Rgb(_) => Color::Rgb(rgb),
Color::LinearRgb(_) => Color::LinearRgb(LinearRgb::from(rgb)),
Color::Hsl(_) => Color::Hsl(Hsl::from(rgb)),
Color::Hsv(_) => Color::Hsv(Hsv::from(rgb)),
Color::Hwb(_) => Color::Hwb(Hwb::from(Hsv::from(rgb))),
Color::Lab(_) => Color::Lab(rgb.into()),
Color::Lab65(_) => Color::Lab65(rgb.into()),
Color::Lch(_) => Color::Lch(rgb.into()),
Color::Lch65(_) => Color::Lch65(rgb.into()),
Color::Oklab(_) => Color::Oklab(rgb.into()),
Color::Oklch(_) => Color::Oklch(rgb.into()),
Color::Xyz50(_) => Color::Xyz50(crate::convert(rgb)),
Color::Xyz65(_) => Color::Xyz65(crate::convert(rgb)),
Color::P3(_) => Color::P3(crate::convert(rgb)),
Color::Rec2020(_) => Color::Rec2020(crate::convert(rgb)),
Color::A98(_) => Color::A98(crate::convert(rgb)),
Color::ProphotoRgb(_) => Color::ProphotoRgb(crate::convert(rgb)),
Color::Cubehelix(_) => Color::Cubehelix(rgb.into()),
Color::Dlab(_) => Color::Dlab(crate::convert(rgb)),
Color::Dlch(_) => Color::Dlch(crate::convert(rgb)),
Color::Jab(_) => Color::Jab(crate::convert(rgb)),
Color::Jch(_) => Color::Jch(crate::convert(rgb)),
Color::Yiq(_) => Color::Yiq(rgb.into()),
Color::Hsi(_) => Color::Hsi(rgb.into()),
Color::Hsluv(_) => Color::Hsluv(rgb.into()),
Color::Hpluv(_) => Color::Hpluv(rgb.into()),
Color::Okhsl(_) => Color::Okhsl(rgb.into()),
Color::Okhsv(_) => Color::Okhsv(rgb.into()),
Color::Itp(_) => Color::Itp(crate::convert(rgb)),
Color::Xyb(_) => Color::Xyb(rgb.into()),
Color::Luv(_) => Color::Luv(rgb.into()),
Color::Lchuv(_) => Color::Lchuv(rgb.into()),
Color::Prismatic(_) => Color::Prismatic(rgb.into()),
}
}
pub fn clamp_chroma(color: Color, mode: &str) -> Color {
if in_gamut(&color, "rgb") {
return color;
}
let (start_l, start_c, start_h, alpha, range_max) = match mode {
"lch" => {
let lch: Lch = match color {
Color::Lch(x) => x,
_ => crate::convert::<crate::spaces::Xyz65, Lch>(to_xyz65(color)),
};
(lch.l, lch.c, lch.h, lch.alpha, 150.0_f64)
}
"oklch" => {
let oklch: Oklch = match color {
Color::Oklch(x) => x,
_ => {
use crate::traits::ColorSpace;
Oklch::from(crate::spaces::Oklab::from_xyz65(to_xyz65(color)))
}
};
(oklch.l, oklch.c, oklch.h, oklch.alpha, 0.4_f64)
}
other => panic!("clamp_chroma: mode must be 'lch' or 'oklch', got '{other}'"),
};
const ITER_DENOM: f64 = 8192.0;
let resolution = range_max / ITER_DENOM;
let mut clamped = make_polar(mode, start_l, 0.0, start_h, alpha);
if !in_gamut(&clamped, "rgb") {
let rgb = clamp_rgb_channels(color_to_rgb(clamped));
return convert_rgb_back_to_source_mode(rgb, color);
}
let mut start = 0.0;
let mut end = if start_c.is_nan() { 0.0 } else { start_c };
let mut last_good_c = 0.0;
while end - start > resolution {
let mid = start + (end - start) * 0.5;
clamped = make_polar(mode, start_l, mid, start_h, alpha);
if in_gamut(&clamped, "rgb") {
last_good_c = mid;
start = mid;
} else {
end = mid;
}
}
let final_color = if in_gamut(&clamped, "rgb") {
clamped
} else {
make_polar(mode, start_l, last_good_c, start_h, alpha)
};
convert_polar_to_source_mode(final_color, color)
}
fn make_polar(mode: &str, l: f64, c: f64, h: f64, alpha: Option<f64>) -> Color {
match mode {
"lch" => Color::Lch(Lch { l, c, h, alpha }),
"oklch" => Color::Oklch(Oklch { l, c, h, alpha }),
_ => unreachable!(),
}
}
fn convert_polar_to_source_mode(polar: Color, template: Color) -> Color {
if std::mem::discriminant(&polar) == std::mem::discriminant(&template) {
return polar;
}
let xyz = to_xyz65(polar);
from_xyz65_in_mode_of(xyz, template)
}
pub(crate) fn to_xyz65(c: Color) -> crate::spaces::Xyz65 {
use crate::traits::ColorSpace;
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(),
}
}
fn from_xyz65_in_mode_of(xyz: crate::spaces::Xyz65, template: Color) -> Color {
use crate::traits::ColorSpace;
match template {
Color::Rgb(_) => Color::Rgb(Rgb::from_xyz65(xyz)),
Color::LinearRgb(_) => Color::LinearRgb(LinearRgb::from_xyz65(xyz)),
Color::Hsl(_) => Color::Hsl(Hsl::from_xyz65(xyz)),
Color::Hsv(_) => Color::Hsv(Hsv::from_xyz65(xyz)),
Color::Hwb(_) => Color::Hwb(Hwb::from_xyz65(xyz)),
Color::Lab(_) => Color::Lab(crate::spaces::Lab::from_xyz65(xyz)),
Color::Lab65(_) => Color::Lab65(crate::spaces::Lab65::from_xyz65(xyz)),
Color::Lch(_) => Color::Lch(Lch::from_xyz65(xyz)),
Color::Lch65(_) => Color::Lch65(crate::spaces::Lch65::from_xyz65(xyz)),
Color::Oklab(_) => Color::Oklab(crate::spaces::Oklab::from_xyz65(xyz)),
Color::Oklch(_) => Color::Oklch(Oklch::from_xyz65(xyz)),
Color::Xyz50(_) => Color::Xyz50(crate::spaces::Xyz50::from_xyz65(xyz)),
Color::Xyz65(_) => Color::Xyz65(xyz),
Color::P3(_) => Color::P3(crate::spaces::P3::from_xyz65(xyz)),
Color::Rec2020(_) => Color::Rec2020(crate::spaces::Rec2020::from_xyz65(xyz)),
Color::A98(_) => Color::A98(crate::spaces::A98::from_xyz65(xyz)),
Color::ProphotoRgb(_) => Color::ProphotoRgb(crate::spaces::ProphotoRgb::from_xyz65(xyz)),
Color::Cubehelix(_) => Color::Cubehelix(crate::spaces::Cubehelix::from_xyz65(xyz)),
Color::Dlab(_) => Color::Dlab(crate::spaces::Dlab::from_xyz65(xyz)),
Color::Dlch(_) => Color::Dlch(crate::spaces::Dlch::from_xyz65(xyz)),
Color::Jab(_) => Color::Jab(crate::spaces::Jab::from_xyz65(xyz)),
Color::Jch(_) => Color::Jch(crate::spaces::Jch::from_xyz65(xyz)),
Color::Yiq(_) => Color::Yiq(crate::spaces::Yiq::from_xyz65(xyz)),
Color::Hsi(_) => Color::Hsi(crate::spaces::Hsi::from_xyz65(xyz)),
Color::Hsluv(_) => Color::Hsluv(crate::spaces::Hsluv::from_xyz65(xyz)),
Color::Hpluv(_) => Color::Hpluv(crate::spaces::Hpluv::from_xyz65(xyz)),
Color::Okhsl(_) => Color::Okhsl(crate::spaces::Okhsl::from_xyz65(xyz)),
Color::Okhsv(_) => Color::Okhsv(crate::spaces::Okhsv::from_xyz65(xyz)),
Color::Itp(_) => Color::Itp(crate::spaces::Itp::from_xyz65(xyz)),
Color::Xyb(_) => Color::Xyb(crate::spaces::Xyb::from_xyz65(xyz)),
Color::Luv(_) => Color::Luv(crate::spaces::Luv::from_xyz65(xyz)),
Color::Lchuv(_) => Color::Lchuv(crate::spaces::Lchuv::from_xyz65(xyz)),
Color::Prismatic(_) => Color::Prismatic(crate::spaces::Prismatic::from_xyz65(xyz)),
}
}