use std::fmt::Display;
use num_traits::clamp;
#[cfg(feature = "wasm")]
use serde::{Deserialize, Serialize};
use crate::{
color::{Hue, Oklab},
math::FloatNumber,
};
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "wasm", derive(Serialize, Deserialize))]
pub struct Oklch<T = f64>
where
T: FloatNumber,
{
pub l: T,
pub c: T,
pub h: Hue<T>,
}
impl<T> Oklch<T>
where
T: FloatNumber,
{
#[must_use]
pub fn new(l: T, c: T, h: T) -> Self {
Self {
l: clamp(l, T::zero(), T::from_u32(100)),
c: clamp(c, T::zero(), T::from_u32(180)),
h: Hue::from_degrees(h),
}
}
}
impl<T> Display for Oklch<T>
where
T: FloatNumber,
{
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"Oklch({:.2}, {:.2}, {:.2})",
self.l,
self.c,
self.h.to_degrees()
)
}
}
impl<T> From<&Oklab<T>> for Oklch<T>
where
T: FloatNumber,
{
fn from(oklab: &Oklab<T>) -> Self {
let l = oklab.l;
let c = (oklab.a.powi(2) + oklab.b.powi(2)).sqrt();
let h = oklab.b.atan2(oklab.a).to_degrees();
Oklch::new(l, c, h)
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "wasm")]
use serde_test::{assert_de_tokens, assert_ser_tokens, Token};
use super::*;
use crate::{assert_approx_eq, color::Oklab};
#[test]
fn test_new() {
let actual = Oklch::new(0.607, 0.121, 166.651);
assert_eq!(
actual,
Oklch {
l: 0.607,
c: 0.121,
h: Hue::from_degrees(166.651)
}
);
}
#[test]
#[cfg(feature = "wasm")]
fn test_serialize() {
let oklch = Oklch::new(0.607, 0.121, 166.651);
assert_ser_tokens(
&oklch,
&[
Token::Struct {
name: "Oklch",
len: 3,
},
Token::Str("l"),
Token::F64(0.607),
Token::Str("c"),
Token::F64(0.121),
Token::Str("h"),
Token::F64(166.651),
Token::StructEnd,
],
)
}
#[test]
#[cfg(feature = "wasm")]
fn test_deserialize() {
let oklch = Oklch::new(0.70, 0.10, 148.0);
assert_de_tokens(
&oklch,
&[
Token::Struct {
name: "Oklch",
len: 3,
},
Token::Str("l"),
Token::F64(0.70),
Token::Str("c"),
Token::F64(0.10),
Token::Str("h"),
Token::F64(148.0),
Token::StructEnd,
],
);
}
#[test]
fn test_fmt() {
let oklch = Oklch::new(0.607, 0.121, 166.651);
let actual = format!("{}", oklch);
assert_eq!(actual, "Oklch(0.61, 0.12, 166.65)");
}
#[test]
fn test_from_oklab() {
let oklab: Oklab<f32> = Oklab::new(0.607, -0.118, 0.028);
let actual = Oklch::from(&oklab);
assert_approx_eq!(actual.l, 0.607);
assert_approx_eq!(actual.c, 0.121276);
assert_approx_eq!(actual.h.to_degrees(), 166.651275);
}
}