use core::ops::Mul;
use crate::{
angle::RealAngle,
bool_mask::HasBoolMask,
color_difference::{DeltaE, EuclideanDistance, ImprovedDeltaE},
convert::FromColorUnclamped,
num::{MinMax, Powf, Real, Sqrt, Trigonometry, Zero},
Alpha,
};
use super::Cam16UcsJmh;
pub type Cam16UcsJaba<T> = Alpha<Cam16UcsJab<T>, T>;
#[derive(Clone, Copy, Debug, Default, WithAlpha, ArrayCast, FromColorUnclamped)]
#[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))]
#[palette(
palette_internal,
component = "T",
skip_derives(Cam16UcsJmh, Cam16UcsJab)
)]
#[repr(C)]
pub struct Cam16UcsJab<T> {
pub lightness: T,
pub a: T,
pub b: T,
}
impl<T> Cam16UcsJab<T> {
pub const fn new(lightness: T, a: T, b: T) -> Self {
Self { lightness, a, b }
}
pub fn into_components(self) -> (T, T, T) {
(self.lightness, self.a, self.b)
}
pub fn from_components((lightness, a, b): (T, T, T)) -> Self {
Self::new(lightness, a, b)
}
}
impl<T> Cam16UcsJab<T>
where
T: Zero + Real,
{
pub fn min_lightness() -> T {
T::zero()
}
pub fn max_lightness() -> T {
T::from_f64(100.0)
}
pub fn min_srgb_a() -> T {
T::from_f64(-50.0)
}
pub fn max_srgb_a() -> T {
T::from_f64(50.0)
}
pub fn min_srgb_b() -> T {
T::from_f64(-50.0)
}
pub fn max_srgb_b() -> T {
T::from_f64(50.0)
}
}
impl<T, A> Alpha<Cam16UcsJab<T>, A> {
pub const fn new(lightness: T, a: T, b: T, alpha: A) -> Self {
Self {
color: Cam16UcsJab::new(lightness, a, b),
alpha,
}
}
pub fn into_components(self) -> (T, T, T, A) {
(self.color.lightness, self.color.a, self.color.b, self.alpha)
}
pub fn from_components((lightness, a, b, alpha): (T, T, T, A)) -> Self {
Self::new(lightness, a, b, alpha)
}
}
impl<T> FromColorUnclamped<Cam16UcsJab<T>> for Cam16UcsJab<T> {
fn from_color_unclamped(val: Cam16UcsJab<T>) -> Self {
val
}
}
impl<T> FromColorUnclamped<Cam16UcsJmh<T>> for Cam16UcsJab<T>
where
T: RealAngle + Zero + Mul<Output = T> + Trigonometry + MinMax + Clone,
{
fn from_color_unclamped(val: Cam16UcsJmh<T>) -> Self {
let (a, b) = val.hue.into_cartesian();
let colorfulness = val.colorfulness.max(T::zero());
Self {
lightness: val.lightness,
a: a * colorfulness.clone(),
b: b * colorfulness,
}
}
}
impl<T> DeltaE for Cam16UcsJab<T>
where
Self: EuclideanDistance<Scalar = T>,
T: Sqrt,
{
type Scalar = T;
#[inline]
fn delta_e(self, other: Self) -> Self::Scalar {
self.distance(other)
}
}
impl<T> ImprovedDeltaE for Cam16UcsJab<T>
where
Self: DeltaE<Scalar = T> + EuclideanDistance<Scalar = T>,
T: Real + Mul<T, Output = T> + Powf,
{
#[inline]
fn improved_delta_e(self, other: Self) -> Self::Scalar {
T::from_f64(1.41) * self.distance_squared(other).powf(T::from_f64(0.63 * 0.5))
}
}
impl<T> HasBoolMask for Cam16UcsJab<T>
where
T: HasBoolMask,
{
type Mask = T::Mask;
}
#[cfg(feature = "bytemuck")]
unsafe impl<T> bytemuck::Zeroable for Cam16UcsJab<T> where T: bytemuck::Zeroable {}
#[cfg(feature = "bytemuck")]
unsafe impl<T> bytemuck::Pod for Cam16UcsJab<T> where T: bytemuck::Pod {}
impl_reference_component_methods!(Cam16UcsJab, [lightness, a, b]);
impl_struct_of_arrays_methods!(Cam16UcsJab, [lightness, a, b]);
impl_tuple_conversion!(Cam16UcsJab as (T, T, T));
impl_is_within_bounds! {
Cam16UcsJab {
lightness => [Self::min_lightness(), Self::max_lightness()]
}
where T: Real + Zero
}
impl_clamp! {
Cam16UcsJab {
lightness => [Self::min_lightness(), Self::max_lightness()]
}
other {a, b}
where T: Real + Zero
}
impl_mix!(Cam16UcsJab);
impl_lighten!(Cam16UcsJab increase {lightness => [Self::min_lightness(), Self::max_lightness()]} other {a, b});
impl_premultiply!(Cam16UcsJab { lightness, a, b });
impl_euclidean_distance!(Cam16UcsJab { lightness, a, b });
impl_hyab!(Cam16UcsJab {
lightness: lightness,
chroma1: a,
chroma2: b
});
impl_lab_color_schemes!(Cam16UcsJab[lightness]);
impl_color_add!(Cam16UcsJab, [lightness, a, b]);
impl_color_sub!(Cam16UcsJab, [lightness, a, b]);
impl_color_mul!(Cam16UcsJab, [lightness, a, b]);
impl_color_div!(Cam16UcsJab, [lightness, a, b]);
impl_array_casts!(Cam16UcsJab<T>, [T; 3]);
impl_simd_array_conversion!(Cam16UcsJab, [lightness, a, b]);
impl_struct_of_array_traits!(Cam16UcsJab, [lightness, a, b]);
impl_eq!(Cam16UcsJab, [lightness, a, b]);
impl_rand_traits_cartesian!(
UniformCam16UcsJab,
Cam16UcsJab {
lightness => [|x| x * Cam16UcsJab::<T>::max_lightness()],
a => [|x| Cam16UcsJab::<T>::min_srgb_a() + x * (Cam16UcsJab::<T>::max_srgb_a() - Cam16UcsJab::<T>::min_srgb_a())],
b => [|x| Cam16UcsJab::<T>::min_srgb_b() + x * (Cam16UcsJab::<T>::max_srgb_b() - Cam16UcsJab::<T>::min_srgb_b())]
}
where T: Real + Zero + core::ops::Add<Output = T> + core::ops::Sub<Output = T> + core::ops::Mul<Output = T>
);
#[cfg(test)]
mod test {
#[cfg(feature = "approx")]
use crate::{cam16::Cam16Jmh, convert::FromColorUnclamped};
use super::Cam16UcsJab;
#[test]
fn ranges() {
assert_ranges! {
Cam16UcsJab<f64>;
clamped {
lightness: 0.0 => 100.0
}
clamped_min {}
unclamped {
a: -100.0 => 100.0,
b: -100.0 => 100.0
}
}
}
#[cfg(feature = "approx")]
#[test]
fn cam16_roundtrip() {
let ucs = Cam16UcsJab::new(50.0f64, 80.0, -30.0);
let cam16 = Cam16Jmh::from_color_unclamped(ucs);
assert_relative_eq!(
Cam16UcsJab::from_color_unclamped(cam16),
ucs,
epsilon = 0.0000000000001
);
}
raw_pixel_conversion_tests!(Cam16UcsJab<>: lightness, a, b);
raw_pixel_conversion_fail_tests!(Cam16UcsJab<>: lightness, a, b);
struct_of_arrays_tests!(
Cam16UcsJab[lightness, a, b],
super::Cam16UcsJaba::new(0.1f32, 0.2, 0.3, 0.4),
super::Cam16UcsJaba::new(0.2, 0.3, 0.4, 0.5),
super::Cam16UcsJaba::new(0.3, 0.4, 0.5, 0.6)
);
#[cfg(feature = "serializing")]
#[test]
fn serialize() {
let serialized = ::serde_json::to_string(&Cam16UcsJab::<f32>::new(0.3, 0.8, 0.1)).unwrap();
assert_eq!(serialized, r#"{"lightness":0.3,"a":0.8,"b":0.1}"#);
}
#[cfg(feature = "serializing")]
#[test]
fn deserialize() {
let deserialized: Cam16UcsJab<f32> =
::serde_json::from_str(r#"{"lightness":0.3,"a":0.8,"b":0.1}"#).unwrap();
assert_eq!(deserialized, Cam16UcsJab::new(0.3, 0.8, 0.1));
}
test_uniform_distribution! {
Cam16UcsJab<f32> {
lightness: (0.0, 100.0),
a: (-50.0, 50.0),
b: (-50.0, 50.0)
},
min: Cam16UcsJab::new(0.0f32, -50.0, -50.0),
max: Cam16UcsJab::new(100.0, 50.0, 50.0)
}
}