palette/cam16/
partial.rs

1use crate::{
2    cam16::Cam16UcsJmh,
3    convert::FromColorUnclamped,
4    num::{Arithmetics, Exp, One, Real},
5    Alpha,
6};
7
8macro_rules! make_partial_cam16 {
9    (
10        $(#[$type_meta: meta])*
11        $module: ident :: $name: ident {
12            $(#[$luminance_meta: meta])+
13            $luminance: ident : $luminance_ty: ident,
14            $(#[$chromaticity_meta: meta])+
15            $chromaticity: ident : $chromaticity_ty: ident
16        }
17    ) => {
18        pub use $module::$name;
19
20        #[doc = concat!("Partial CIE CAM16, with ", stringify!($luminance), " and ", stringify!($chromaticity), ", and helper types.")]
21        pub mod $module {
22            use crate::{
23                bool_mask::HasBoolMask,
24                cam16::{BakedParameters, Cam16, WhitePointParameter, Cam16FromUnclamped, IntoCam16Unclamped, Cam16IntoUnclamped},
25                convert::FromColorUnclamped,
26                hues::{Cam16Hue, Cam16HueIter},
27                num::{FromScalar, Zero},
28                Alpha, Xyz,
29            };
30
31            use crate::cam16::math::chromaticity::*;
32            use crate::cam16::math::luminance::*;
33
34            #[doc = concat!("Partial CIE CAM16, with ", stringify!($luminance), " and ", stringify!($chromaticity), ".")]
35            ///
36            /// It contains enough information for converting CAM16 to other
37            /// color spaces. See [Cam16] for more details about CIE CAM16.
38            ///
39            /// The full list of partial CAM16 variants is:
40            ///
41            /// * [`Cam16Jch`](crate::cam16::Cam16Jch): lightness and chroma.
42            /// * [`Cam16Jmh`](crate::cam16::Cam16Jmh): lightness and
43            ///   colorfulness.
44            /// * [`Cam16Jsh`](crate::cam16::Cam16Jsh): lightness and
45            ///   saturation.
46            /// * [`Cam16Qch`](crate::cam16::Cam16Qch): brightness and chroma.
47            /// * [`Cam16Qmh`](crate::cam16::Cam16Qmh): brightness and
48            ///   colorfulness.
49            /// * [`Cam16Qsh`](crate::cam16::Cam16Qsh): brightness and
50            ///   saturation.
51            ///
52            /// # Creating a Value
53            ///
54            /// Any partial CAM16 set can be obtained from the full set of
55            /// attributes. It's also possible to convert directly to it, using
56            #[doc = concat!("[`from_xyz`][", stringify!($name), "::from_xyz],")]
57            /// or to create a new value by calling
58            #[doc = concat!("[`new`][", stringify!($name), "::new].")]
59            /// ```
60            /// use palette::{
61            ///     Srgb, FromColor, IntoColor, hues::Cam16Hue,
62            #[doc = concat!("    cam16::{Cam16, Parameters, ", stringify!($name), "},")]
63            /// };
64            ///
65            #[doc = concat!("let partial = ", stringify!($name), "::new(50.0f32, 80.0, 120.0);")]
66            ///
67            /// // There's also `new_const`:
68            #[doc = concat!("const PARTIAL: ", stringify!($name), "<f32> = ", stringify!($name), "::new_const(50.0, 80.0, Cam16Hue::new(120.0));")]
69            ///
70            /// // Customize these according to the viewing conditions:
71            /// let mut example_parameters = Parameters::default_static_wp(40.0);
72            ///
73            /// // Partial CAM16 from sRGB, or most other color spaces:
74            /// let rgb = Srgb::new(0.3f32, 0.8, 0.1);
75            #[doc = concat!("let partial_from_rgb = ", stringify!($name), "::from_xyz(rgb.into_color(), example_parameters);")]
76            ///
77            /// // Partial CAM16 from sRGB, via full CAM16:
78            /// let rgb = Srgb::new(0.3f32, 0.8, 0.1);
79            /// let cam16_from_rgb = Cam16::from_xyz(rgb.into_color(), example_parameters);
80            #[doc = concat!("let partial_from_full = ", stringify!($name), "::from(cam16_from_rgb);")]
81            ///
82            /// // Direct conversion has the same result as going via full CAM16.
83            /// assert_eq!(partial_from_rgb, partial_from_full);
84            ///
85            /// // It's also possible to convert from (and to) arrays and tuples:
86            #[doc = concat!("let partial_from_array = ", stringify!($name), "::from([50.0f32, 80.0, 120.0]);")]
87            #[doc = concat!("let partial_from_tuple = ", stringify!($name), "::from((50.0f32, 80.0, 120.0));")]
88            ///  ```
89            #[derive(Clone, Copy, Debug, Default, ArrayCast, WithAlpha, FromColorUnclamped)]
90            #[palette(
91                palette_internal,
92                component = "T",
93                skip_derives(Cam16, $name)
94            )]
95            $(#[$type_meta])*
96            #[repr(C)]
97            pub struct $name<T> {
98                $(#[$luminance_meta])+
99                pub $luminance: T,
100
101                $(#[$chromaticity_meta])+
102                pub $chromaticity: T,
103
104                /// The [hue](https://cie.co.at/eilvterm/17-22-067) (h) of the color.
105                ///
106                /// See [`Cam16::hue`][crate::cam16::Cam16::hue].
107                #[palette(unsafe_same_layout_as = "T")]
108                pub hue: Cam16Hue<T>,
109            }
110
111            impl<T> $name<T> {
112                /// Create a partial CIE CAM16 color.
113                #[inline]
114                pub fn new<H>($luminance: T, $chromaticity: T, hue: H) -> Self
115                where
116                    H: Into<Cam16Hue<T>>,
117                {
118                    Self::new_const($luminance.into(), $chromaticity.into(), hue.into())
119                }
120
121                #[doc = concat!("Create a partial CIE CAM16 color. This is the same as `", stringify!($name), "::new`")]
122                /// without the generic hue type. It's temporary until `const fn`
123                /// supports traits.
124                #[inline]
125                pub const fn new_const($luminance: T, $chromaticity: T, hue: Cam16Hue<T>) -> Self {
126                    Self {
127                        $luminance,
128                        $chromaticity,
129                        hue,
130                    }
131                }
132
133                #[doc = concat!("Convert to a `(", stringify!($luminance), ", ", stringify!($chromaticity), ", hue)` tuple.")]
134                #[inline]
135                pub fn into_components(self) -> (T, T, Cam16Hue<T>) {
136                    (self.$luminance, self.$chromaticity, self.hue)
137                }
138
139                #[doc = concat!("Convert from a `(", stringify!($luminance), ", ", stringify!($chromaticity), ", hue)` tuple.")]
140                #[inline]
141                pub fn from_components<H>(($luminance, $chromaticity, hue): (T, T, H)) -> Self
142                where
143                    H: Into<Cam16Hue<T>>,
144                {
145                    Self::new($luminance, $chromaticity, hue)
146                }
147
148                /// Derive partial CIE CAM16 attributes for the provided color, under the provided
149                /// viewing conditions.
150                ///
151                /// ```
152                #[doc = concat!("use palette::{Srgb, IntoColor, cam16::{", stringify!($name), ", Parameters}};")]
153                ///
154                /// // Customize these according to the viewing conditions:
155                /// let mut example_parameters = Parameters::default_static_wp(40.0);
156                ///
157                /// let rgb = Srgb::new(0.3f32, 0.8, 0.1);
158                #[doc = concat!("let partial = ", stringify!($name), "::from_xyz(rgb.into_color(), example_parameters);")]
159                /// ```
160                ///
161                /// It's also possible to "pre-bake" the parameters, to avoid recalculate
162                /// some of the derived values when converting multiple color value.
163                ///
164                /// ```
165                #[doc = concat!("use palette::{Srgb, IntoColor, cam16::{", stringify!($name), ", Parameters}};")]
166                ///
167                /// // Customize these according to the viewing conditions:
168                /// let mut example_parameters = Parameters::default_static_wp(40.0);
169                ///
170                /// let baked_parameters = example_parameters.bake();
171                ///
172                /// let rgb = Srgb::new(0.3f32, 0.8, 0.1);
173                #[doc = concat!("let partial = ", stringify!($name), "::from_xyz(rgb.into_color(), baked_parameters);")]
174                /// ```
175                #[inline]
176                pub fn from_xyz<WpParam>(
177                    color: Xyz<WpParam::StaticWp, T>,
178                    parameters: impl Into<BakedParameters<WpParam, T::Scalar>>,
179                ) -> Self
180                where
181                    Xyz<WpParam::StaticWp, T>: IntoCam16Unclamped<WpParam, Self, Scalar = T::Scalar>,
182                    T: FromScalar,
183                    WpParam: WhitePointParameter<T::Scalar>,
184                {
185                    color.into_cam16_unclamped(parameters.into())
186                }
187
188                /// Construct an XYZ color from these CIE CAM16 attributes, under the
189                /// provided viewing conditions.
190                ///
191                /// ```
192                #[doc = concat!("use palette::{Srgb, FromColor, cam16::{", stringify!($name), ", Parameters}};")]
193                ///
194                /// // Customize these according to the viewing conditions:
195                /// let mut example_parameters = Parameters::default_static_wp(40.0);
196                ///
197                #[doc = concat!("let partial = ", stringify!($name), "::new(50.0f32, 80.0, 120.0);")]
198                /// let rgb = Srgb::from_color(partial.into_xyz(example_parameters));
199                /// ```
200                ///
201                /// It's also possible to "pre-bake" the parameters, to avoid recalculate
202                /// some of the derived values when converting multiple color value.
203                ///
204                /// ```
205                #[doc = concat!("use palette::{Srgb, FromColor, cam16::{", stringify!($name), ", Parameters}};")]
206                ///
207                /// // Customize these according to the viewing conditions:
208                /// let mut example_parameters = Parameters::default_static_wp(40.0);
209                ///
210                /// let baked_parameters = example_parameters.bake();
211                ///
212                #[doc = concat!("let partial = ", stringify!($name), "::new(50.0f32, 80.0, 120.0);")]
213                /// let rgb = Srgb::from_color(partial.into_xyz(baked_parameters));
214                /// ```
215                #[inline]
216                pub fn into_xyz<WpParam>(
217                    self,
218                    parameters: impl Into<BakedParameters<WpParam, T::Scalar>>,
219                ) -> Xyz<WpParam::StaticWp, T>
220                where
221                    Self: Cam16IntoUnclamped<WpParam, Xyz<WpParam::StaticWp, T>, Scalar = T::Scalar>,
222                    WpParam: WhitePointParameter<T>,
223                    T: FromScalar,
224                {
225                    self.cam16_into_unclamped(parameters.into())
226                }
227
228                /// Create a partial set of CIE CAM16 attributes.
229                ///
230                #[doc = concat!("It's also possible to use `", stringify!($name), "::from` or `Cam16::into`.")]
231                #[inline]
232                pub fn from_full(full: Cam16<T>) -> Self {
233                    Self {
234                        hue: full.hue,
235                        $chromaticity: full.$chromaticity,
236                        $luminance: full.$luminance,
237                    }
238                }
239
240                /// Reconstruct a full set of CIE CAM16 attributes, using the original viewing conditions.
241                ///
242                /// ```
243                #[doc = concat!("use palette::{Srgb, IntoColor, cam16::{Cam16, ", stringify!($name), ", Parameters}};")]
244                /// use approx::assert_relative_eq;
245                ///
246                /// // Customize these according to the viewing conditions:
247                /// let mut example_parameters = Parameters::default_static_wp(40.0);
248                ///
249                /// // Optional, but saves some work:
250                /// let baked_parameters = example_parameters.bake();
251                ///
252                /// let rgb = Srgb::new(0.3f64, 0.8, 0.1);
253                /// let cam16 = Cam16::from_xyz(rgb.into_color(), baked_parameters);
254                #[doc = concat!("let partial = ", stringify!($name), "::from(cam16);")]
255                /// let reconstructed = partial.into_full(baked_parameters);
256                ///
257                /// assert_relative_eq!(cam16, reconstructed, epsilon = 0.0000000000001);
258                ///  ```
259                #[inline]
260                pub fn into_full<WpParam>(self, parameters: impl Into<BakedParameters<WpParam, T::Scalar>>) -> Cam16<T>
261                where
262                    Self: IntoCam16Unclamped<WpParam, Cam16<T>, Scalar = T::Scalar>,
263                    T: FromScalar
264                {
265                    self.into_cam16_unclamped(parameters.into())
266                }
267
268                // Turn the chromaticity and luminance into dynamically decided
269                // attributes, to help conversion to a full set of attributes.
270                #[inline(always)]
271                pub(crate) fn into_dynamic(self) -> (LuminanceType<T>, ChromaticityType<T>, Cam16Hue<T>) {
272                    (
273                        LuminanceType::$luminance_ty(self.$luminance),
274                        ChromaticityType::$chromaticity_ty(self.$chromaticity),
275                        self.hue,
276                    )
277                }
278            }
279
280            #[doc = concat!(r#""<span id=""#, stringify!($name), r#"a"></span>[`"#, stringify!($name), "a`](crate::cam16::", stringify!($name), "a)")]
281            ///implementations.
282            impl<T, A> Alpha<$name<T>, A> {
283                /// Create a partial CIE CAM16 color with transparency.
284                #[inline]
285                pub fn new<H: Into<Cam16Hue<T>>>($luminance: T, $chromaticity: T, hue: H, alpha: A) -> Self{
286                    Self::new_const($luminance.into(), $chromaticity.into(), hue.into(), alpha)
287                }
288
289                /// Create a partial CIE CAM16 color with transparency. This is the
290                #[doc = concat!("same as `", stringify!($name), "::new` without the generic hue type. It's temporary until")]
291                /// `const fn` supports traits.
292                #[inline]
293                pub const fn new_const($luminance: T, $chromaticity: T, hue: Cam16Hue<T>, alpha: A) -> Self {
294                    Alpha {
295                        color: $name::new_const($luminance, $chromaticity, hue),
296                        alpha,
297                    }
298                }
299
300                #[doc = concat!("Convert to a `(", stringify!($luminance), ", ", stringify!($chromaticity), ", hue, alpha)` tuple.")]
301                #[inline]
302                pub fn into_components(self) -> (T, T, Cam16Hue<T>, A) {
303                    (
304                        self.color.$luminance,
305                        self.color.$chromaticity,
306                        self.color.hue,
307                        self.alpha,
308                    )
309                }
310
311                #[doc = concat!("Convert from a `(", stringify!($luminance), ", ", stringify!($chromaticity), ", hue, alpha)` tuple.")]
312                #[inline]
313                pub fn from_components<H: Into<Cam16Hue<T>>>(
314                    ($luminance, $chromaticity, hue, alpha): (T, T, H, A),
315                ) -> Self {
316                    Self::new($luminance, $chromaticity, hue, alpha)
317                }
318
319                /// Derive partial CIE CAM16 attributes with transparency, for the provided
320                /// color, under the provided viewing conditions.
321                ///
322                /// ```
323                #[doc = concat!("use palette::{Srgba, IntoColor, cam16::{", stringify!($name), "a, Parameters}};")]
324                ///
325                /// // Customize these according to the viewing conditions:
326                /// let mut example_parameters = Parameters::default_static_wp(40.0);
327                ///
328                /// let rgba = Srgba::new(0.3f32, 0.8, 0.1, 0.9);
329                #[doc = concat!("let partial = ", stringify!($name), "a::from_xyz(rgba.into_color(), example_parameters);")]
330                /// ```
331                ///
332                /// It's also possible to "pre-bake" the parameters, to avoid recalculate
333                /// some of the derived values when converting multiple color value.
334                ///
335                /// ```
336                #[doc = concat!("use palette::{Srgba, IntoColor, cam16::{", stringify!($name), "a, Parameters}};")]
337                ///
338                /// // Customize these according to the viewing conditions:
339                /// let mut example_parameters = Parameters::default_static_wp(40.0);
340                ///
341                /// let baked_parameters = example_parameters.bake();
342                ///
343                /// let rgba = Srgba::new(0.3f32, 0.8, 0.1, 0.9);
344                #[doc = concat!("let partial = ", stringify!($name), "a::from_xyz(rgba.into_color(), baked_parameters);")]
345                /// ```
346                #[inline]
347                pub fn from_xyz<WpParam>(
348                    color: Alpha<Xyz<WpParam::StaticWp, T>, A>,
349                    parameters: impl Into<BakedParameters<WpParam, T::Scalar>>,
350                ) -> Self
351                where
352                    Xyz<WpParam::StaticWp, T>: IntoCam16Unclamped<WpParam, $name<T>, Scalar = T::Scalar>,
353                    T: FromScalar,
354                    WpParam: WhitePointParameter<T::Scalar>,
355                {
356                    let Alpha { color, alpha } = color;
357
358                    Alpha {
359                        color: $name::from_xyz(color, parameters),
360                        alpha,
361                    }
362                }
363
364                /// Construct an XYZ color with transparency, from these CIE CAM16
365                /// attributes, under the provided viewing conditions.
366                ///
367                /// ```
368                #[doc = concat!("use palette::{Srgba, FromColor, cam16::{", stringify!($name), "a, Parameters}};")]
369                ///
370                /// // Customize these according to the viewing conditions:
371                /// let mut example_parameters = Parameters::default_static_wp(40.0);
372                ///
373                #[doc = concat!("let partial = ", stringify!($name), "a::new(50.0f32, 80.0, 120.0, 0.9);")]
374                /// let rgba = Srgba::from_color(partial.into_xyz(example_parameters));
375                /// ```
376                ///
377                /// It's also possible to "pre-bake" the parameters, to avoid recalculate
378                /// some of the derived values when converting multiple color value.
379                ///
380                /// ```
381                #[doc = concat!("use palette::{Srgba, FromColor, cam16::{", stringify!($name), "a, Parameters}};")]
382                ///
383                /// // Customize these according to the viewing conditions:
384                /// let mut example_parameters = Parameters::default_static_wp(40.0);
385                ///
386                /// let baked_parameters = example_parameters.bake();
387                ///
388                #[doc = concat!("let partial = ", stringify!($name), "a::new(50.0f32, 80.0, 120.0, 0.9);")]
389                /// let rgba = Srgba::from_color(partial.into_xyz(baked_parameters));
390                /// ```
391                #[inline]
392                pub fn into_xyz<WpParam>(
393                    self,
394                    parameters: impl Into<BakedParameters<WpParam, T::Scalar>>,
395                ) -> Alpha<Xyz<WpParam::StaticWp, T>, A>
396                where
397                    $name<T>: Cam16IntoUnclamped<WpParam, Xyz<WpParam::StaticWp, T>, Scalar = T::Scalar>,
398                    WpParam: WhitePointParameter<T>,
399                    T: FromScalar,
400                {
401                    let Alpha { color, alpha } = self;
402
403                    Alpha {
404                        color: color.into_xyz(parameters),
405                        alpha,
406                    }
407                }
408
409                /// Create a partial set of CIE CAM16 attributes with transparency.
410                ///
411                #[doc = concat!("It's also possible to use `", stringify!($name), "a::from` or `Cam16a::into`.")]
412                #[inline]
413                pub fn from_full(full: Alpha<Cam16<T>, A>) -> Self {
414                    let Alpha { color, alpha } = full;
415
416                    Alpha {
417                        color: $name::from_full(color),
418                        alpha,
419                    }
420                }
421
422                /// Reconstruct a full set of CIE CAM16 attributes with transparency, using
423                /// the original viewing conditions.
424                ///
425                /// ```
426                #[doc = concat!("use palette::{Srgba, IntoColor, cam16::{Cam16a, ", stringify!($name), "a, Parameters}};")]
427                /// use approx::assert_relative_eq;
428                ///
429                /// // Customize these according to the viewing conditions:
430                /// let mut example_parameters = Parameters::default_static_wp(40.0);
431                ///
432                /// // Optional, but saves some work:
433                /// let baked_parameters = example_parameters.bake();
434                ///
435                /// let rgba = Srgba::new(0.3f64, 0.8, 0.1, 0.9);
436                /// let cam16a = Cam16a::from_xyz(rgba.into_color(), baked_parameters);
437                #[doc = concat!("let partial = ", stringify!($name), "a::from(cam16a);")]
438                /// let reconstructed = partial.into_full(baked_parameters);
439                ///
440                /// assert_relative_eq!(cam16a, reconstructed, epsilon = 0.0000000000001);
441                ///  ```
442                #[inline]
443                pub fn into_full<WpParam>(
444                    self,
445                    parameters: impl Into<BakedParameters<WpParam, T::Scalar>>,
446                ) -> Alpha<Cam16<T>, A>
447                where
448                    $name<T>: IntoCam16Unclamped<WpParam, Cam16<T>, Scalar = T::Scalar>,
449                    WpParam: WhitePointParameter<T>,
450                    T: FromScalar,
451                {
452                    let Alpha { color, alpha } = self;
453
454                    Alpha {
455                        color: color.into_full(parameters),
456                        alpha,
457                    }
458                }
459            }
460
461            impl<T> FromColorUnclamped<Self> for $name<T> {
462                #[inline]
463                fn from_color_unclamped(val: Self) -> Self {
464                    val
465                }
466            }
467
468            impl<T> FromColorUnclamped<Cam16<T>> for $name<T> {
469                #[inline]
470                fn from_color_unclamped(val: Cam16<T>) -> Self {
471                    Self::from_full(val)
472                }
473            }
474
475            impl<WpParam, T> Cam16FromUnclamped<WpParam, Xyz<WpParam::StaticWp, T>> for $name<T>
476            where
477                Xyz<WpParam::StaticWp, T>: IntoCam16Unclamped<WpParam, Cam16<T>>,
478                WpParam: WhitePointParameter<T>,
479            {
480                type Scalar = <Xyz<WpParam::StaticWp, T> as IntoCam16Unclamped<WpParam, Cam16<T>>>::Scalar;
481
482                fn cam16_from_unclamped(color: Xyz<WpParam::StaticWp, T>, parameters: BakedParameters<WpParam, Self::Scalar>) -> Self {
483                    color.into_cam16_unclamped(parameters).into()
484                }
485            }
486
487            impl<T> From<Cam16<T>> for $name<T> {
488                #[inline]
489                fn from(value: Cam16<T>) -> Self {
490                    Self::from_full(value)
491                }
492            }
493
494            impl<T, A> From<Alpha<Cam16<T>, A>> for Alpha<$name<T>, A> {
495                #[inline]
496                fn from(value: Alpha<Cam16<T>, A>) -> Self {
497                    Self::from_full(value)
498                }
499            }
500
501            impl<T> HasBoolMask for $name<T>
502            where
503                T: HasBoolMask,
504            {
505                type Mask = T::Mask;
506            }
507
508            #[cfg(feature = "bytemuck")]
509            unsafe impl<T> bytemuck::Zeroable for $name<T> where T: bytemuck::Zeroable {}
510
511            #[cfg(feature = "bytemuck")]
512            unsafe impl<T> bytemuck::Pod for $name<T> where T: bytemuck::Pod {}
513
514            impl_reference_component_methods_hue!($name, [$luminance, $chromaticity]);
515            impl_struct_of_arrays_methods_hue!($name, [$luminance, $chromaticity]);
516
517            impl_tuple_conversion_hue!($name as (T, T, H), Cam16Hue);
518
519            impl_is_within_bounds! {
520                $name {
521                    $luminance => [T::zero(), None],
522                    $chromaticity => [T::zero(), None]
523                }
524                where T: Zero
525            }
526            impl_clamp! {
527                $name {
528                    $luminance => [T::zero()],
529                    $chromaticity => [T::zero()]
530                }
531                other {hue}
532                where T: Zero
533            }
534
535            impl_mix_hue!($name {$luminance, $chromaticity});
536            impl_hue_ops!($name, Cam16Hue);
537
538            impl_color_add!($name, [$luminance, $chromaticity, hue]);
539            impl_color_sub!($name, [$luminance, $chromaticity, hue]);
540
541            impl_array_casts!($name<T>, [T; 3]);
542            impl_simd_array_conversion_hue!($name, [$luminance, $chromaticity]);
543            impl_struct_of_array_traits_hue!($name, Cam16HueIter, [$luminance, $chromaticity]);
544
545            impl_eq_hue!($name, Cam16Hue, [$luminance, $chromaticity, hue]);
546        }
547    };
548}
549
550/// Partial CIE CAM16 with lightness, chroma, and an alpha component.
551///
552/// See the [`Cam16Jcha` implementation in `Alpha`](crate::Alpha#Cam16Jcha).
553pub type Cam16Jcha<T> = Alpha<Cam16Jch<T>, T>;
554make_partial_cam16! {
555    cam16_jch::Cam16Jch {
556        /// The [lightness](https://cie.co.at/eilvterm/17-22-063) (J) of the
557        /// color.
558        ///
559        /// See [`Cam16::lightness`][crate::cam16::Cam16::lightness].
560        lightness: Lightness,
561
562        /// The [chroma](https://cie.co.at/eilvterm/17-22-074) (C) of the color.
563        ///
564        /// See [`Cam16::chroma`][crate::cam16::Cam16::chroma].
565        chroma: Chroma
566    }
567}
568
569/// Partial CIE CAM16 with lightness, colorfulness, and an alpha component.
570///
571/// See the [`Cam16Jmha` implementation in `Alpha`](crate::Alpha#Cam16Jmha).
572pub type Cam16Jmha<T> = Alpha<Cam16Jmh<T>, T>;
573make_partial_cam16! {
574    ///
575    /// `Cam16Jmh` can also convert from CAM16-UCS types, such as
576    /// [`Cam16UcsJmh`][crate::cam16::Cam16UcsJmh].
577    ///
578    /// ```
579    /// use palette::{Srgb, FromColor, cam16::{Cam16Jmh, Cam16UcsJmh}};
580    ///
581    /// let ucs = Cam16UcsJmh::new(50.0f32, 80.0, 120.0);
582    /// let partial_from_ucs = Cam16Jmh::from_color(ucs);
583    /// ```
584    #[palette(skip_derives(Cam16UcsJmh))]
585    cam16_jmh::Cam16Jmh {
586        /// The [lightness](https://cie.co.at/eilvterm/17-22-063) (J) of the
587        /// color.
588        ///
589        /// See [`Cam16::lightness`][crate::cam16::Cam16::lightness].
590        lightness: Lightness,
591
592        /// The [colorfulness](https://cie.co.at/eilvterm/17-22-072) (M) of the
593        /// color.
594        ///
595        /// See [`Cam16::colorfulness`][crate::cam16::Cam16::colorfulness].
596        colorfulness: Colorfulness
597    }
598}
599
600/// Partial CIE CAM16 with lightness, saturation, and an alpha component.
601///
602/// See the [`Cam16Jsha` implementation in `Alpha`](crate::Alpha#Cam16Jsha).
603pub type Cam16Jsha<T> = Alpha<Cam16Jsh<T>, T>;
604make_partial_cam16! {
605    cam16_jsh::Cam16Jsh {
606        /// The [lightness](https://cie.co.at/eilvterm/17-22-063) (J) of the
607        /// color.
608        ///
609        /// See [`Cam16::lightness`][crate::cam16::Cam16::lightness].
610        lightness: Lightness,
611
612        /// The [saturation](https://cie.co.at/eilvterm/17-22-073) (s) of the
613        /// color.
614        ///
615        /// See ['Cam16::saturation][crate::cam16::Cam16::saturation].
616        saturation: Saturation
617    }
618}
619
620/// Partial CIE CAM16 with brightness, chroma, and an alpha component.
621///
622/// See the [`Cam16Qcha` implementation in `Alpha`](crate::Alpha#Cam16Qcha).
623pub type Cam16Qcha<T> = Alpha<Cam16Qch<T>, T>;
624make_partial_cam16! {
625    cam16_qch::Cam16Qch {
626        /// The [brightness](https://cie.co.at/eilvterm/17-22-059) (Q) of the
627        /// color.
628        ///
629        /// See [`Cam16::brightness`][crate::cam16::Cam16::brightness].
630        brightness: Brightness,
631
632        /// The [chroma](https://cie.co.at/eilvterm/17-22-074) (C) of the color.
633        ///
634        /// See [`Cam16::chroma`][crate::cam16::Cam16::chroma].
635        chroma: Chroma
636    }
637}
638
639/// Partial CIE CAM16 with brightness, colorfulness, and an alpha component.
640///
641/// See the [`Cam16Qmha` implementation in `Alpha`](crate::Alpha#Cam16Qmha).
642pub type Cam16Qmha<T> = Alpha<Cam16Qmh<T>, T>;
643make_partial_cam16! {
644    cam16_qmh::Cam16Qmh {
645        /// The [brightness](https://cie.co.at/eilvterm/17-22-059) (Q) of the
646        /// color.
647        ///
648        /// See [`Cam16::brightness`][crate::cam16::Cam16::brightness].
649        brightness: Brightness,
650
651        /// The [colorfulness](https://cie.co.at/eilvterm/17-22-072) (M) of the
652        /// color.
653        ///
654        /// See [`Cam16::colorfulness`][crate::cam16::Cam16::colorfulness].
655        colorfulness: Colorfulness
656    }
657}
658
659/// Partial CIE CAM16 with brightness, saturation, and an alpha component.
660///
661/// See the [`Cam16Qsha` implementation in `Alpha`](crate::Alpha#Cam16Qsha).
662pub type Cam16Qsha<T> = Alpha<Cam16Qsh<T>, T>;
663make_partial_cam16! {
664    cam16_qsh::Cam16Qsh {
665        /// The [brightness](https://cie.co.at/eilvterm/17-22-059) (Q) of the
666        /// color.
667        ///
668        /// See [`Cam16::brightness`][crate::cam16::Cam16::brightness].
669        brightness: Brightness,
670
671        /// The [saturation](https://cie.co.at/eilvterm/17-22-073) (s) of the
672        /// color.
673        ///
674        /// See ['Cam16::saturation][crate::cam16::Cam16::saturation].
675        saturation: Saturation
676    }
677}
678
679impl<T> FromColorUnclamped<Cam16UcsJmh<T>> for Cam16Jmh<T>
680where
681    T: Real + One + Exp + Arithmetics + Clone,
682{
683    #[inline]
684    fn from_color_unclamped(val: Cam16UcsJmh<T>) -> Self {
685        let colorfulness =
686            ((val.colorfulness * T::from_f64(0.0228)).exp() - T::one()) / T::from_f64(0.0228);
687        let lightness =
688            val.lightness.clone() / (T::from_f64(1.7) - T::from_f64(0.007) * val.lightness);
689
690        Self {
691            hue: val.hue,
692            colorfulness,
693            lightness,
694        }
695    }
696}
697
698#[cfg(test)]
699#[cfg(feature = "approx")]
700mod test {
701    use super::{Cam16Jch, Cam16Jmh, Cam16Jsh, Cam16Qch, Cam16Qmh, Cam16Qsh};
702    use crate::{
703        cam16::{Cam16, Parameters, StaticWp},
704        convert::IntoColorUnclamped,
705        white_point::D65,
706        Srgb,
707    };
708
709    macro_rules! assert_partial_to_full {
710        ($cam16: expr) => {assert_partial_to_full!($cam16,)};
711        ($cam16: expr, $($params:tt)*) => {
712            assert_relative_eq!(
713                Cam16Jch::from($cam16).into_full(Parameters::<StaticWp<D65>, _>::TEST_DEFAULTS),
714                $cam16,
715                $($params)*
716            );
717            assert_relative_eq!(
718                Cam16Jmh::from($cam16).into_full(Parameters::<StaticWp<D65>, _>::TEST_DEFAULTS),
719                $cam16,
720                $($params)*
721            );
722            assert_relative_eq!(
723                Cam16Jsh::from($cam16).into_full(Parameters::<StaticWp<D65>, _>::TEST_DEFAULTS),
724                $cam16,
725                $($params)*
726            );
727
728            assert_relative_eq!(
729                Cam16Qch::from($cam16).into_full(Parameters::<StaticWp<D65>, _>::TEST_DEFAULTS),
730                $cam16,
731                $($params)*
732            );
733            assert_relative_eq!(
734                Cam16Qmh::from($cam16).into_full(Parameters::<StaticWp<D65>, _>::TEST_DEFAULTS),
735                $cam16,
736                $($params)*
737            );
738            assert_relative_eq!(
739                Cam16Qsh::from($cam16).into_full(Parameters::<StaticWp<D65>, _>::TEST_DEFAULTS),
740                $cam16,
741                $($params)*
742            );
743        };
744    }
745
746    #[test]
747    fn example_blue() {
748        // Uses the example color from https://observablehq.com/@jrus/cam16
749        let xyz = Srgb::from(0x5588cc).into_linear().into_color_unclamped();
750        let cam16: Cam16<f64> = Cam16::from_xyz(xyz, Parameters::TEST_DEFAULTS);
751        assert_partial_to_full!(cam16, epsilon = 0.0000000000001);
752    }
753
754    #[test]
755    fn black() {
756        // Checks against the output from https://observablehq.com/@jrus/cam16
757        let xyz = Srgb::from(0x000000).into_linear().into_color_unclamped();
758        let cam16: Cam16<f64> = Cam16::from_xyz(xyz, Parameters::TEST_DEFAULTS);
759        assert_partial_to_full!(cam16);
760    }
761
762    #[test]
763    fn white() {
764        // Checks against the output from https://observablehq.com/@jrus/cam16
765        let xyz = Srgb::from(0xffffff).into_linear().into_color_unclamped();
766        let cam16: Cam16<f64> = Cam16::from_xyz(xyz, Parameters::TEST_DEFAULTS);
767        assert_partial_to_full!(cam16, epsilon = 0.000000000000001);
768    }
769
770    #[test]
771    fn red() {
772        // Checks against the output from https://observablehq.com/@jrus/cam16
773        let xyz = Srgb::from(0xff0000).into_linear().into_color_unclamped();
774        let cam16: Cam16<f64> = Cam16::from_xyz(xyz, Parameters::TEST_DEFAULTS);
775        assert_partial_to_full!(cam16, epsilon = 0.0000000000001);
776    }
777
778    #[test]
779    fn green() {
780        // Checks against the output from https://observablehq.com/@jrus/cam16
781        let xyz = Srgb::from(0x00ff00).into_linear().into_color_unclamped();
782        let cam16: Cam16<f64> = Cam16::from_xyz(xyz, Parameters::TEST_DEFAULTS);
783        assert_partial_to_full!(cam16, epsilon = 0.0000000000001);
784    }
785
786    #[test]
787    fn blue() {
788        // Checks against the output from https://observablehq.com/@jrus/cam16
789        let xyz = Srgb::from(0x0000ff).into_linear().into_color_unclamped();
790        let cam16: Cam16<f64> = Cam16::from_xyz(xyz, Parameters::TEST_DEFAULTS);
791        assert_partial_to_full!(cam16);
792    }
793}