#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[repr(C)]
pub struct Oklch {
pub l: f32,
pub c: f32,
pub h: f32,
}
impl Oklch {
#[must_use]
pub const fn new(l: f32, c: f32, h: f32) -> Self {
Self { l, c, h }
}
#[must_use]
pub fn lerp(self, other: Self, t: f32) -> Self {
use crate::space::math::{lerp_f32, lerp_hue};
Self {
l: lerp_f32(self.l, other.l, t),
c: lerp_f32(self.c, other.c, t),
h: lerp_hue(self.h, other.h, t),
}
}
#[must_use]
pub fn hue_degrees(self) -> f32 {
self.h * 360.0
}
#[must_use]
pub fn from_degrees(l: f32, c: f32, h_deg: f32) -> Self {
Self::new(l, c, h_deg / 360.0)
}
}
impl From<[f32; 3]> for Oklch {
fn from([l, c, h]: [f32; 3]) -> Self {
Self { l, c, h }
}
}
impl From<Oklch> for [f32; 3] {
fn from(c: Oklch) -> Self {
[c.l, c.c, c.h]
}
}
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod tests {
use super::*;
#[test]
fn lerp_hue_midpoint() {
let a = Oklch::new(0.5, 0.2, 0.0);
let b = Oklch::new(0.5, 0.2, 0.5);
let mid = a.lerp(b, 0.5);
assert!((mid.h - 0.25).abs() < 1e-5, "hue={}", mid.h);
}
#[test]
fn lerp_hue_shortest_path() {
let a = Oklch::new(0.5, 0.2, 0.9);
let b = Oklch::new(0.5, 0.2, 0.1);
let mid = a.lerp(b, 0.5);
assert!(mid.h < 0.05 || mid.h > 0.95, "hue={}", mid.h);
}
#[test]
fn hue_degrees_roundtrip() {
let c = Oklch::new(0.5, 0.2, 0.25);
assert!((c.hue_degrees() - 90.0).abs() < 1e-4);
let c2 = Oklch::from_degrees(0.5, 0.2, 90.0);
assert!((c2.h - 0.25).abs() < 1e-4);
}
#[test]
fn from_array_roundtrip() {
let c = Oklch::new(0.5, 0.2, 0.3);
let arr: [f32; 3] = c.into();
assert!((arr[0] - 0.5).abs() < f32::EPSILON);
assert!((arr[1] - 0.2).abs() < f32::EPSILON);
assert!((arr[2] - 0.3).abs() < f32::EPSILON);
let back = Oklch::from(arr);
assert!((back.l - 0.5).abs() < f32::EPSILON);
}
#[cfg(any(feature = "std", feature = "libm"))]
mod conv_tests {
use super::*;
use crate::space::{Oklab, Srgb};
#[test]
fn srgb_roundtrip_red() {
let red = Oklch::from(Srgb::RED);
let back = Srgb::from(red).clamp();
assert!((back.r - 1.0).abs() < 0.01, "r={}", back.r);
assert!(back.g.abs() < 0.01, "g={}", back.g);
assert!(back.b.abs() < 0.01, "b={}", back.b);
}
#[test]
fn oklab_roundtrip() {
let lab = Oklab::new(0.6, 0.1, -0.1);
let lch = Oklch::from(lab);
let back = Oklab::from(lch);
assert!((back.l - lab.l).abs() < 1e-5);
assert!((back.a - lab.a).abs() < 1e-5);
assert!((back.b - lab.b).abs() < 1e-5);
}
}
}