1use crate::spaces::okhsl_helpers::{get_st_max, toe, toe_inv};
8use crate::spaces::{LinearRgb, Oklab, Rgb, Xyz65};
9use crate::traits::ColorSpace;
10use crate::util::normalize_hue;
11
12#[derive(Debug, Clone, Copy, PartialEq)]
14pub struct Okhsv {
15 pub h: f64,
17 pub s: f64,
19 pub v: f64,
21 pub alpha: Option<f64>,
23}
24
25impl ColorSpace for Okhsv {
26 const MODE: &'static str = "okhsv";
27 const CHANNELS: &'static [&'static str] = &["h", "s", "v"];
28
29 fn alpha(&self) -> Option<f64> {
30 self.alpha
31 }
32
33 fn with_alpha(self, alpha: Option<f64>) -> Self {
34 Self { alpha, ..self }
35 }
36
37 fn to_xyz65(&self) -> Xyz65 {
38 Oklab::from(*self).to_xyz65()
39 }
40
41 fn from_xyz65(xyz: Xyz65) -> Self {
42 Oklab::from_xyz65(xyz).into()
43 }
44}
45
46impl From<Okhsv> for Oklab {
47 fn from(c: Okhsv) -> Self {
48 let h = c.h;
49 let s = c.s;
50 let v = c.v;
51
52 let a_ = h.to_radians().cos();
53 let b_ = h.to_radians().sin();
54
55 let (s_max, t) = get_st_max(a_, b_, None);
56 let s_0 = 0.5;
57 let k = 1.0 - s_0 / s_max;
58 let l_v = 1.0 - (s * s_0) / (s_0 + t - t * k * s);
59 let c_v = (s * t * s_0) / (s_0 + t - t * k * s);
60
61 let l_vt = toe_inv(l_v);
62 let c_vt = (c_v * l_vt) / l_v;
63 let rgb_scale = LinearRgb::from(Oklab {
64 l: l_vt,
65 a: a_ * c_vt,
66 b: b_ * c_vt,
67 alpha: None,
68 });
69 let scale_l = (1.0 / rgb_scale.r.max(rgb_scale.g).max(rgb_scale.b).max(0.0)).cbrt();
70
71 let l_new = toe_inv(v * l_v);
72 let chroma = (c_v * l_new) / l_v;
73
74 Self {
75 l: l_new * scale_l,
76 a: chroma * a_ * scale_l,
77 b: chroma * b_ * scale_l,
78 alpha: c.alpha,
79 }
80 }
81}
82
83impl From<Oklab> for Okhsv {
84 fn from(lab: Oklab) -> Self {
85 let mut l = lab.l;
86 let a = lab.a;
87 let b = lab.b;
88
89 let mut c = (a * a + b * b).sqrt();
90 let a_ = if c != 0.0 { a / c } else { 1.0 };
91 let b_ = if c != 0.0 { b / c } else { 1.0 };
92
93 let (s_max, t) = get_st_max(a_, b_, None);
94 let s_0 = 0.5;
95 let k = 1.0 - s_0 / s_max;
96
97 let t_split = t / (c + l * t);
98 let l_v = t_split * l;
99 let c_v = t_split * c;
100
101 let l_vt = toe_inv(l_v);
102 let c_vt = (c_v * l_vt) / l_v;
103 let rgb_scale = LinearRgb::from(Oklab {
104 l: l_vt,
105 a: a_ * c_vt,
106 b: b_ * c_vt,
107 alpha: None,
108 });
109 let scale_l = (1.0 / rgb_scale.r.max(rgb_scale.g).max(rgb_scale.b).max(0.0)).cbrt();
110
111 l /= scale_l;
112 c = ((c / scale_l) * toe(l)) / l;
113 l = toe(l);
114
115 let mut out = Okhsv {
116 h: f64::NAN,
117 s: if c != 0.0 {
118 ((s_0 + t) * c_v) / (t * s_0 + t * k * c_v)
119 } else {
120 0.0
121 },
122 v: if l != 0.0 { l / l_v } else { 0.0 },
123 alpha: lab.alpha,
124 };
125 if out.s != 0.0 {
126 out.h = normalize_hue(b.atan2(a).to_degrees());
127 }
128 out
129 }
130}
131
132impl From<Rgb> for Okhsv {
135 fn from(c: Rgb) -> Self {
136 Oklab::from(c).into()
137 }
138}