use super::color::{TransformFn, WhitePoint};
use crate::{FType, Mat3, Vec3, PI, TAU};
#[derive(Copy, Clone)]
pub struct ColorTransform {
first: for<'r> fn(&'r mut Vec3, WhitePoint),
second: Option<for<'r> fn(&'r mut Vec3, WhitePoint)>,
}
impl ColorTransform {
pub fn new(src_transform: TransformFn, dst_transform: TransformFn) -> Option<Self> {
use super::transform::*;
let from_transform = if src_transform == TransformFn::NONE {
None
} else {
Some(TRANSFORMS_INVERSE[src_transform as usize - 1])
};
let to_transform = if dst_transform == TransformFn::NONE {
None
} else {
Some(TRANSFORMS[dst_transform as usize - 1])
};
let (first, second) = if from_transform.is_some() {
(from_transform.unwrap(), to_transform)
} else if to_transform.is_some() {
(to_transform.unwrap(), None)
} else {
return None;
};
Some(Self { first, second })
}
pub fn apply(&self, color: &mut Vec3, white_point: WhitePoint) {
(self.first)(color, white_point);
if let Some(second) = self.second {
second(color, white_point);
}
}
}
const TRANSFORMS: [fn(&mut Vec3, WhitePoint); 14] = [
sRGB_gamma,
XYZ_to_Oklab,
XYZ_to_xyY,
XYZ_to_CIELAB,
XYZ_to_CIELCh,
XYZ_to_CIE_1960_UCS,
XYZ_to_CIE_1960_UCS_uvV,
XYZ_to_CIE_1964_UVW,
XYZ_to_CIE_1976_Luv,
hsx::RGB_to_HSL,
hsx::RGB_to_HSV,
hsx::RGB_to_HSI,
ICtCp::RGB_to_ICtCp_PQ,
ICtCp::RGB_to_ICtCp_HLG,
];
const TRANSFORMS_INVERSE: [fn(&mut Vec3, WhitePoint); 14] = [
sRGB_gamma_inverse,
Oklab_to_XYZ,
xyY_to_XYZ,
CIELAB_to_XYZ,
CIELCh_to_XYZ,
CIE_1960_UCS_to_XYZ,
CIE_1960_UCS_uvV_to_XYZ,
CIE_1964_UVW_to_XYZ,
CIE_1976_Luv_to_XYZ,
hsx::HSL_to_RGB,
hsx::HSV_to_RGB,
hsx::HSI_to_RGB,
ICtCp::ICtCp_PQ_to_RGB,
ICtCp::ICtCp_HLG_to_RGB,
];
pub fn sRGB_gamma(color: &mut Vec3, _wp: WhitePoint) {
let cutoff = color.cmplt(Vec3::splat(0.0031308));
let higher = Vec3::splat(1.055) * color.powf(1.0 / 2.4) - Vec3::splat(0.055);
let lower = *color * Vec3::splat(12.92);
*color = Vec3::select(cutoff, lower, higher);
}
pub fn sRGB_gamma_inverse(color: &mut Vec3, _wp: WhitePoint) {
let cutoff = color.cmplt(Vec3::splat(0.04045));
let higher = ((*color + Vec3::splat(0.055)) / 1.055).powf(2.4);
let lower = *color / 12.92;
*color = Vec3::select(cutoff, lower, higher);
}
#[rustfmt::skip]
const OKLAB_M_1: [FType;9] =
[0.8189330101,0.3618667424,-0.1288597137,
0.0329845436,0.9293118715,0.0361456387,
0.0482003018,0.2643662691,0.6338517070];
#[rustfmt::skip]
const OKLAB_M_2: [FType;9] =
[0.2104542553,0.7936177850,-0.0040720468,
1.9779984951,-2.4285922050,0.4505937099,
0.0259040371,0.7827717662,-0.8086757660];
pub fn XYZ_to_Oklab(color: &mut Vec3, _wp: WhitePoint) {
let mut lms = Mat3::from_cols_array(&OKLAB_M_1).transpose() * *color;
lms = lms.powf(1.0 / 3.0);
*color = Mat3::from_cols_array(&OKLAB_M_2).transpose() * lms
}
pub fn Oklab_to_XYZ(color: &mut Vec3, _wp: WhitePoint) {
let mut lms = Mat3::from_cols_array(&OKLAB_M_2).transpose().inverse() * *color;
lms = lms.powf(3.0);
*color = Mat3::from_cols_array(&OKLAB_M_1).transpose().inverse() * lms
}
pub fn XYZ_to_xyY(color: &mut Vec3, _wp: WhitePoint) {
let x = color.x / (color.x + color.y + color.z);
let y = color.y / (color.x + color.y + color.z);
let Y = color.y;
*color = Vec3::new(x, y, Y);
}
pub fn xyY_to_XYZ(color: &mut Vec3, _wp: WhitePoint) {
let x = (color.z / color.y) * color.x;
let y = color.z;
let z = (color.z / color.y) * (1.0 - color.x - color.y);
*color = Vec3::new(x, y, z);
}
pub fn XYZ_to_CIELAB(color: &mut Vec3, wp: WhitePoint) {
fn magic_f(v: FType) -> FType {
if v > 0.008856 {
v.powf(1.0 / 3.0)
} else {
v * 7.78703703704 + 0.13793103448
}
}
let wp_value = wp.values();
let x = magic_f(color.x / wp_value[0]);
let y = magic_f(color.y / wp_value[1]);
let z = magic_f(color.z / wp_value[2]);
let l = 116.0 * y - 16.0;
let a = 500.0 * (x - y);
let b = 200.0 * (y - z);
*color = Vec3::new(l, a, b);
}
pub fn CIELAB_to_XYZ(color: &mut Vec3, wp: WhitePoint) {
fn magic_f_inverse(v: FType) -> FType {
if v > 0.008856 {
v.powf(3.0)
} else {
0.12841854934 * (v - 0.13793103448)
}
}
let wp_value = wp.values();
let L = (color.x + 16.0) / 116.0;
let a = color.y / 500.0;
let b = color.z / 200.0;
let X = wp_value[0] * magic_f_inverse(L + a);
let Y = wp_value[1] * magic_f_inverse(L);
let Z = wp_value[2] * magic_f_inverse(L - b);
*color = Vec3::new(X, Y, Z);
}
pub fn XYZ_to_CIELCh(color: &mut Vec3, wp: WhitePoint) {
XYZ_to_CIELAB(color, wp);
CIELAB_to_CIELCh(color);
}
pub fn CIELCh_to_XYZ(color: &mut Vec3, wp: WhitePoint) {
CIELCh_to_CIELAB(color);
CIELAB_to_XYZ(color, wp);
}
pub fn CIELAB_to_CIELCh(color: &mut Vec3) {
let mut h = color.z.atan2(color.y);
if h > 0.0 {
h = (h / PI) * 180.0;
} else {
h = 360.0 - (h.abs() / PI) * 180.0
}
let C = (color.y * color.y + color.z * color.z).sqrt();
color.y = C;
color.z = h;
}
pub fn CIELCh_to_CIELAB(color: &mut Vec3) {
let angle = (color.z / 360.0) * TAU;
let a = color.y * angle.cos();
let b = color.y * angle.sin();
color.y = a;
color.z = b;
}
pub fn XYZ_to_CIE_1960_UCS(color: &mut Vec3, _wp: WhitePoint) {
let U = (2.0 / 3.0) * color.x;
let V = color.y;
let W = 0.5 * (-color.x + 3.0 * color.y + color.z);
*color = Vec3::new(U, V, W);
}
pub fn CIE_1960_UCS_to_XYZ(color: &mut Vec3, _wp: WhitePoint) {
let X = (3.0 / 2.0) * color.x;
let Y = color.y;
let Z = (3.0 / 2.0) * color.x - 3.0 * color.y + 2.0 * color.z;
*color = Vec3::new(X, Y, Z)
}
pub fn CIE_1960_UCS_uvV_to_XYZ(color: &mut Vec3, wp: WhitePoint) {
CIE_1960_uvV_to_UCS(color, wp);
CIE_1960_UCS_to_XYZ(color, wp);
}
pub fn XYZ_to_CIE_1960_UCS_uvV(color: &mut Vec3, wp: WhitePoint) {
XYZ_to_CIE_1960_UCS(color, wp);
CIE_1960_UCS_to_uvV(color, wp);
}
pub fn CIE_1960_UCS_to_uvV(color: &mut Vec3, _wp: WhitePoint) {
let u_v_w = color.x + color.y + color.z;
let u = color.x / u_v_w;
let v = color.y / u_v_w;
*color = Vec3::new(u, v, color.y)
}
pub fn CIE_1960_uvV_to_UCS(color: &mut Vec3, _wp: WhitePoint) {
let U = color.z * (color.x / color.y);
let W = -color.z * (color.x + color.y - 1.0) / color.y;
*color = Vec3::new(U, color.y, W);
}
pub fn CIE_1960_uvV_to_xyV(color: &mut Vec3, _wp: WhitePoint) {
let d = 2.0 * color.x - 8.0 * color.y - 4.0;
let x = 3.0 * (color.x / d);
let y = 2.0 * (color.y / d);
*color = Vec3::new(x, y, color.z);
}
pub fn CIE_1960_xyV_to_uvV(color: &mut Vec3, _wp: WhitePoint) {
let d = 12.0 * color.y - 2.0 * color.x + 3.0;
let u = 4.0 * (color.x / d);
let v = 6.0 * (color.y / d);
*color = Vec3::new(u, v, color.z);
}
pub fn XYZ_to_CIE_1964_UVW(_color: &mut Vec3, _wp: WhitePoint) {
}
pub fn CIE_1964_UVW_to_XYZ(_color: &mut Vec3, _wp: WhitePoint) {
}
pub fn XYZ_to_CIE_1976_Luv(color: &mut Vec3, wp: WhitePoint) {
let U = (4.0 * color.x) / (color.x + (15.0 * color.y) + (3.0 * color.z));
let V = (9.0 * color.y) / (color.x + (15.0 * color.y) + (3.0 * color.z));
let Y = color.y / 100.0;
let Y = if Y > 0.008856 {
Y.powf(1.0 / 3.0)
} else {
(7.787 * Y) + (16.0 / 116.0)
};
let wp_values = wp.values();
let ref_U =
(4.0 * wp_values[0]) / (wp_values[0] + (15.0 * wp_values[1]) + (3.0 * wp_values[2]));
let ref_V =
(9.0 * wp_values[1]) / (wp_values[0] + (15.0 * wp_values[1]) + (3.0 * wp_values[2]));
color.x = (116.0 * Y) - 16.0;
color.y = 13.0 * color.x * (U - ref_U);
color.z = 13.0 * color.x * (V - ref_V);
}
pub fn CIE_1976_Luv_to_XYZ(color: &mut Vec3, wp: WhitePoint) {
let Y = (color.x + 16.0) / 116.0;
let Y = if Y.powf(3.0) > 0.008856 {
Y.powf(3.0)
} else {
(Y - 16.0 / 116.0) / 7.787
};
let wp_values = wp.values();
let ref_U =
(4.0 * wp_values[0]) / (wp_values[0] + (15.0 * wp_values[1]) + (3.0 * wp_values[2]));
let ref_V =
(9.0 * wp_values[1]) / (wp_values[0] + (15.0 * wp_values[1]) + (3.0 * wp_values[2]));
let U = color.y / (13.0 * color.x) + ref_U;
let V = color.z / (13.0 * color.x) + ref_V;
color.y = Y * 100.0;
color.x = -(9.0 * color.y * U) / ((U - 4.0) * V - U * V);
color.z = (9.0 * color.y - (15.0 * V * color.y) - (V * color.x)) / (3.0 * V);
}
#[allow(non_upper_case_globals)]
mod HLG {
use super::*;
const HLG_a: FType = 0.17883277;
const HLG_b: FType = 0.28466892;
const HLG_c: FType = 0.55991073;
const HLG_r: FType = 0.5;
fn HLG_channel(E: FType) -> FType {
if E <= 1.0 {
HLG_r * E.sqrt()
} else {
HLG_a * (E - HLG_b).ln() + HLG_c
}
}
#[allow(non_upper_case_globals)]
pub fn hybrid_log_gamma(color: &mut Vec3, _wp: WhitePoint) {
color.x = HLG_channel(color.x);
color.y = HLG_channel(color.y);
color.z = HLG_channel(color.z);
}
#[allow(non_upper_case_globals)]
pub fn hybrid_log_gamma_inverse(color: &mut Vec3, _wp: WhitePoint) {
fn HLG_channel_inverse(E_p: FType) -> FType {
if E_p <= HLG_channel(1.0) {
(E_p / HLG_r).powf(2.0)
} else {
((E_p - HLG_c) / HLG_a).exp() + HLG_b
}
}
color.x = HLG_channel_inverse(color.x);
color.y = HLG_channel_inverse(color.y);
color.z = HLG_channel_inverse(color.z);
}
}
#[allow(non_upper_case_globals)]
mod PQ {
use super::*;
const L_p: FType = 10000.0;
const M_1: FType = 0.1593017578125;
const M_2: FType = 78.84375;
const M_1_d: FType = 1.0 / M_1;
const M_2_d: FType = 1.0 / M_2;
const C_1: FType = 0.8359375;
const C_2: FType = 18.8515625;
const C_3: FType = 18.6875;
pub fn ST_2084_PQ_inverse(color: &mut Vec3) {
fn apply_channel(f: FType) -> FType {
let Y_p = (f / L_p).powf(M_1);
((C_1 + C_2 * Y_p) / (C_3 * Y_p + 1.0)).powf(M_2)
}
color.x = apply_channel(color.x);
color.y = apply_channel(color.y);
color.z = apply_channel(color.z);
}
pub fn ST_2084_PQ(color: &mut Vec3) {
fn apply_channel(f: FType) -> FType {
let V_p = f.powf(M_2_d);
let n = (V_p - C_1).max(0.0);
let L = (n / (C_2 - C_3 * V_p)).powf(M_1_d);
L_p * L
}
color.x = apply_channel(color.x);
color.y = apply_channel(color.y);
color.z = apply_channel(color.z);
}
}
pub mod ICtCp {
use super::*;
#[rustfmt::skip]
#[allow(non_upper_case_globals)]
const ICtCp_LMS: [FType; 9] = [
0.412109, 0.523926, 0.0639648,
0.166748, 0.720459, 0.112793,
0.0241699, 0.0754395, 0.900391,
];
#[rustfmt::skip]
#[allow(non_upper_case_globals)]
const ICtCp_LMS_INVERSE: [FType; 9] = [
3.43661, -2.50646, 0.0698459,
-0.79133, 1.9836, -0.192271,
-0.0259498, -0.0989138, 1.12486,
];
pub fn RGB_to_ICtCp_HLG(color: &mut Vec3, wp: WhitePoint) {
#[rustfmt::skip]
#[allow(non_upper_case_globals)]
const ICtCp_From_HLG: [FType; 9] = [
0.5, 0.5, 0.0,
0.88501, -1.82251, 0.9375,
2.31934, -2.24902, -0.0703125,
];
let to_lms = Mat3::from_cols_array(&ICtCp_LMS).transpose();
let mut lms = to_lms * *color;
HLG::hybrid_log_gamma(&mut lms, wp);
let hlg_to_ICtCp = Mat3::from_cols_array(&ICtCp_From_HLG).transpose();
*color = hlg_to_ICtCp * lms;
}
pub fn ICtCp_HLG_to_RGB(color: &mut Vec3, wp: WhitePoint) {
#[rustfmt::skip]
#[allow(non_upper_case_globals)]
const ICtCp_From_HLG_INVERSE: [FType; 9] = [
0.999998, 0.0157186, 0.209581,
1.0, -0.0157186, -0.209581,
1.0, 1.02127, -0.605275,
];
let ICtCp_to_hlg = Mat3::from_cols_array(&ICtCp_From_HLG_INVERSE).transpose();
let mut lms_hlg = ICtCp_to_hlg * *color;
HLG::hybrid_log_gamma_inverse(&mut lms_hlg, wp);
let lms = lms_hlg;
let lms_to_rgb = Mat3::from_cols_array(&ICtCp_LMS_INVERSE).transpose();
*color = lms_to_rgb * lms;
}
pub fn RGB_to_ICtCp_PQ(color: &mut Vec3, _wp: WhitePoint) {
#[rustfmt::skip]
#[allow(non_upper_case_globals)]
const ICtCp_From_PQ: [FType; 9] = [
0.5, 0.5, 0.0,
1.61377, -3.32349, 1.70972,
4.37817, -4.24561, -0.132568
];
let to_lms = Mat3::from_cols_array(&ICtCp_LMS).transpose();
let mut lms = to_lms * *color;
PQ::ST_2084_PQ_inverse(&mut lms);
let pq_to_ICtCp = Mat3::from_cols_array(&ICtCp_From_PQ).transpose();
*color = pq_to_ICtCp * lms;
}
pub fn ICtCp_PQ_to_RGB(color: &mut Vec3, _wp: WhitePoint) {
#[rustfmt::skip]
#[allow(non_upper_case_globals)]
const ICtCp_From_PQ_INVERSE: [FType; 9] = [
1.0, 0.00860904, 0.11103,
1.0, -0.00860904, -0.11103,
1.0, 0.560031, -0.320627
];
let ICtCp_to_pq = Mat3::from_cols_array(&ICtCp_From_PQ_INVERSE).transpose();
let mut lms_pq = ICtCp_to_pq * *color;
PQ::ST_2084_PQ(&mut lms_pq);
let lms = lms_pq;
let lms_to_rgb = Mat3::from_cols_array(&ICtCp_LMS_INVERSE).transpose();
*color = lms_to_rgb * lms;
}
}
pub mod hsx {
use super::*;
fn HSX_hue_and_chroma_from_RGB(color: &Vec3, x_max: FType, x_min: FType) -> (FType, FType) {
let chroma = x_max - x_min;
let hue = if chroma == 0.0 {
0.0
} else if color.x > color.y && color.x > color.z {
60.0 * (color.y - color.z) / chroma
} else if color.y > color.x && color.y > color.z {
60.0 * (2.0 + (color.z - color.x) / chroma)
} else {
60.0 * (4.0 + (color.x - color.y) / chroma)
};
let hue = if hue < 0.0 { 360.0 + hue } else { hue };
(hue, chroma)
}
pub fn RGB_to_HSL(color: &mut Vec3, _wp: WhitePoint) {
RGB_to_HSX(color, |_, x_max, x_min, _| {
let lightness = (x_max + x_min) / 2.0;
let saturation = if lightness <= 0.0 || lightness >= 1.0 {
0.0
} else {
(x_max - lightness) / lightness.min(1.0 - lightness)
};
(saturation, lightness)
});
}
pub fn RGB_to_HSV(color: &mut Vec3, _wp: WhitePoint) {
RGB_to_HSX(color, |_, max, _, chroma| {
let value = max;
let saturation = if value == 0.0 { 0.0 } else { chroma / value };
(saturation, value)
});
}
pub fn RGB_to_HSI(color: &mut Vec3, _wp: WhitePoint) {
RGB_to_HSX(color, |color, _, min, _| {
let intensity = (color.x + color.y + color.z) * (1.0 / 3.0);
let saturation = if intensity == 0.0 {
0.0
} else {
1.0 - min / intensity
};
(saturation, intensity)
})
}
fn RGB_to_HSX<F: FnOnce(&mut Vec3, FType, FType, FType) -> (FType, FType)>(
color: &mut Vec3,
f: F,
) {
let x_max = color.x.max(color.y.max(color.z));
let x_min = color.x.min(color.y.min(color.z));
let (hue, chroma) = HSX_hue_and_chroma_from_RGB(color, x_max, x_min);
let (saturation, vli) = f(color, x_max, x_min, chroma);
color.x = hue;
color.y = saturation;
color.z = vli;
}
fn HSX_to_RGB<
F: FnOnce(
&Vec3,
) -> (
FType,
FType,
FType,
FType,
),
>(
color: &mut Vec3,
f: F,
) {
let (hue_prime, chroma, largest_component, lightness_match) = f(color);
let (r, g, b) = RGB_from_HCX(hue_prime, chroma, largest_component);
color.x = r + lightness_match;
color.y = g + lightness_match;
color.z = b + lightness_match;
}
pub fn HSL_to_RGB(color: &mut Vec3, _wp: WhitePoint) {
HSX_to_RGB(color, |color| {
let chroma = (1.0 - (2.0 * color.z - 1.0).abs()) * color.y;
let hue_prime = color.x / 60.0;
let largest_component = chroma * (1.0 - (hue_prime % 2.0 - 1.0).abs());
let lightness_match = color.z - chroma / 2.0;
(chroma, hue_prime, largest_component, lightness_match)
});
}
pub fn HSV_to_RGB(color: &mut Vec3, _wp: WhitePoint) {
HSX_to_RGB(color, |color| {
let chroma = color.z * color.y;
let hue_prime = color.x / 60.0;
let largest_component = chroma * (1.0 - (hue_prime % 2.0 - 1.0).abs());
let lightness_match = color.z - chroma;
(chroma, hue_prime, largest_component, lightness_match)
});
}
pub fn HSI_to_RGB(color: &mut Vec3, _wp: WhitePoint) {
HSX_to_RGB(color, |color| {
let hue_prime = color.x / 60.0;
let z = 1.0 - (hue_prime % 2.0 - 1.0).abs();
let chroma = (3.0 * color.z * color.y) / (1.0 + z);
let largest_component = chroma * z;
let lightness_match = color.z * (1.0 - color.y);
(hue_prime, chroma, largest_component, lightness_match)
});
}
fn RGB_from_HCX(
hue_prime: FType,
chroma: FType,
largest_component: FType,
) -> (FType, FType, FType) {
let (r, g, b) = if hue_prime < 1.0 {
(chroma, largest_component, 0.0)
} else if hue_prime < 2.0 {
(largest_component, chroma, 0.0)
} else if hue_prime < 3.0 {
(0.0, chroma, largest_component)
} else if hue_prime < 4.0 {
(0.0, largest_component, chroma)
} else if hue_prime < 5.0 {
(largest_component, 0.0, chroma)
} else {
(chroma, 0.0, largest_component)
};
(r, g, b)
}
}