use core::marker::PhantomData;
use crate::illuminant::{D65, Illuminant};
use crate::math::{DefaultMath, Mat3, MathState};
use crate::xyz::Xyz;
use crate::{Asserts, Color, ColorSpace, Transform};
#[inline(always)]
fn lab_f(t: f32) -> f32 {
const DELTA: f32 = 6.0 / 29.0;
const DELTA2: f32 = DELTA * DELTA;
if t > DELTA * DELTA2 {
DefaultMath::cbrt(t)
} else {
t / (3.0 * DELTA2) + 4.0 / 29.0
}
}
#[inline(always)]
fn lab_f_inv(t: f32) -> f32 {
const DELTA: f32 = 6.0 / 29.0;
const DELTA2: f32 = DELTA * DELTA;
if t > DELTA {
t * t * t
} else {
3.0 * DELTA2 * (t - 4.0 / 29.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Lab<W: Illuminant = D65>(PhantomData<W>);
impl<W: Illuminant> ColorSpace for Lab<W> {
const CHANNELS: usize = 3;
const LUMINANCE_WEIGHTS: Option<[f32; 3]> = None;
}
impl<W: Illuminant> Asserts<[f32; 3]> for Lab<W> {}
impl<W: Illuminant> Asserts<[f32; 4]> for Lab<W> {}
impl<W: Illuminant> Transform<Color<[f32; 3], Xyz<W>>> for Color<[f32; 3], Lab<W>> {
fn transform_from(src: Color<[f32; 3], Xyz<W>>, _: &()) -> Self {
let [x, y, z] = src.inner();
let [xn, yn, zn] = W::WHITE_POINT_XYZ;
let fx = lab_f(x / xn);
let fy = lab_f(y / yn);
let fz = lab_f(z / zn);
Color::new_unchecked([116.0 * fy - 16.0, 500.0 * (fx - fy), 200.0 * (fy - fz)])
}
}
impl<W: Illuminant> Transform<Color<[f32; 3], Lab<W>>> for Color<[f32; 3], Xyz<W>> {
fn transform_from(src: Color<[f32; 3], Lab<W>>, _: &()) -> Self {
let [l, a, b] = src.inner();
let [xn, yn, zn] = W::WHITE_POINT_XYZ;
let fy = (l + 16.0) / 116.0;
Color::new_unchecked([
xn * lab_f_inv(a / 500.0 + fy),
yn * lab_f_inv(fy),
zn * lab_f_inv(fy - b / 200.0),
])
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct LCh<W: Illuminant = D65>(PhantomData<W>);
impl<W: Illuminant> ColorSpace for LCh<W> {
const CHANNELS: usize = 3;
const LUMINANCE_WEIGHTS: Option<[f32; 3]> = None;
}
impl<W: Illuminant> Asserts<[f32; 3]> for LCh<W> {}
impl<W: Illuminant> Asserts<[f32; 4]> for LCh<W> {}
impl<W: Illuminant> Transform<Color<[f32; 3], Lab<W>>> for Color<[f32; 3], LCh<W>> {
fn transform_from(src: Color<[f32; 3], Lab<W>>, _: &()) -> Self {
let [l, a, b] = src.inner();
let c = DefaultMath::sqrt(a * a + b * b);
let h = DefaultMath::atan2(b, a).to_degrees().rem_euclid(360.0);
Color::new_unchecked([l, c, h])
}
}
impl<W: Illuminant> Transform<Color<[f32; 3], LCh<W>>> for Color<[f32; 3], Lab<W>> {
fn transform_from(src: Color<[f32; 3], LCh<W>>, _: &()) -> Self {
let [l, c, h] = src.inner();
let h_rad = h.to_radians();
Color::new_unchecked([l, c * DefaultMath::cos(h_rad), c * DefaultMath::sin(h_rad)])
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Oklab;
impl ColorSpace for Oklab {
const CHANNELS: usize = 3;
const LUMINANCE_WEIGHTS: Option<[f32; 3]> = None;
}
impl Asserts<[f32; 3]> for Oklab {}
impl Asserts<[f32; 4]> for Oklab {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Oklch;
impl ColorSpace for Oklch {
const CHANNELS: usize = 3;
const LUMINANCE_WEIGHTS: Option<[f32; 3]> = None;
}
impl Asserts<[f32; 3]> for Oklch {}
impl Asserts<[f32; 4]> for Oklch {}
#[allow(clippy::excessive_precision)]
const OKLAB_M1: Mat3 = Mat3 {
col0: [0.8189330101, 0.0329845436, 0.0482003018, 0.0],
col1: [0.3618667424, 0.9293118715, 0.2643662691, 0.0],
col2: [-0.1288597137, 0.0361456387, 0.6338517070, 0.0],
};
#[allow(clippy::excessive_precision)]
const OKLAB_M2: Mat3 = Mat3 {
col0: [0.2104542553, 1.9779984951, 0.0259040371, 0.0],
col1: [0.7936177850, -2.4285922050, 0.7827717662, 0.0],
col2: [-0.0040720468, 0.4505937099, -0.8086757660, 0.0],
};
#[allow(clippy::excessive_precision)]
const OKLAB_M1_INV: Mat3 = Mat3 {
col0: [1.2270138511, -0.0405801784, -0.0763812845, 0.0],
col1: [-0.5577999807, 1.1122568696, -0.4214819784, 0.0],
col2: [0.2812561490, -0.0716766787, 1.5861632204, 0.0],
};
#[allow(clippy::excessive_precision)]
const OKLAB_M2_INV: Mat3 = Mat3 {
col0: [1.0, 1.0, 1.0, 0.0],
col1: [0.3963377774, -0.1055613458, -0.0894841775, 0.0],
col2: [0.2158037573, -0.0638541728, -1.2914855480, 0.0],
};
impl Transform<Color<[f32; 3], Xyz<D65>>> for Color<[f32; 3], Oklab> {
fn transform_from(src: Color<[f32; 3], Xyz<D65>>, _: &()) -> Self {
let xyz = src.inner();
let lms = OKLAB_M1.apply(xyz);
let lms_g = [
DefaultMath::cbrt(lms[0]),
DefaultMath::cbrt(lms[1]),
DefaultMath::cbrt(lms[2]),
];
Color::new_unchecked(OKLAB_M2.apply(lms_g))
}
}
impl Transform<Color<[f32; 3], Oklab>> for Color<[f32; 3], Xyz<D65>> {
fn transform_from(src: Color<[f32; 3], Oklab>, _: &()) -> Self {
let lab = src.inner();
let lms_g = OKLAB_M2_INV.apply(lab);
let lms = [lms_g[0].powi(3), lms_g[1].powi(3), lms_g[2].powi(3)];
Color::new_unchecked(OKLAB_M1_INV.apply(lms))
}
}
impl Transform<Color<[f32; 3], Oklab>> for Color<[f32; 3], Oklch> {
fn transform_from(src: Color<[f32; 3], Oklab>, _: &()) -> Self {
let [l, a, b] = src.inner();
let c = DefaultMath::sqrt(a * a + b * b);
let h = DefaultMath::atan2(b, a).to_degrees().rem_euclid(360.0);
Color::new_unchecked([l, c, h])
}
}
impl Transform<Color<[f32; 3], Oklch>> for Color<[f32; 3], Oklab> {
fn transform_from(src: Color<[f32; 3], Oklch>, _: &()) -> Self {
let [l, c, h] = src.inner();
let h_rad = h.to_radians();
Color::new_unchecked([l, c * DefaultMath::cos(h_rad), c * DefaultMath::sin(h_rad)])
}
}
pub type LabD65 = Lab<D65>;
pub type LChD65 = LCh<D65>;