use crate::space::{Hsl, Hsv, LinearRgb, Oklab, Srgb};
pub trait ToLinear: Copy {
#[must_use]
fn to_linear(self) -> LinearRgb;
#[must_use]
fn from_linear(c: LinearRgb) -> Self;
}
impl ToLinear for LinearRgb {
fn to_linear(self) -> LinearRgb {
self
}
fn from_linear(c: LinearRgb) -> Self {
c
}
}
impl ToLinear for Srgb {
fn to_linear(self) -> LinearRgb {
LinearRgb::from(self)
}
fn from_linear(c: LinearRgb) -> Self {
Self::from(c)
}
}
impl ToLinear for Hsl {
fn to_linear(self) -> LinearRgb {
LinearRgb::from(Srgb::from(self))
}
fn from_linear(c: LinearRgb) -> Self {
Self::from(Srgb::from(c))
}
}
impl ToLinear for Hsv {
fn to_linear(self) -> LinearRgb {
LinearRgb::from(Srgb::from(self))
}
fn from_linear(c: LinearRgb) -> Self {
Self::from(Srgb::from(c))
}
}
impl ToLinear for Oklab {
fn to_linear(self) -> LinearRgb {
LinearRgb::from(self)
}
fn from_linear(c: LinearRgb) -> Self {
Self::from(c)
}
}
#[cfg(any(feature = "std", feature = "libm"))]
impl ToLinear for crate::space::Oklch {
fn to_linear(self) -> LinearRgb {
LinearRgb::from(Oklab::from(self))
}
fn from_linear(c: LinearRgb) -> Self {
Self::from(Oklab::from_linear(c))
}
}
pub trait ConvertSpace: ToLinear {
#[must_use]
fn convert<T: ToLinear>(self) -> T {
T::from_linear(self.to_linear())
}
}
impl<S: ToLinear> ConvertSpace for S {}
#[cfg(test)]
mod tests {
use super::*;
use crate::space::Oklch;
#[test]
fn srgb_convert_is_identity_through_linear() {
let c = Srgb::new(0.8, 0.3, 0.5);
let back: Srgb = c.convert();
assert!((back.r - c.r).abs() < 1e-5);
assert!((back.g - c.g).abs() < 1e-5);
assert!((back.b - c.b).abs() < 1e-5);
}
#[test]
fn hsl_to_oklab_direct_conversion_with_no_hand_written_from_impl() {
let hsl = Hsl::new(0.6, 0.8, 0.5);
let oklab: Oklab = hsl.convert();
let back: Hsl = oklab.convert();
assert!((back.h - hsl.h).abs() < 0.01, "h: {} vs {}", back.h, hsl.h);
assert!((back.s - hsl.s).abs() < 0.05, "s: {} vs {}", back.s, hsl.s);
assert!((back.l - hsl.l).abs() < 0.05, "l: {} vs {}", back.l, hsl.l);
}
#[test]
fn hsv_to_srgb_via_hub_matches_direct_from_impl() {
let hsv = Hsv::new(0.25, 0.6, 0.9);
let via_hub: Srgb = hsv.convert();
let via_from = Srgb::from(hsv);
assert!((via_hub.r - via_from.r).abs() < 1e-5);
assert!((via_hub.g - via_from.g).abs() < 1e-5);
assert!((via_hub.b - via_from.b).abs() < 1e-5);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn oklch_roundtrip_via_hub() {
let lch = Oklch::new(0.6, 0.15, 0.3);
let linear = lch.to_linear();
let back = Oklch::from_linear(linear);
assert!((back.l - lch.l).abs() < 1e-4);
assert!((back.c - lch.c).abs() < 1e-4);
assert!((back.h - lch.h).abs() < 1e-4);
}
#[test]
fn linear_rgb_identity() {
let c = LinearRgb::new(0.1, 0.2, 0.3);
assert_eq!(c.to_linear(), c);
assert_eq!(LinearRgb::from_linear(c), c);
}
}