1use core::ops::Mul;
2
3use crate::{
4 angle::RealAngle,
5 bool_mask::HasBoolMask,
6 color_difference::{DeltaE, EuclideanDistance, ImprovedDeltaE},
7 convert::FromColorUnclamped,
8 num::{MinMax, Powf, Real, Sqrt, Trigonometry, Zero},
9 Alpha,
10};
11
12use super::Cam16UcsJmh;
13
14pub type Cam16UcsJaba<T> = Alpha<Cam16UcsJab<T>, T>;
19
20#[derive(Clone, Copy, Debug, Default, WithAlpha, ArrayCast, FromColorUnclamped)]
51#[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))]
52#[palette(
53 palette_internal,
54 component = "T",
55 skip_derives(Cam16UcsJmh, Cam16UcsJab)
56)]
57#[repr(C)]
58pub struct Cam16UcsJab<T> {
59 pub lightness: T,
64
65 pub a: T,
70
71 pub b: T,
76}
77
78impl<T> Cam16UcsJab<T> {
79 pub const fn new(lightness: T, a: T, b: T) -> Self {
81 Self { lightness, a, b }
82 }
83
84 pub fn into_components(self) -> (T, T, T) {
86 (self.lightness, self.a, self.b)
87 }
88
89 pub fn from_components((lightness, a, b): (T, T, T)) -> Self {
91 Self::new(lightness, a, b)
92 }
93}
94
95impl<T> Cam16UcsJab<T>
96where
97 T: Zero + Real,
98{
99 pub fn min_lightness() -> T {
101 T::zero()
102 }
103
104 pub fn max_lightness() -> T {
106 T::from_f64(100.0)
107 }
108
109 pub fn min_srgb_a() -> T {
117 T::from_f64(-50.0)
119 }
120
121 pub fn max_srgb_a() -> T {
129 T::from_f64(50.0)
131 }
132
133 pub fn min_srgb_b() -> T {
141 T::from_f64(-50.0)
143 }
144
145 pub fn max_srgb_b() -> T {
153 T::from_f64(50.0)
155 }
156}
157
158impl<T, A> Alpha<Cam16UcsJab<T>, A> {
160 pub const fn new(lightness: T, a: T, b: T, alpha: A) -> Self {
162 Self {
163 color: Cam16UcsJab::new(lightness, a, b),
164 alpha,
165 }
166 }
167
168 pub fn into_components(self) -> (T, T, T, A) {
170 (self.color.lightness, self.color.a, self.color.b, self.alpha)
171 }
172
173 pub fn from_components((lightness, a, b, alpha): (T, T, T, A)) -> Self {
175 Self::new(lightness, a, b, alpha)
176 }
177}
178
179impl<T> FromColorUnclamped<Cam16UcsJab<T>> for Cam16UcsJab<T> {
180 fn from_color_unclamped(val: Cam16UcsJab<T>) -> Self {
181 val
182 }
183}
184
185impl<T> FromColorUnclamped<Cam16UcsJmh<T>> for Cam16UcsJab<T>
186where
187 T: RealAngle + Zero + Mul<Output = T> + Trigonometry + MinMax + Clone,
188{
189 fn from_color_unclamped(val: Cam16UcsJmh<T>) -> Self {
190 let (a, b) = val.hue.into_cartesian();
191 let colorfulness = val.colorfulness.max(T::zero());
192
193 Self {
194 lightness: val.lightness,
195 a: a * colorfulness.clone(),
196 b: b * colorfulness,
197 }
198 }
199}
200
201impl<T> DeltaE for Cam16UcsJab<T>
202where
203 Self: EuclideanDistance<Scalar = T>,
204 T: Sqrt,
205{
206 type Scalar = T;
207
208 #[inline]
209 fn delta_e(self, other: Self) -> Self::Scalar {
210 self.distance(other)
211 }
212}
213
214impl<T> ImprovedDeltaE for Cam16UcsJab<T>
215where
216 Self: DeltaE<Scalar = T> + EuclideanDistance<Scalar = T>,
217 T: Real + Mul<T, Output = T> + Powf,
218{
219 #[inline]
220 fn improved_delta_e(self, other: Self) -> Self::Scalar {
221 T::from_f64(1.41) * self.distance_squared(other).powf(T::from_f64(0.63 * 0.5))
228 }
229}
230
231impl<T> HasBoolMask for Cam16UcsJab<T>
232where
233 T: HasBoolMask,
234{
235 type Mask = T::Mask;
236}
237
238#[cfg(feature = "bytemuck")]
239unsafe impl<T> bytemuck::Zeroable for Cam16UcsJab<T> where T: bytemuck::Zeroable {}
240
241#[cfg(feature = "bytemuck")]
242unsafe impl<T> bytemuck::Pod for Cam16UcsJab<T> where T: bytemuck::Pod {}
243
244impl_reference_component_methods!(Cam16UcsJab, [lightness, a, b]);
247impl_struct_of_arrays_methods!(Cam16UcsJab, [lightness, a, b]);
248
249impl_tuple_conversion!(Cam16UcsJab as (T, T, T));
250
251impl_is_within_bounds! {
252 Cam16UcsJab {
253 lightness => [Self::min_lightness(), Self::max_lightness()]
254 }
255 where T: Real + Zero
256}
257impl_clamp! {
258 Cam16UcsJab {
259 lightness => [Self::min_lightness(), Self::max_lightness()]
260 }
261 other {a, b}
262 where T: Real + Zero
263}
264
265impl_mix!(Cam16UcsJab);
266impl_lighten!(Cam16UcsJab increase {lightness => [Self::min_lightness(), Self::max_lightness()]} other {a, b});
267impl_premultiply!(Cam16UcsJab { lightness, a, b });
268impl_euclidean_distance!(Cam16UcsJab { lightness, a, b });
269impl_hyab!(Cam16UcsJab {
270 lightness: lightness,
271 chroma1: a,
272 chroma2: b
273});
274impl_lab_color_schemes!(Cam16UcsJab[lightness]);
275
276impl_color_add!(Cam16UcsJab, [lightness, a, b]);
277impl_color_sub!(Cam16UcsJab, [lightness, a, b]);
278impl_color_mul!(Cam16UcsJab, [lightness, a, b]);
279impl_color_div!(Cam16UcsJab, [lightness, a, b]);
280
281impl_array_casts!(Cam16UcsJab<T>, [T; 3]);
282impl_simd_array_conversion!(Cam16UcsJab, [lightness, a, b]);
283impl_struct_of_array_traits!(Cam16UcsJab, [lightness, a, b]);
284
285impl_eq!(Cam16UcsJab, [lightness, a, b]);
286
287impl_rand_traits_cartesian!(
288 UniformCam16UcsJab,
289 Cam16UcsJab {
290 lightness => [|x| x * Cam16UcsJab::<T>::max_lightness()],
291 a => [|x| Cam16UcsJab::<T>::min_srgb_a() + x * (Cam16UcsJab::<T>::max_srgb_a() - Cam16UcsJab::<T>::min_srgb_a())],
292 b => [|x| Cam16UcsJab::<T>::min_srgb_b() + x * (Cam16UcsJab::<T>::max_srgb_b() - Cam16UcsJab::<T>::min_srgb_b())]
293 }
294 where T: Real + Zero + core::ops::Add<Output = T> + core::ops::Sub<Output = T> + core::ops::Mul<Output = T>
295);
296
297#[cfg(test)]
300mod test {
301 #[cfg(feature = "approx")]
302 use crate::{cam16::Cam16Jmh, convert::FromColorUnclamped};
303
304 use super::Cam16UcsJab;
305
306 #[test]
307 fn ranges() {
308 assert_ranges! {
309 Cam16UcsJab<f64>;
310 clamped {
311 lightness: 0.0 => 100.0
312 }
313 clamped_min {}
314 unclamped {
315 a: -100.0 => 100.0,
316 b: -100.0 => 100.0
317 }
318 }
319 }
320
321 #[cfg(feature = "approx")]
322 #[test]
323 fn cam16_roundtrip() {
324 let ucs = Cam16UcsJab::new(50.0f64, 80.0, -30.0);
325 let cam16 = Cam16Jmh::from_color_unclamped(ucs);
326 assert_relative_eq!(
327 Cam16UcsJab::from_color_unclamped(cam16),
328 ucs,
329 epsilon = 0.0000000000001
330 );
331 }
332
333 raw_pixel_conversion_tests!(Cam16UcsJab<>: lightness, a, b);
334 raw_pixel_conversion_fail_tests!(Cam16UcsJab<>: lightness, a, b);
335
336 struct_of_arrays_tests!(
337 Cam16UcsJab[lightness, a, b],
338 super::Cam16UcsJaba::new(0.1f32, 0.2, 0.3, 0.4),
339 super::Cam16UcsJaba::new(0.2, 0.3, 0.4, 0.5),
340 super::Cam16UcsJaba::new(0.3, 0.4, 0.5, 0.6)
341 );
342
343 #[cfg(feature = "serializing")]
344 #[test]
345 fn serialize() {
346 let serialized = ::serde_json::to_string(&Cam16UcsJab::<f32>::new(0.3, 0.8, 0.1)).unwrap();
347
348 assert_eq!(serialized, r#"{"lightness":0.3,"a":0.8,"b":0.1}"#);
349 }
350
351 #[cfg(feature = "serializing")]
352 #[test]
353 fn deserialize() {
354 let deserialized: Cam16UcsJab<f32> =
355 ::serde_json::from_str(r#"{"lightness":0.3,"a":0.8,"b":0.1}"#).unwrap();
356
357 assert_eq!(deserialized, Cam16UcsJab::new(0.3, 0.8, 0.1));
358 }
359
360 test_uniform_distribution! {
361 Cam16UcsJab<f32> {
362 lightness: (0.0, 100.0),
363 a: (-50.0, 50.0),
364 b: (-50.0, 50.0)
365 },
366 min: Cam16UcsJab::new(0.0f32, -50.0, -50.0),
367 max: Cam16UcsJab::new(100.0, 50.0, 50.0)
368 }
369}