use rgb::ComponentMap;
pub use rgb::RGB;
#[derive(Copy, Clone, Debug, PartialOrd, PartialEq)]
#[repr(C)]
pub struct Oklab {
pub l: f32,
pub a: f32,
pub b: f32,
}
pub fn linear_srgb_to_oklab(c: RGB<f32>) -> Oklab {
let l = 0.4122214708 * c.r + 0.5363325363 * c.g + 0.0514459929 * c.b;
let m = 0.2119034982 * c.r + 0.6806995451 * c.g + 0.1073969566 * c.b;
let s = 0.0883024619 * c.r + 0.2817188376 * c.g + 0.6299787005 * c.b;
let l_ = l.cbrt();
let m_ = m.cbrt();
let s_ = s.cbrt();
Oklab {
l: 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_,
a: 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_,
b: 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_,
}
}
pub fn oklab_to_linear_srgb(c: Oklab) -> RGB<f32> {
let l_ = c.l + 0.3963377774 * c.a + 0.2158037573 * c.b;
let m_ = c.l - 0.1055613458 * c.a - 0.0638541728 * c.b;
let s_ = c.l - 0.0894841775 * c.a - 1.2914855480 * c.b;
let l = l_ * l_ * l_;
let m = m_ * m_ * m_;
let s = s_ * s_ * s_;
RGB {
r: 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
g: -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
b: -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s,
}
}
#[inline(always)]
fn to_gamma8(u: f32) -> u8 {
(to_gamma(u) * 255.0).round() as u8
}
#[inline(always)]
fn to_linear8(c: u8) -> f32 {
to_linear(c as f32 / 255.0)
}
#[inline]
fn to_gamma(u: f32) -> f32 {
if u >= 0.0031308 {
(1.055) * u.powf(1.0 / 2.4) - 0.055
} else {
12.92 * u
}
}
#[inline]
fn to_linear(u: f32) -> f32 {
if u >= 0.04045 {
((u + 0.055) / (1. + 0.055)).powf(2.4)
} else {
u / 12.92
}
}
#[inline]
pub fn srgb_to_oklab(c: RGB<u8>) -> Oklab {
linear_srgb_to_oklab(c.map(to_linear8))
}
#[inline]
pub fn oklab_to_srgb(c: Oklab) -> RGB<u8> {
oklab_to_linear_srgb(c).map(to_gamma8)
}
impl Oklab {
#[inline(always)]
pub fn to_srgb(&self) -> RGB<u8> {
oklab_to_srgb(*self)
}
#[inline(always)]
pub fn to_linear_rgb(&self) -> RGB<f32> {
oklab_to_linear_srgb(*self)
}
}
impl From<RGB<u8>> for Oklab {
#[inline(always)]
fn from(rgb: RGB<u8>) -> Self {
srgb_to_oklab(rgb)
}
}
#[test]
fn linear() {
for i in 0..=255 {
assert_eq!(i, to_gamma8(to_linear8(i)));
}
}
#[test]
fn roundtrip_half1() {
for r in 128..=255 {
for g in 0..=255 {
for b in 0..=255 {
let c = RGB::new(r,g,b);
assert_eq!(c, oklab_to_srgb(srgb_to_oklab(c)));
}
}
}
}
#[test]
fn roundtrip_half2() {
for r in 0..128 {
for g in 0..=255 {
for b in 0..=255 {
let c = RGB::new(r,g,b);
assert_eq!(c, oklab_to_srgb(srgb_to_oklab(c)));
}
}
}
}