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}