1use crate::{
2 angle::RealAngle,
3 bool_mask::HasBoolMask,
4 color_difference::{DeltaE, ImprovedDeltaE},
5 convert::{FromColorUnclamped, IntoColorUnclamped},
6 hues::{Cam16Hue, Cam16HueIter},
7 num::{Arithmetics, Hypot, Ln, One, Real, Trigonometry, Zero},
8 Alpha,
9};
10
11use super::{Cam16Jmh, Cam16UcsJab};
12
13pub type Cam16UcsJmha<T> = Alpha<Cam16UcsJmh<T>, T>;
18
19#[derive(Clone, Copy, Debug, Default, WithAlpha, ArrayCast, FromColorUnclamped)]
50#[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))]
51#[palette(
52 palette_internal,
53 component = "T",
54 skip_derives(Cam16Jmh, Cam16UcsJmh, Cam16UcsJab)
55)]
56#[repr(C)]
57pub struct Cam16UcsJmh<T> {
58 pub lightness: T,
63
64 pub colorfulness: T,
68
69 #[palette(unsafe_same_layout_as = "T")]
74 pub hue: Cam16Hue<T>,
75}
76
77impl<T> Cam16UcsJmh<T> {
78 pub fn new<H: Into<Cam16Hue<T>>>(lightness: T, colorfulness: T, hue: H) -> Self {
80 Self::new_const(lightness, colorfulness, hue.into())
81 }
82
83 pub const fn new_const(lightness: T, colorfulness: T, hue: Cam16Hue<T>) -> Self {
87 Self {
88 lightness,
89 colorfulness,
90 hue,
91 }
92 }
93
94 pub fn into_components(self) -> (T, T, Cam16Hue<T>) {
96 (self.lightness, self.colorfulness, self.hue)
97 }
98
99 pub fn from_components<H: Into<Cam16Hue<T>>>(
101 (lightness, colorfulness, hue): (T, T, H),
102 ) -> Self {
103 Self::new(lightness, colorfulness, hue)
104 }
105}
106
107impl<T> Cam16UcsJmh<T>
108where
109 T: Zero + Real,
110{
111 pub fn min_lightness() -> T {
113 T::zero()
114 }
115
116 pub fn max_lightness() -> T {
118 T::from_f64(100.0)
119 }
120
121 pub fn min_colorfulness() -> T {
123 T::zero()
124 }
125
126 pub fn max_srgb_colorfulness() -> T {
134 T::from_f64(50.0)
136 }
137}
138
139impl<T, A> Alpha<Cam16UcsJmh<T>, A> {
141 pub fn new<H: Into<Cam16Hue<T>>>(lightness: T, colorfulness: T, hue: H, alpha: A) -> Self {
143 Self::new_const(lightness, colorfulness, hue.into(), alpha)
144 }
145
146 pub const fn new_const(lightness: T, colorfulness: T, hue: Cam16Hue<T>, alpha: A) -> Self {
150 Self {
151 color: Cam16UcsJmh::new_const(lightness, colorfulness, hue),
152 alpha,
153 }
154 }
155
156 pub fn into_components(self) -> (T, T, Cam16Hue<T>, A) {
158 (
159 self.color.lightness,
160 self.color.colorfulness,
161 self.color.hue,
162 self.alpha,
163 )
164 }
165
166 pub fn from_components<H: Into<Cam16Hue<T>>>(
168 (lightness, colorfulness, hue, alpha): (T, T, H, A),
169 ) -> Self {
170 Self::new(lightness, colorfulness, hue, alpha)
171 }
172}
173
174impl<T> FromColorUnclamped<Cam16UcsJmh<T>> for Cam16UcsJmh<T> {
175 fn from_color_unclamped(val: Cam16UcsJmh<T>) -> Self {
176 val
177 }
178}
179
180impl<T> FromColorUnclamped<Cam16Jmh<T>> for Cam16UcsJmh<T>
181where
182 T: Real + One + Ln + Arithmetics,
183{
184 fn from_color_unclamped(val: Cam16Jmh<T>) -> Self {
185 let colorfulness =
186 (T::one() + T::from_f64(0.0228) * val.colorfulness).ln() / T::from_f64(0.0228);
187 let lightness =
188 T::from_f64(1.7) * &val.lightness / (T::one() + T::from_f64(0.007) * val.lightness);
189
190 Cam16UcsJmh {
191 lightness,
192 colorfulness,
193 hue: val.hue,
194 }
195 }
196}
197
198impl<T> FromColorUnclamped<Cam16UcsJab<T>> for Cam16UcsJmh<T>
199where
200 T: RealAngle + Hypot + Trigonometry + Arithmetics + Clone,
201{
202 fn from_color_unclamped(val: Cam16UcsJab<T>) -> Self {
203 Self {
204 lightness: val.lightness,
205 colorfulness: val.a.clone().hypot(val.b.clone()),
206 hue: Cam16Hue::from_cartesian(val.a, val.b),
207 }
208 }
209}
210
211impl<T> DeltaE for Cam16UcsJmh<T>
212where
213 Cam16UcsJab<T>: DeltaE<Scalar = T> + FromColorUnclamped<Self>,
214{
215 type Scalar = T;
216
217 #[inline]
218 fn delta_e(self, other: Self) -> Self::Scalar {
219 Cam16UcsJab::from_color_unclamped(self).delta_e(other.into_color_unclamped())
221 }
222}
223
224impl<T> ImprovedDeltaE for Cam16UcsJmh<T>
225where
226 Cam16UcsJab<T>: ImprovedDeltaE<Scalar = T> + FromColorUnclamped<Self>,
227{
228 #[inline]
229 fn improved_delta_e(self, other: Self) -> Self::Scalar {
230 Cam16UcsJab::from_color_unclamped(self).improved_delta_e(other.into_color_unclamped())
232 }
233}
234
235impl<T> HasBoolMask for Cam16UcsJmh<T>
236where
237 T: HasBoolMask,
238{
239 type Mask = T::Mask;
240}
241
242#[cfg(feature = "bytemuck")]
243unsafe impl<T> bytemuck::Zeroable for Cam16UcsJmh<T> where T: bytemuck::Zeroable {}
244
245#[cfg(feature = "bytemuck")]
246unsafe impl<T> bytemuck::Pod for Cam16UcsJmh<T> where T: bytemuck::Pod {}
247
248impl_reference_component_methods_hue!(Cam16UcsJmh, [lightness, colorfulness]);
251impl_struct_of_arrays_methods_hue!(Cam16UcsJmh, [lightness, colorfulness]);
252impl_tuple_conversion_hue!(Cam16UcsJmh as (T, T, H), Cam16Hue);
253
254impl_is_within_bounds! {
255 Cam16UcsJmh {
256 lightness => [Self::min_lightness(), Self::max_lightness()],
257 colorfulness => [Self::min_colorfulness(), None]
258 }
259 where T: Zero + Real
260}
261impl_clamp! {
262 Cam16UcsJmh {
263 lightness => [Self::min_lightness(), Self::max_lightness()],
264 colorfulness => [Self::min_colorfulness()]
265 }
266 other {hue}
267 where T: Zero + Real
268}
269
270impl_mix_hue!(Cam16UcsJmh {
271 lightness,
272 colorfulness
273});
274impl_lighten!(Cam16UcsJmh increase {lightness => [Self::min_lightness(), Self::max_lightness()]} other {hue, colorfulness});
275impl_saturate!(Cam16UcsJmh increase {colorfulness => [Self::min_colorfulness(), Self::max_srgb_colorfulness()]} other {hue, lightness});
276impl_hue_ops!(Cam16UcsJmh, Cam16Hue);
277
278impl_color_add!(Cam16UcsJmh, [lightness, colorfulness, hue]);
279impl_color_sub!(Cam16UcsJmh, [lightness, colorfulness, hue]);
280
281impl_array_casts!(Cam16UcsJmh<T>, [T; 3]);
282impl_simd_array_conversion_hue!(Cam16UcsJmh, [lightness, colorfulness]);
283impl_struct_of_array_traits_hue!(Cam16UcsJmh, Cam16HueIter, [lightness, colorfulness]);
284
285impl_eq_hue!(Cam16UcsJmh, Cam16Hue, [lightness, colorfulness, hue]);
286
287impl_rand_traits_cylinder!(
288 UniformCam16UcsJmh,
289 Cam16UcsJmh {
290 hue: UniformCam16Hue => Cam16Hue,
291 height: lightness => [|l: T| l * Cam16UcsJmh::<T>::max_lightness()],
292 radius: colorfulness => [|c| c * Cam16UcsJmh::<T>::max_srgb_colorfulness()]
293 }
294 where T: Real + Zero + core::ops::Mul<Output = T>,
295);
296
297#[cfg(test)]
300mod test {
301 use crate::{
302 cam16::{Cam16Jmh, Cam16UcsJmh},
303 convert::FromColorUnclamped,
304 };
305
306 #[cfg(feature = "approx")]
307 use crate::color_difference::DeltaE;
308
309 #[cfg(all(feature = "approx", feature = "alloc"))]
310 use crate::{
311 cam16::Cam16UcsJab, color_difference::ImprovedDeltaE, convert::IntoColorUnclamped,
312 };
313
314 #[test]
315 fn ranges() {
316 assert_ranges! {
317 Cam16UcsJmh<f64>;
318 clamped {
319 lightness: 0.0 => 100.0
320 }
321 clamped_min {
322 colorfulness: 0.0 => 200.0
323 }
324 unclamped {
325 hue: -360.0 => 360.0
326 }
327 }
328 }
329
330 #[test]
331 fn cam16_roundtrip() {
332 let ucs = Cam16UcsJmh::new(50.0f64, 80.0, 120.0);
333 let cam16 = Cam16Jmh::from_color_unclamped(ucs);
334 assert_eq!(Cam16UcsJmh::from_color_unclamped(cam16), ucs);
335 }
336
337 raw_pixel_conversion_tests!(Cam16UcsJmh<>: lightness, colorfulness, hue);
338 raw_pixel_conversion_fail_tests!(Cam16UcsJmh<>: lightness, colorfulness, hue);
339
340 #[test]
341 #[cfg(feature = "approx")]
342 fn delta_e_large_hue_diff() {
343 let lhs1 = Cam16UcsJmh::<f64>::new(50.0, 64.0, -730.0);
344 let rhs1 = Cam16UcsJmh::new(50.0, 64.0, 730.0);
345
346 let lhs2 = Cam16UcsJmh::<f64>::new(50.0, 64.0, -10.0);
347 let rhs2 = Cam16UcsJmh::new(50.0, 64.0, 10.0);
348
349 assert_relative_eq!(
350 lhs1.delta_e(rhs1),
351 lhs2.delta_e(rhs2),
352 epsilon = 0.0000000000001
353 );
354 }
355
356 #[test]
358 #[cfg(all(feature = "approx", feature = "alloc"))]
359 fn jab_delta_e_equality() {
360 let mut jab_colors: Vec<Cam16UcsJab<f64>> = alloc::vec::Vec::new();
361
362 for j_step in 0i8..5 {
363 for a_step in -2i8..3 {
364 for b_step in -2i8..3 {
365 jab_colors.push(Cam16UcsJab::new(
366 j_step as f64 * 25.0,
367 a_step as f64 * 60.0,
368 b_step as f64 * 60.0,
369 ))
370 }
371 }
372 }
373
374 let jmh_colors: alloc::vec::Vec<Cam16UcsJmh<_>> = jab_colors.clone().into_color_unclamped();
375
376 for (&lhs_jab, &lhs_jmh) in jab_colors.iter().zip(&jmh_colors) {
377 for (&rhs_jab, &rhs_jmh) in jab_colors.iter().zip(&jmh_colors) {
378 let delta_e_jab = lhs_jab.delta_e(rhs_jab);
379 let delta_e_jmh = lhs_jmh.delta_e(rhs_jmh);
380 assert_relative_eq!(delta_e_jab, delta_e_jmh, epsilon = 0.0000000000001);
381 }
382 }
383 }
384
385 #[test]
388 #[cfg(all(feature = "approx", feature = "alloc"))]
389 fn jab_improved_delta_e_equality() {
390 let mut jab_colors: Vec<Cam16UcsJab<f64>> = alloc::vec::Vec::new();
391
392 for j_step in 0i8..5 {
393 for a_step in -2i8..3 {
394 for b_step in -2i8..3 {
395 jab_colors.push(Cam16UcsJab::new(
396 j_step as f64 * 25.0,
397 a_step as f64 * 60.0,
398 b_step as f64 * 60.0,
399 ))
400 }
401 }
402 }
403
404 let jmh_colors: alloc::vec::Vec<Cam16UcsJmh<_>> = jab_colors.clone().into_color_unclamped();
405
406 for (&lhs_jab, &lhs_jmh) in jab_colors.iter().zip(&jmh_colors) {
407 for (&rhs_jab, &rhs_jmh) in jab_colors.iter().zip(&jmh_colors) {
408 let delta_e_jab = lhs_jab.improved_delta_e(rhs_jab);
409 let delta_e_jmh = lhs_jmh.improved_delta_e(rhs_jmh);
410 assert_relative_eq!(delta_e_jab, delta_e_jmh, epsilon = 0.0000000000001);
411 }
412 }
413 }
414
415 struct_of_arrays_tests!(
416 Cam16UcsJmh[lightness, colorfulness, hue],
417 super::Cam16UcsJmha::new(0.1f32, 0.2, 0.3, 0.4),
418 super::Cam16UcsJmha::new(0.2, 0.3, 0.4, 0.5),
419 super::Cam16UcsJmha::new(0.3, 0.4, 0.5, 0.6)
420 );
421
422 #[cfg(feature = "serializing")]
423 #[test]
424 fn serialize() {
425 let serialized = ::serde_json::to_string(&Cam16UcsJmh::new(0.3, 0.8, 0.1)).unwrap();
426
427 assert_eq!(
428 serialized,
429 r#"{"lightness":0.3,"colorfulness":0.8,"hue":0.1}"#
430 );
431 }
432
433 #[cfg(feature = "serializing")]
434 #[test]
435 fn deserialize() {
436 let deserialized: Cam16UcsJmh<f32> =
437 ::serde_json::from_str(r#"{"lightness":0.3,"colorfulness":0.8,"hue":0.1}"#).unwrap();
438
439 assert_eq!(deserialized, Cam16UcsJmh::new(0.3, 0.8, 0.1));
440 }
441
442 test_uniform_distribution! {
443 Cam16UcsJmh<f32> as crate::cam16::Cam16UcsJab<f32> {
444 lightness: (0.0, 100.0),
445 a: (-30.0, 30.0),
446 b: (-30.0, 30.0),
447 },
448 min: Cam16UcsJmh::new(0.0f32, 0.0, 0.0),
449 max: Cam16UcsJmh::new(100.0, 50.0, 360.0)
450 }
451}