use crate::constants::{ILLUMINANT_D65_XYZ, ILLUMINANT_C_XYZ};
pub fn xyz_to_lab(xyz: [f64; 3], illuminant: &str) -> [f64; 3] {
let (xn, yn, zn) = match illuminant {
"D65" => (ILLUMINANT_D65_XYZ[0], ILLUMINANT_D65_XYZ[1], ILLUMINANT_D65_XYZ[2]),
"C" => (ILLUMINANT_C_XYZ[0], ILLUMINANT_C_XYZ[1], ILLUMINANT_C_XYZ[2]),
_ => (ILLUMINANT_C_XYZ[0], ILLUMINANT_C_XYZ[1], ILLUMINANT_C_XYZ[2]), };
let x_r = xyz[0] / xn;
let y_r = xyz[1] / yn;
let z_r = xyz[2] / zn;
const CIE_E: f64 = 0.008856451679035631;
const CIE_K: f64 = 903.2962962962963;
let fx = if x_r > CIE_E {
x_r.powf(1.0 / 3.0)
} else {
(CIE_K * x_r + 16.0) / 116.0
};
let fy = if y_r > CIE_E {
y_r.powf(1.0 / 3.0)
} else {
(CIE_K * y_r + 16.0) / 116.0
};
let fz = if z_r > CIE_E {
z_r.powf(1.0 / 3.0)
} else {
(CIE_K * z_r + 16.0) / 116.0
};
let l = 116.0 * fy - 16.0;
let a = 500.0 * (fx - fy);
let b = 200.0 * (fy - fz);
[l, a, b]
}
pub fn lab_to_xyz(lab: [f64; 3], illuminant: &str) -> [f64; 3] {
let (xn, yn, zn) = match illuminant {
"D65" => (ILLUMINANT_D65_XYZ[0], ILLUMINANT_D65_XYZ[1], ILLUMINANT_D65_XYZ[2]),
"C" => (ILLUMINANT_C_XYZ[0], ILLUMINANT_C_XYZ[1], ILLUMINANT_C_XYZ[2]),
_ => (ILLUMINANT_C_XYZ[0], ILLUMINANT_C_XYZ[1], ILLUMINANT_C_XYZ[2]),
};
let l = lab[0];
let a = lab[1];
let b = lab[2];
let fy = (l + 16.0) / 116.0;
let fx = a / 500.0 + fy;
let fz = fy - b / 200.0;
const CIE_E: f64 = 0.008856451679035631;
const CIE_K: f64 = 903.2962962962963;
let x_r = {
let fx3 = fx * fx * fx;
if fx3 > CIE_E {
fx3
} else {
(116.0 * fx - 16.0) / CIE_K
}
};
let y_r = if l > CIE_K * CIE_E {
((l + 16.0) / 116.0).powi(3)
} else {
l / CIE_K
};
let z_r = {
let fz3 = fz * fz * fz;
if fz3 > CIE_E {
fz3
} else {
(116.0 * fz - 16.0) / CIE_K
}
};
[x_r * xn, y_r * yn, z_r * zn]
}
pub fn lab_to_lchab(lab: [f64; 3]) -> [f64; 3] {
let l = lab[0];
let a = lab[1];
let b = lab[2];
let c = (a * a + b * b).sqrt();
let h_rad = b.atan2(a);
let mut h = h_rad.to_degrees();
if h < 0.0 {
h += 360.0;
}
[l, c, h]
}
pub fn lchab_to_lab(lchab: [f64; 3]) -> [f64; 3] {
let l = lchab[0];
let c = lchab[1];
let h = lchab[2];
let h_rad = h.to_radians();
let a = c * h_rad.cos();
let b = c * h_rad.sin();
[l, a, b]
}
pub fn lchab_to_munsell_specification(lchab: [f64; 3]) -> [f64; 4] {
let l = lchab[0];
let c = lchab[1];
let h = lchab[2];
let value = l / 10.0;
let chroma = c / 5.0;
let (hue, code) = hue_angle_to_munsell_hue_simple(h);
[hue, value, chroma, code as f64]
}
fn hue_angle_to_munsell_hue_simple(angle: f64) -> (f64, u8) {
let normalized_angle = if angle < 0.0 {
angle + 360.0
} else if angle >= 360.0 {
angle - 360.0
} else {
angle
};
let (hue, code) = if normalized_angle < 20.0 {
(normalized_angle / 2.0, 7) } else if normalized_angle < 60.0 {
((normalized_angle - 20.0) / 4.0, 6) } else if normalized_angle < 100.0 {
((normalized_angle - 60.0) / 4.0, 5) } else if normalized_angle < 140.0 {
((normalized_angle - 100.0) / 4.0, 4) } else if normalized_angle < 180.0 {
((normalized_angle - 140.0) / 4.0, 3) } else if normalized_angle < 220.0 {
((normalized_angle - 180.0) / 4.0, 2) } else if normalized_angle < 260.0 {
((normalized_angle - 220.0) / 4.0, 1) } else if normalized_angle < 300.0 {
((normalized_angle - 260.0) / 4.0, 10) } else if normalized_angle < 340.0 {
((normalized_angle - 300.0) / 4.0, 9) } else {
((normalized_angle - 340.0) / 2.0, 8) };
let hue = if hue > 10.0 { 10.0 } else if hue < 0.0 { 0.0 } else { hue };
(hue, code)
}
pub fn srgb_to_xyz(rgb: [f64; 3]) -> [f64; 3] {
let linear_rgb: Vec<f64> = rgb.iter().map(|&c| {
if c <= 0.04045 {
c / 12.92
} else {
((c + 0.055) / 1.055).powf(2.4)
}
}).collect();
let x = 0.4124564 * linear_rgb[0] + 0.3575761 * linear_rgb[1] + 0.1804375 * linear_rgb[2];
let y = 0.2126729 * linear_rgb[0] + 0.7151522 * linear_rgb[1] + 0.0721750 * linear_rgb[2];
let z = 0.0193339 * linear_rgb[0] + 0.1191920 * linear_rgb[1] + 0.9503041 * linear_rgb[2];
[x, y, z]
}
pub fn xyz_to_xy(xyz: [f64; 3]) -> [f64; 2] {
let sum = xyz[0] + xyz[1] + xyz[2];
if sum < 1e-10 {
[0.31006, 0.31616]
} else {
[xyz[0] / sum, xyz[1] / sum]
}
}
pub fn xyy_to_xyz(xyy: [f64; 3]) -> [f64; 3] {
let x = xyy[0];
let y = xyy[1];
let y_luminance = xyy[2];
if y < 1e-10 {
[0.0, y_luminance, 0.0]
} else {
let x_coordinate = x * y_luminance / y;
let z_coordinate = (1.0 - x - y) * y_luminance / y;
[x_coordinate, y_luminance, z_coordinate]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_xyz_to_lab() {
let xyz = [0.2, 0.3, 0.4];
let lab = xyz_to_lab(xyz, "C");
assert!(lab[0] >= 0.0 && lab[0] <= 100.0);
}
#[test]
fn test_lab_to_lchab() {
let lab = [50.0, 20.0, 30.0];
let lchab = lab_to_lchab(lab);
assert_eq!(lchab[0], 50.0); assert!((lchab[1] - 36.0555).abs() < 0.001); assert!((lchab[2] - 56.3099).abs() < 0.001); }
#[test]
fn test_roundtrip_lab_lchab() {
let lab = [50.0, 20.0, 30.0];
let lchab = lab_to_lchab(lab);
let lab2 = lchab_to_lab(lchab);
assert!((lab[0] - lab2[0]).abs() < 1e-10);
assert!((lab[1] - lab2[1]).abs() < 1e-10);
assert!((lab[2] - lab2[2]).abs() < 1e-10);
}
}