1use crate::{
2 okcolor_convert::{okhsv_to_oklab, oklab_to_okhsv},
3 okhsla::Okhsla,
4 Alpha, ColorToComponents, Gray, Hsla, Hsva, Hue, Hwba, Laba, Lcha, LinearRgba, Mix, Oklaba,
5 Oklcha, Saturation, Srgba, StandardColor, Xyza,
6};
7use bevy_math::{Vec3, Vec4};
8#[cfg(feature = "bevy_reflect")]
9use bevy_reflect::prelude::*;
10
11#[doc = include_str!("../docs/conversion.md")]
17#[doc = include_str!("../docs/diagrams/model_graph.svg")]
19#[derive(Debug, Clone, Copy, PartialEq)]
21#[cfg_attr(
22 feature = "bevy_reflect",
23 derive(Reflect),
24 reflect(Clone, PartialEq, Default)
25)]
26#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
27#[cfg_attr(
28 all(feature = "serialize", feature = "bevy_reflect"),
29 reflect(Serialize, Deserialize)
30)]
31pub struct Okhsva {
32 pub hue: f32,
34 pub saturation: f32,
36 pub value: f32,
38 pub alpha: f32,
40}
41
42impl StandardColor for Okhsva {}
43
44impl Okhsva {
45 pub const fn new(hue: f32, saturation: f32, value: f32, alpha: f32) -> Self {
54 Self {
55 hue,
56 saturation,
57 value,
58 alpha,
59 }
60 }
61
62 pub const fn hsv(hue: f32, saturation: f32, value: f32) -> Self {
70 Self::new(hue, saturation, value, 1.0)
71 }
72
73 pub const fn with_saturation(self, saturation: f32) -> Self {
75 Self { saturation, ..self }
76 }
77
78 pub const fn with_value(self, value: f32) -> Self {
80 Self { value, ..self }
81 }
82}
83
84impl Default for Okhsva {
85 fn default() -> Self {
86 Self::new(0., 0., 1., 1.)
87 }
88}
89
90impl Mix for Okhsva {
91 #[inline]
92 fn mix(&self, other: &Self, factor: f32) -> Self {
93 let n_factor = 1.0 - factor;
94 Self {
95 hue: crate::color_ops::lerp_hue(self.hue, other.hue, factor),
96 saturation: self.saturation * n_factor + other.saturation * factor,
97 value: self.value * n_factor + other.value * factor,
98 alpha: self.alpha * n_factor + other.alpha * factor,
99 }
100 }
101}
102
103impl Gray for Okhsva {
104 const BLACK: Self = Self::new(0., 0., 0., 1.);
105 const WHITE: Self = Self::new(0., 0., 1., 1.);
106}
107
108impl Alpha for Okhsva {
109 #[inline]
110 fn with_alpha(&self, alpha: f32) -> Self {
111 Self { alpha, ..*self }
112 }
113
114 #[inline]
115 fn alpha(&self) -> f32 {
116 self.alpha
117 }
118
119 #[inline]
120 fn set_alpha(&mut self, alpha: f32) {
121 self.alpha = alpha;
122 }
123}
124
125impl Hue for Okhsva {
126 #[inline]
127 fn with_hue(&self, hue: f32) -> Self {
128 Self { hue, ..*self }
129 }
130
131 #[inline]
132 fn hue(&self) -> f32 {
133 self.hue
134 }
135
136 #[inline]
137 fn set_hue(&mut self, hue: f32) {
138 self.hue = hue;
139 }
140}
141
142impl Saturation for Okhsva {
143 #[inline]
144 fn with_saturation(&self, saturation: f32) -> Self {
145 Self {
146 saturation,
147 ..*self
148 }
149 }
150
151 #[inline]
152 fn saturation(&self) -> f32 {
153 self.saturation
154 }
155
156 #[inline]
157 fn set_saturation(&mut self, saturation: f32) {
158 self.saturation = saturation;
159 }
160}
161
162impl ColorToComponents for Okhsva {
163 fn to_f32_array(self) -> [f32; 4] {
164 [self.hue, self.saturation, self.value, self.alpha]
165 }
166
167 fn to_f32_array_no_alpha(self) -> [f32; 3] {
168 [self.hue, self.saturation, self.value]
169 }
170
171 fn to_vec4(self) -> Vec4 {
172 Vec4::new(self.hue, self.saturation, self.value, self.alpha)
173 }
174
175 fn to_vec3(self) -> Vec3 {
176 Vec3::new(self.hue, self.saturation, self.value)
177 }
178
179 fn from_f32_array(color: [f32; 4]) -> Self {
180 Self {
181 hue: color[0],
182 saturation: color[1],
183 value: color[2],
184 alpha: color[3],
185 }
186 }
187
188 fn from_f32_array_no_alpha(color: [f32; 3]) -> Self {
189 Self {
190 hue: color[0],
191 saturation: color[1],
192 value: color[2],
193 alpha: 1.0,
194 }
195 }
196
197 fn from_vec4(color: Vec4) -> Self {
198 Self {
199 hue: color[0],
200 saturation: color[1],
201 value: color[2],
202 alpha: color[3],
203 }
204 }
205
206 fn from_vec3(color: Vec3) -> Self {
207 Self {
208 hue: color[0],
209 saturation: color[1],
210 value: color[2],
211 alpha: 1.0,
212 }
213 }
214}
215
216impl From<Oklaba> for Okhsva {
217 fn from(value: Oklaba) -> Self {
218 oklab_to_okhsv(value)
219 }
220}
221
222impl From<Okhsva> for Oklaba {
223 fn from(value: Okhsva) -> Self {
224 okhsv_to_oklab(value)
225 }
226}
227
228impl From<LinearRgba> for Okhsva {
231 fn from(value: LinearRgba) -> Self {
232 Oklaba::from(value).into()
233 }
234}
235
236impl From<Okhsva> for LinearRgba {
237 fn from(value: Okhsva) -> Self {
238 Oklaba::from(value).into()
239 }
240}
241
242impl From<Srgba> for Okhsva {
243 fn from(value: Srgba) -> Self {
244 Oklaba::from(value).into()
245 }
246}
247
248impl From<Okhsva> for Srgba {
249 fn from(value: Okhsva) -> Self {
250 Oklaba::from(value).into()
251 }
252}
253
254impl From<Hwba> for Okhsva {
255 fn from(value: Hwba) -> Self {
256 Oklaba::from(value).into()
257 }
258}
259
260impl From<Okhsva> for Hwba {
261 fn from(value: Okhsva) -> Self {
262 Oklaba::from(value).into()
263 }
264}
265
266impl From<Lcha> for Okhsva {
267 fn from(value: Lcha) -> Self {
268 Oklaba::from(value).into()
269 }
270}
271
272impl From<Okhsva> for Lcha {
273 fn from(value: Okhsva) -> Self {
274 Oklaba::from(value).into()
275 }
276}
277
278impl From<Xyza> for Okhsva {
279 fn from(value: Xyza) -> Self {
280 Oklaba::from(value).into()
281 }
282}
283
284impl From<Okhsva> for Xyza {
285 fn from(value: Okhsva) -> Self {
286 Oklaba::from(value).into()
287 }
288}
289
290impl From<Okhsla> for Okhsva {
291 fn from(value: Okhsla) -> Self {
292 Oklaba::from(value).into()
293 }
294}
295
296impl From<Okhsva> for Okhsla {
297 fn from(value: Okhsva) -> Self {
298 Oklaba::from(value).into()
299 }
300}
301
302impl From<Hsla> for Okhsva {
303 fn from(value: Hsla) -> Self {
304 Oklaba::from(value).into()
305 }
306}
307
308impl From<Okhsva> for Hsla {
309 fn from(value: Okhsva) -> Self {
310 Oklaba::from(value).into()
311 }
312}
313
314impl From<Hsva> for Okhsva {
315 fn from(value: Hsva) -> Self {
316 Oklaba::from(value).into()
317 }
318}
319
320impl From<Okhsva> for Hsva {
321 fn from(value: Okhsva) -> Self {
322 Oklaba::from(value).into()
323 }
324}
325
326impl From<Laba> for Okhsva {
327 fn from(value: Laba) -> Self {
328 Oklaba::from(value).into()
329 }
330}
331
332impl From<Okhsva> for Laba {
333 fn from(value: Okhsva) -> Self {
334 Oklaba::from(value).into()
335 }
336}
337
338impl From<Oklcha> for Okhsva {
339 fn from(value: Oklcha) -> Self {
340 Oklaba::from(value).into()
341 }
342}
343
344impl From<Okhsva> for Oklcha {
345 fn from(value: Okhsva) -> Self {
346 Oklaba::from(value).into()
347 }
348}
349
350#[cfg(test)]
351mod tests {
352 use super::*;
353 use crate::{
354 color_difference::EuclideanDistance, test_colors::TEST_COLORS, testing::assert_approx_eq,
355 };
356
357 #[test]
358 fn test_from_oklaba() {
359 let oklaba = Oklaba::new(0.0, 0.5, 0.5, 1.0);
361 let okhsva: Okhsva = oklaba.into();
362 let oklaba2: Oklaba = okhsva.into();
363 assert_approx_eq!(okhsva.hue, 0.0, 0.001);
364 assert_approx_eq!(okhsva.saturation, 0.0, 0.001);
365 assert_approx_eq!(okhsva.value, 0.0, 0.001);
366 assert_approx_eq!(okhsva.alpha, 1.0, 0.001);
367
368 assert_approx_eq!(oklaba.lightness, oklaba2.lightness, 0.001);
369 assert_approx_eq!(0.0, oklaba2.a, 0.001);
370 assert_approx_eq!(0.0, oklaba2.b, 0.001);
371 assert_approx_eq!(oklaba.alpha, oklaba2.alpha, 0.001);
372
373 let oklaba = Oklaba::new(1.0, 0.5, 0.5, 1.0);
375 let okhsva: Okhsva = oklaba.into();
376 let oklaba2: Oklaba = okhsva.into();
377 assert_approx_eq!(okhsva.hue, 0.0, 0.001);
378 assert_approx_eq!(okhsva.saturation, 0.0, 0.001);
379 assert_approx_eq!(okhsva.value, 1.0, 0.001);
380 assert_approx_eq!(okhsva.alpha, 1.0, 0.001);
381
382 assert_approx_eq!(oklaba.lightness, oklaba2.lightness, 0.001);
383 assert_approx_eq!(0.0, oklaba2.a, 0.001);
384 assert_approx_eq!(0.0, oklaba2.b, 0.001);
385 assert_approx_eq!(oklaba.alpha, oklaba2.alpha, 0.001);
386
387 let oklaba = Oklaba::new(0.5, 0.0, 0.0, 1.0);
389 let okhsva: Okhsva = oklaba.into();
390 let oklaba2: Oklaba = okhsva.into();
391 assert_approx_eq!(okhsva.hue, 0.0, 0.001);
392 assert_approx_eq!(okhsva.saturation, 0.0, 0.001);
393 assert_approx_eq!(okhsva.value, 0.42114055, 0.001);
394 assert_approx_eq!(okhsva.alpha, 1.0, 0.001);
395
396 assert_approx_eq!(oklaba.lightness, oklaba2.lightness, 0.001);
397 assert_approx_eq!(0.0, oklaba2.a, 0.001);
398 assert_approx_eq!(0.0, oklaba2.b, 0.001);
399 assert_approx_eq!(oklaba.alpha, oklaba2.alpha, 0.001);
400 }
401
402 #[test]
403 fn test_to_from_srgba() {
404 let okhsva = Okhsva::new(180.0, 0.5, 0.5, 1.0);
405 let srgba: Srgba = okhsva.into();
406 let okhsva2: Okhsva = srgba.into();
407 assert_approx_eq!(okhsva.hue, okhsva2.hue, 0.001);
408 assert_approx_eq!(okhsva.saturation, okhsva2.saturation, 0.001);
409 assert_approx_eq!(okhsva.value, okhsva2.value, 0.001);
410 assert_approx_eq!(okhsva.alpha, okhsva2.alpha, 0.001);
411 }
412
413 #[test]
414 fn test_to_from_srgba_2() {
415 for color in TEST_COLORS.iter() {
416 let rgb2: Srgba = (color.okhsv).into();
417 let okhsv: Okhsva = (color.rgb).into();
418 assert!(
419 color.rgb.distance(&rgb2) < 0.001,
420 "{}: {:?} != {:?}",
421 color.name,
422 color.rgb,
423 rgb2,
424 );
425 let msg = alloc::format!(
426 "{}: expected {:?}, got {:?}",
427 color.name,
428 color.okhsv,
429 okhsv
430 );
431 assert_approx_eq!(color.okhsv.hue, okhsv.hue, 0.001, msg);
432 assert_approx_eq!(color.okhsv.saturation, okhsv.saturation, 0.001, msg);
433 assert_approx_eq!(color.okhsv.value, okhsv.value, 0.001, msg);
434 assert_approx_eq!(color.okhsv.alpha, okhsv.alpha, 0.001, msg);
435 }
436 }
437
438 #[test]
439 fn test_to_from_linear() {
440 let okhsva = Okhsva::new(0.5, 0.5, 0.5, 1.0);
441 let linear: LinearRgba = okhsva.into();
442 let okhsva2: Okhsva = linear.into();
443 assert_approx_eq!(okhsva.hue, okhsva2.hue, 0.001);
444 assert_approx_eq!(okhsva.saturation, okhsva2.saturation, 0.001);
445 assert_approx_eq!(okhsva.value, okhsva2.value, 0.001);
446 assert_approx_eq!(okhsva.alpha, okhsva2.alpha, 0.001);
447 }
448}