use glam::Vec3;
use super::space::ColorSpace;
#[must_use]
pub fn project_to_gamut(xyz: Vec3, space: ColorSpace) -> Vec3 {
project_to_gamut_bounded(xyz, space, f32::INFINITY)
}
#[must_use]
pub fn project_to_gamut_bounded(xyz: Vec3, space: ColorSpace, max: f32) -> Vec3 {
const STEPS: u32 = 32;
let sum = xyz.x + xyz.y + xyz.z;
if !sum.is_finite() || sum <= 1e-6 {
return Vec3::ZERO;
}
let x = xyz.x / sum;
let y = xyz.y / sum;
let luminance = xyz.y.max(0.0);
let linear = space.xyz_to_linear(xyz);
let in_range =
|v: Vec3| v.x >= 0.0 && v.y >= 0.0 && v.z >= 0.0 && v.x <= max && v.y <= max && v.z <= max;
if in_range(linear) {
return linear;
}
let (white_x, white_y) = space.white_point_xy();
let mapped_xyz_for_t = |t: f32| -> Vec3 {
let xp = (white_x - x).mul_add(t, x);
let yp = (white_y - y).mul_add(t, y);
if yp > 1e-6 {
Vec3::new(
(xp / yp) * luminance,
luminance,
((1.0 - xp - yp) / yp) * luminance,
)
} else {
Vec3::new(
(white_x / white_y) * luminance,
luminance,
((1.0 - white_x - white_y) / white_y) * luminance,
)
}
};
let mut resolved = space.xyz_to_linear(mapped_xyz_for_t(1.0)).max(Vec3::ZERO);
for i in 0..=STEPS {
let t = i as f32 / STEPS as f32;
let candidate = space.xyz_to_linear(mapped_xyz_for_t(t));
if in_range(candidate) {
resolved = candidate;
break;
}
}
resolved
}
#[must_use]
pub fn project_to_srgb(xyz: Vec3) -> Vec3 {
project_to_gamut(xyz, ColorSpace::Srgb)
}