use crate::{
angle::RealAngle,
bool_mask::{HasBoolMask, LazySelect},
hues::Cam16Hue,
num::{Abs, Arithmetics, FromScalar, PartialCmp, Powf, Real, Signum, Sqrt, Trigonometry, Zero},
Alpha, GetHue, Xyz,
};
use super::{
BakedParameters, Cam16FromUnclamped, Cam16IntoUnclamped, Cam16Jch, Cam16Jmh, Cam16Jsh,
Cam16Qch, Cam16Qmh, Cam16Qsh, FromCam16Unclamped, IntoCam16Unclamped, WhitePointParameter,
};
pub type Cam16a<T> = Alpha<Cam16<T>, T>;
#[derive(Clone, Copy, Debug, WithAlpha, Default)]
#[palette(palette_internal, component = "T")]
#[repr(C)]
pub struct Cam16<T> {
#[doc(alias = "J")]
pub lightness: T,
#[doc(alias = "C")]
pub chroma: T,
#[doc(alias = "h")]
pub hue: Cam16Hue<T>,
#[doc(alias = "Q")]
pub brightness: T,
#[doc(alias = "M")]
pub colorfulness: T,
#[doc(alias = "s")]
pub saturation: T,
}
impl<T> Cam16<T> {
#[inline]
pub fn from_xyz<WpParam>(
color: Xyz<WpParam::StaticWp, T>,
parameters: impl Into<BakedParameters<WpParam, T::Scalar>>,
) -> Self
where
Xyz<WpParam::StaticWp, T>: IntoCam16Unclamped<WpParam, Self, Scalar = T::Scalar>,
T: FromScalar,
WpParam: WhitePointParameter<T::Scalar>,
{
color.into_cam16_unclamped(parameters.into())
}
#[inline]
pub fn into_xyz<WpParam>(
self,
parameters: impl Into<BakedParameters<WpParam, T::Scalar>>,
) -> Xyz<WpParam::StaticWp, T>
where
Self: Cam16IntoUnclamped<WpParam, Xyz<WpParam::StaticWp, T>, Scalar = T::Scalar>,
WpParam: WhitePointParameter<T>,
T: FromScalar,
{
self.cam16_into_unclamped(parameters.into())
}
}
impl<T, A> Alpha<Cam16<T>, A> {
#[inline]
pub fn from_xyz<WpParam>(
color: Alpha<Xyz<WpParam::StaticWp, T>, A>,
parameters: impl Into<BakedParameters<WpParam, T::Scalar>>,
) -> Self
where
Xyz<WpParam::StaticWp, T>: IntoCam16Unclamped<WpParam, Cam16<T>, Scalar = T::Scalar>,
T: FromScalar,
WpParam: WhitePointParameter<T::Scalar>,
{
let Alpha { color, alpha } = color;
Alpha {
color: Cam16::from_xyz(color, parameters),
alpha,
}
}
#[inline]
pub fn into_xyz<WpParam>(
self,
parameters: impl Into<BakedParameters<WpParam, T::Scalar>>,
) -> Alpha<Xyz<WpParam::StaticWp, T>, A>
where
Cam16<T>: Cam16IntoUnclamped<WpParam, Xyz<WpParam::StaticWp, T>, Scalar = T::Scalar>,
WpParam: WhitePointParameter<T>,
T: FromScalar,
{
let Alpha { color, alpha } = self;
Alpha {
color: color.into_xyz(parameters),
alpha,
}
}
}
impl<WpParam, T> Cam16FromUnclamped<WpParam, Xyz<WpParam::StaticWp, T>> for Cam16<T>
where
WpParam: WhitePointParameter<T::Scalar>,
T: Real
+ FromScalar
+ Arithmetics
+ Powf
+ Sqrt
+ Abs
+ Signum
+ Trigonometry
+ RealAngle
+ Clone,
T::Scalar: Clone,
{
type Scalar = T::Scalar;
fn cam16_from_unclamped(
color: Xyz<WpParam::StaticWp, T>,
parameters: BakedParameters<WpParam, Self::Scalar>,
) -> Self {
super::math::xyz_to_cam16(color.with_white_point(), parameters.inner)
}
}
macro_rules! impl_from_cam16_partial {
($($name: ident),+) => {
$(
impl<WpParam, T> Cam16FromUnclamped<WpParam, $name<T>> for Cam16<T>
where
WpParam: WhitePointParameter<T>,
T: Real + FromScalar + Zero + Arithmetics + Sqrt + PartialCmp + Clone,
T::Mask: LazySelect<T> + Clone,
T::Scalar: Clone
{
type Scalar = T::Scalar;
fn cam16_from_unclamped(
cam16: $name<T>,
parameters: crate::cam16::BakedParameters<WpParam, Self::Scalar>,
) -> Self {
let (
luminance,
chromaticity,
hue,
) = cam16.into_dynamic();
let (lightness, brightness) = luminance.into_cam16(parameters.clone());
let (chroma, colorfulness, saturation) =
chromaticity.into_cam16(lightness.clone(), parameters);
Cam16 {
lightness,
chroma,
hue,
brightness,
colorfulness,
saturation,
}
}
}
impl<WpParam, T> FromCam16Unclamped<WpParam, $name<T>> for Cam16<T>
where
Self: Cam16FromUnclamped<WpParam, $name<T>>,
{
type Scalar = <Self as Cam16FromUnclamped<WpParam, $name<T>>>::Scalar;
fn from_cam16_unclamped(
cam16: $name<T>,
parameters: crate::cam16::BakedParameters<WpParam, Self::Scalar>,
) -> Self {
Self::cam16_from_unclamped(cam16, parameters)
}
}
)+
};
}
impl_from_cam16_partial!(Cam16Jmh, Cam16Jch, Cam16Jsh, Cam16Qmh, Cam16Qch, Cam16Qsh);
impl<T> GetHue for Cam16<T>
where
T: Clone,
{
type Hue = Cam16Hue<T>;
fn get_hue(&self) -> Cam16Hue<T> {
self.hue.clone()
}
}
impl<T> HasBoolMask for Cam16<T>
where
T: HasBoolMask,
{
type Mask = T::Mask;
}
impl_is_within_bounds! {
Cam16 {
lightness => [T::zero(), None],
chroma => [T::zero(), None],
brightness => [T::zero(), None],
colorfulness => [T::zero(), None],
saturation => [T::zero(), None]
}
where T: Zero
}
impl_clamp! {
Cam16 {
lightness => [T::zero()],
chroma => [T::zero()],
brightness => [T::zero()],
colorfulness => [T::zero()],
saturation => [T::zero()]
}
other {hue}
where T: Zero
}
impl_eq_hue!(
Cam16,
Cam16Hue,
[lightness, chroma, brightness, colorfulness, saturation]
);
impl_simd_array_conversion_hue!(
Cam16,
[lightness, chroma, brightness, colorfulness, saturation]
);
#[cfg(test)]
#[cfg(feature = "approx")]
mod test {
use crate::{
cam16::{
math::{chromaticity::ChromaticityType, luminance::LuminanceType},
BakedParameters, Cam16Jch, Parameters,
},
convert::{FromColorUnclamped, IntoColorUnclamped},
Srgb,
};
use super::Cam16;
macro_rules! assert_cam16_to_rgb {
($cam16:expr, $rgb:expr, $($params:tt)*) => {
let cam16 = $cam16;
let parameters = BakedParameters::from(Parameters::TEST_DEFAULTS);
let rgb: Srgb<f64> = cam16.into_xyz(parameters).into_color_unclamped();
assert_relative_eq!(rgb, $rgb, $($params)*);
let chromaticities = [
ChromaticityType::Chroma(cam16.chroma),
ChromaticityType::Colorfulness(cam16.colorfulness),
ChromaticityType::Saturation(cam16.saturation),
];
let luminances = [
LuminanceType::Lightness(cam16.lightness),
LuminanceType::Brightness(cam16.brightness),
];
for luminance in luminances {
for chromaticity in chromaticities {
let partial = (
luminance,
chromaticity,
cam16.hue,
);
let xyz = crate::cam16::math::cam16_to_xyz(partial, parameters.inner).with_white_point();
assert_relative_eq!(
Srgb::<f64>::from_color_unclamped(xyz),
$rgb,
$($params)*
);
}
}
};
}
#[test]
fn converts_with_jch() {
let parameters = Parameters::TEST_DEFAULTS.bake();
let xyz = Srgb::from(0x5588cc).into_linear().into_color_unclamped();
let mut cam16: Cam16<f64> = Cam16::from_xyz(xyz, parameters);
let cam16jch = Cam16Jch::from_full(cam16);
cam16.brightness = 0.0;
cam16.colorfulness = 0.0;
cam16.saturation = 0.0;
assert_eq!(cam16.into_xyz(parameters), cam16jch.into_xyz(parameters));
}
#[test]
fn example_blue() {
let xyz = Srgb::from(0x5588cc).into_linear().into_color_unclamped();
let mut cam16: Cam16<f64> = Cam16::from_xyz(xyz, Parameters::TEST_DEFAULTS);
cam16.hue = cam16.hue.into_positive_degrees().into();
assert_relative_eq!(
cam16,
Cam16 {
lightness: 45.544264720360346,
chroma: 45.07001048293764,
hue: 259.225345298129.into(),
brightness: 132.96974182692045,
colorfulness: 39.4130607870103,
saturation: 54.4432031413259,
},
epsilon = 0.01
);
assert_cam16_to_rgb!(
cam16,
Srgb::from(0x5588cc).into_format(),
epsilon = 0.0000001
);
}
#[test]
fn black() {
let xyz = Srgb::from(0x000000).into_linear().into_color_unclamped();
let mut cam16: Cam16<f64> = Cam16::from_xyz(xyz, Parameters::TEST_DEFAULTS);
cam16.hue = cam16.hue.into_positive_degrees().into();
assert_relative_eq!(
cam16,
Cam16 {
lightness: 0.0,
chroma: 0.0,
hue: 0.0.into(),
brightness: 0.0,
colorfulness: 0.0,
saturation: 0.0,
},
epsilon = 0.01
);
assert_cam16_to_rgb!(
cam16,
Srgb::from(0x000000).into_format(),
epsilon = 0.0000001
);
}
#[test]
fn white() {
let xyz = Srgb::from(0xffffff).into_linear().into_color_unclamped();
let mut cam16: Cam16<f64> = Cam16::from_xyz(xyz, Parameters::TEST_DEFAULTS);
cam16.hue = cam16.hue.into_positive_degrees().into();
assert_relative_eq!(
cam16,
Cam16 {
lightness: 99.99955537650459,
chroma: 2.1815254387079435,
hue: 209.49854407518228.into(),
brightness: 197.03120459014184,
colorfulness: 1.9077118865271965,
saturation: 9.839859256901553,
},
epsilon = 0.1
);
assert_cam16_to_rgb!(
cam16,
Srgb::from(0xffffff).into_format(),
epsilon = 0.0000001
);
}
#[test]
fn red() {
let xyz = Srgb::from(0xff0000).into_linear().into_color_unclamped();
let mut cam16: Cam16<f64> = Cam16::from_xyz(xyz, Parameters::TEST_DEFAULTS);
cam16.hue = cam16.hue.into_positive_degrees().into();
assert_relative_eq!(
cam16,
Cam16 {
lightness: 46.23623443823762,
chroma: 113.27879472174797,
hue: 27.412485587695937.into(),
brightness: 133.9760614641257,
colorfulness: 99.06063864657237,
saturation: 85.98782392745971,
},
epsilon = 0.01
);
assert_cam16_to_rgb!(cam16, Srgb::from(0xff0000).into_format(), epsilon = 0.00001);
}
#[test]
fn green() {
let xyz = Srgb::from(0x00ff00).into_linear().into_color_unclamped();
let mut cam16: Cam16<f64> = Cam16::from_xyz(xyz, Parameters::TEST_DEFAULTS);
cam16.hue = cam16.hue.into_positive_degrees().into();
assert_relative_eq!(
cam16,
Cam16 {
lightness: 79.23121430933533,
chroma: 107.77869525794452,
hue: 141.93451307926003.into(),
brightness: 175.38164288466993,
colorfulness: 94.25088262080988,
saturation: 73.30787758114869,
},
epsilon = 0.01
);
assert_cam16_to_rgb!(
cam16,
Srgb::from(0x00ff00).into_format(),
epsilon = 0.000001
);
}
#[test]
fn blue() {
let xyz = Srgb::from(0x0000ff).into_linear().into_color_unclamped();
let mut cam16: Cam16<f64> = Cam16::from_xyz(xyz, Parameters::TEST_DEFAULTS);
cam16.hue = cam16.hue.into_positive_degrees().into();
assert_relative_eq!(
cam16,
Cam16 {
lightness: 25.22701796474445,
chroma: 86.59618504567312,
hue: 282.81848901862566.into(),
brightness: 98.96210767195342,
colorfulness: 75.72708922311855,
saturation: 87.47645277637828,
},
epsilon = 0.01
);
assert_cam16_to_rgb!(
cam16,
Srgb::from(0x0000ff).into_format(),
epsilon = 0.000001
);
}
#[cfg(feature = "wide")]
#[test]
fn simd() {
let white_srgb = Srgb::from(0xffffff).into_format();
let white_cam16 = Cam16 {
lightness: 99.99955537650459,
chroma: 2.1815254387079435,
hue: 209.49854407518228.into(),
brightness: 197.03120459014184,
colorfulness: 1.9077118865271965,
saturation: 9.839859256901553,
};
let red_srgb = Srgb::from(0xff0000).into_format();
let red_cam16 = Cam16 {
lightness: 46.23623443823762,
chroma: 113.27879472174797,
hue: 27.412485587695937.into(),
brightness: 133.9760614641257,
colorfulness: 99.06063864657237,
saturation: 85.98782392745971,
};
let green_srgb = Srgb::from(0x00ff00).into_format();
let green_cam16 = Cam16 {
lightness: 79.23121430933533,
chroma: 107.77869525794452,
hue: 141.93451307926003.into(),
brightness: 175.38164288466993,
colorfulness: 94.25088262080988,
saturation: 73.30787758114869,
};
let blue_srgb = Srgb::from(0x0000ff).into_format();
let blue_cam16 = Cam16 {
lightness: 25.22701796474445,
chroma: 86.59618504567312,
hue: 282.81848901862566.into(),
brightness: 98.96210767195342,
colorfulness: 75.72708922311855,
saturation: 87.47645277637828,
};
let srgb = Srgb::<wide::f64x4>::from([white_srgb, red_srgb, green_srgb, blue_srgb]);
let xyz = srgb.into_linear().into_color_unclamped();
let mut cam16 = Cam16::from_xyz(xyz, Parameters::TEST_DEFAULTS);
cam16.hue = cam16.hue.into_positive_degrees().into();
assert_relative_eq!(
&<[Cam16<_>; 4]>::from(cam16)[..],
&[white_cam16, red_cam16, green_cam16, blue_cam16][..],
epsilon = 0.1
);
let srgb = Srgb::from_color_unclamped(cam16.into_xyz(Parameters::TEST_DEFAULTS));
assert_relative_eq!(
&<[Srgb<_>; 4]>::from(srgb)[..],
&[white_srgb, red_srgb, green_srgb, blue_srgb][..],
epsilon = 0.00001
);
}
}