use color::{Color, XYZColor};
use coord::Coord;
use illuminants::Illuminant;
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
pub struct CIELABColor {
pub l: f64,
pub a: f64,
pub b: f64,
}
impl Color for CIELABColor {
fn from_xyz(xyz: XYZColor) -> CIELABColor {
let f = |x: &f64| {
let delta: f64 = 6.0 / 29.0;
if *x <= delta.powf(3.0) {
x / (3.0 * delta * delta) + 4.0 / 29.0
} else {
x.powf(1.0 / 3.0)
}
};
let white_point = Illuminant::D50.white_point();
let xyz_adapted = xyz.color_adapt(Illuminant::D50);
let xyz_scaled = [
xyz_adapted.x / white_point[0],
xyz_adapted.y / white_point[1],
xyz_adapted.z / white_point[2],
];
let xyz_transformed: Vec<f64> = xyz_scaled.iter().map(f).collect();
let l = 116.0 * xyz_transformed[1] - 16.0;
let a = 500.0 * (xyz_transformed[0] - xyz_transformed[1]);
let b = 200.0 * (xyz_transformed[1] - xyz_transformed[2]);
CIELABColor { l, a, b }
}
fn to_xyz(&self, illuminant: Illuminant) -> XYZColor {
let f_inv = |x: f64| {
let delta: f64 = 6.0 / 29.0;
if x > delta {
x * x * x
} else {
3.0 * delta * delta * (x - 4.0 / 29.0)
}
};
let xyz_n = Illuminant::D50.white_point();
let x = xyz_n[0] * f_inv((self.l + 16.0) / 116.0 + (self.a / 500.0));
let y = xyz_n[1] * f_inv((self.l + 16.0) / 116.0);
let z = xyz_n[2] * f_inv((self.l + 16.0) / 116.0 - (self.b / 200.0));
XYZColor {
x,
y,
z,
illuminant: Illuminant::D50,
}
.color_adapt(illuminant)
}
}
impl From<Coord> for CIELABColor {
fn from(c: Coord) -> CIELABColor {
CIELABColor {
l: c.x,
a: c.y,
b: c.z,
}
}
}
impl From<CIELABColor> for Coord {
fn from(val: CIELABColor) -> Self {
Coord {
x: val.l,
y: val.a,
z: val.b,
}
}
}
#[cfg(test)]
mod tests {
#[allow(unused_imports)]
use super::*;
use color::RGBColor;
use consts::TEST_PRECISION;
#[test]
fn test_cielab_xyz_conversion_d50() {
let xyz = XYZColor {
x: 0.4,
y: 0.2,
z: 0.6,
illuminant: Illuminant::D50,
};
let lab = CIELABColor::from_xyz(xyz);
let xyz2 = lab.to_xyz(Illuminant::D50);
assert!(xyz.approx_equal(&xyz2));
assert!(xyz.distance(&xyz2) <= TEST_PRECISION);
}
#[test]
fn test_cielab_xyz_conversion() {
let xyz = XYZColor {
x: 0.4,
y: 0.2,
z: 0.6,
illuminant: Illuminant::D65,
};
let lab = CIELABColor::from_xyz(xyz);
let xyz_d50 = lab.to_xyz(Illuminant::D50);
let xyz2 = xyz_d50.color_adapt(Illuminant::D65);
assert!(xyz.approx_equal(&xyz2));
assert!(xyz.distance(&xyz2) <= TEST_PRECISION);
}
#[test]
fn test_out_of_gamut() {
let _color1 = CIELABColor {
l: 0.0,
a: 100.0,
b: 100.0,
};
let _color2: RGBColor = _color1.convert();
let _color3: CIELABColor = _color2.convert();
}
}