From 9973f6b9ebd5e186dbf788e99a01562b4650ad12 Mon Sep 17 00:00:00 2001 From: Erik Hedvall Date: Sat, 20 Apr 2024 15:44:11 +0200 Subject: [PATCH] Add traits for converting to and from CAM16 --- palette/src/cam16.rs | 60 ++++++++- palette/src/cam16/full.rs | 162 +++++++++++++++---------- palette/src/cam16/math/chromaticity.rs | 24 ++-- palette/src/cam16/math/luminance.rs | 18 +-- palette/src/cam16/parameters.rs | 2 +- palette/src/cam16/partial.rs | 106 ++++++---------- palette/src/xyz.rs | 65 +++++++++- 7 files changed, 279 insertions(+), 158 deletions(-) diff --git a/palette/src/cam16.rs b/palette/src/cam16.rs index 7695e181f..606088a3a 100644 --- a/palette/src/cam16.rs +++ b/palette/src/cam16.rs @@ -12,8 +12,66 @@ pub use ucs_jab::UniformCam16UcsJab; pub use ucs_jmh::UniformCam16UcsJmh; mod full; -mod math; +pub(crate) mod math; mod parameters; mod partial; mod ucs_jab; mod ucs_jmh; + +/// A trait for converting into a CAM16 color type from `C` without clamping. +pub trait Cam16FromUnclamped { + /// The number type that's used in `parameters` when converting. + type Scalar; + + /// Converts `color` into `Self`, using the provided parameters. + fn cam16_from_unclamped(color: C, parameters: BakedParameters) -> Self; +} + +/// A trait for converting into a CAM16 color type `C` without clamping. +pub trait IntoCam16Unclamped { + /// The number type that's used in `parameters` when converting. + type Scalar; + + /// Converts `self` into `C`, using the provided parameters. + fn into_cam16_unclamped(self, parameters: BakedParameters) -> C; +} + +impl IntoCam16Unclamped for U +where + T: Cam16FromUnclamped, +{ + type Scalar = T::Scalar; + + fn into_cam16_unclamped(self, parameters: BakedParameters) -> T { + T::cam16_from_unclamped(self, parameters) + } +} + +/// A trait for converting from a CAM16 color type `C` without clamping. +pub trait FromCam16Unclamped { + /// The number type that's used in `parameters` when converting. + type Scalar; + + /// Converts `cam16` into `Self`, using the provided parameters. + fn from_cam16_unclamped(cam16: C, parameters: BakedParameters) -> Self; +} + +/// A trait for converting from a CAM16 color type into `C` without clamping. +pub trait Cam16IntoUnclamped { + /// The number type that's used in `parameters` when converting. + type Scalar; + + /// Converts `self` into `C`, using the provided parameters. + fn cam16_into_unclamped(self, parameters: BakedParameters) -> C; +} + +impl Cam16IntoUnclamped for U +where + T: FromCam16Unclamped, +{ + type Scalar = T::Scalar; + + fn cam16_into_unclamped(self, parameters: BakedParameters) -> T { + T::from_cam16_unclamped(self, parameters) + } +} diff --git a/palette/src/cam16/full.rs b/palette/src/cam16/full.rs index a93077c65..b78caee67 100644 --- a/palette/src/cam16/full.rs +++ b/palette/src/cam16/full.rs @@ -1,14 +1,15 @@ use crate::{ - angle::{RealAngle, SignedAngle}, + angle::RealAngle, bool_mask::{HasBoolMask, LazySelect}, hues::Cam16Hue, - num::{ - Abs, Arithmetics, FromScalar, One, PartialCmp, Powf, Real, Signum, Sqrt, Trigonometry, Zero, - }, + num::{Abs, Arithmetics, FromScalar, PartialCmp, Powf, Real, Signum, Sqrt, Trigonometry, Zero}, Alpha, GetHue, Xyz, }; -use super::{BakedParameters, Cam16Jch, WhitePointParameter}; +use super::{ + BakedParameters, Cam16FromUnclamped, Cam16IntoUnclamped, Cam16Jch, Cam16Jmh, Cam16Jsh, + Cam16Qch, Cam16Qmh, Cam16Qsh, FromCam16Unclamped, IntoCam16Unclamped, WhitePointParameter, +}; /// CIE CAM16 with an alpha component. /// @@ -193,20 +194,11 @@ impl Cam16 { parameters: impl Into>, ) -> Self where - WpParam: WhitePointParameter, - T: Real - + FromScalar - + Arithmetics - + Powf - + Sqrt - + Abs - + Signum - + Trigonometry - + RealAngle - + Clone, - T::Scalar: Clone, + Xyz: IntoCam16Unclamped, + T: FromScalar, + WpParam: WhitePointParameter, { - super::math::xyz_to_cam16(color.with_white_point(), parameters.into().inner) + color.into_cam16_unclamped(parameters.into()) } /// Construct an XYZ color that matches these CIE CAM16 attributes, under @@ -251,25 +243,11 @@ impl Cam16 { parameters: impl Into>, ) -> Xyz where + Self: Cam16IntoUnclamped, Scalar = T::Scalar>, WpParam: WhitePointParameter, - T: Real - + FromScalar - + One - + Zero - + Sqrt - + Powf - + Abs - + Signum - + Arithmetics - + Trigonometry - + RealAngle - + SignedAngle - + PartialCmp - + Clone, - T::Mask: LazySelect + Clone, - T::Scalar: Clone, + T: FromScalar, { - Cam16Jch::from_full(self).into_xyz(parameters) + self.cam16_into_unclamped(parameters.into()) } } @@ -308,18 +286,9 @@ impl Alpha, A> { parameters: impl Into>, ) -> Self where - WpParam: WhitePointParameter, - T: Real - + FromScalar - + Arithmetics - + Powf - + Sqrt - + Abs - + Signum - + Trigonometry - + RealAngle - + Clone, - T::Scalar: Clone, + Xyz: IntoCam16Unclamped, Scalar = T::Scalar>, + T: FromScalar, + WpParam: WhitePointParameter, { let Alpha { color, alpha } = color; @@ -371,23 +340,9 @@ impl Alpha, A> { parameters: impl Into>, ) -> Alpha, A> where + Cam16: Cam16IntoUnclamped, Scalar = T::Scalar>, WpParam: WhitePointParameter, - T: Real - + FromScalar - + One - + Zero - + Sqrt - + Powf - + Abs - + Signum - + Arithmetics - + Trigonometry - + RealAngle - + SignedAngle - + PartialCmp - + Clone, - T::Mask: LazySelect + Clone, - T::Scalar: Clone, + T: FromScalar, { let Alpha { color, alpha } = self; @@ -398,6 +353,87 @@ impl Alpha, A> { } } +impl Cam16FromUnclamped> for Cam16 +where + WpParam: WhitePointParameter, + T: Real + + FromScalar + + Arithmetics + + Powf + + Sqrt + + Abs + + Signum + + Trigonometry + + RealAngle + + Clone, + T::Scalar: Clone, +{ + type Scalar = T::Scalar; + + fn cam16_from_unclamped( + color: Xyz, + parameters: BakedParameters, + ) -> Self { + super::math::xyz_to_cam16(color.with_white_point(), parameters.inner) + } +} + +macro_rules! impl_from_cam16_partial { + ($($name: ident),+) => { + $( + impl Cam16FromUnclamped> for Cam16 + where + WpParam: WhitePointParameter, + T: Real + FromScalar + Zero + Arithmetics + Sqrt + PartialCmp + Clone, + T::Mask: LazySelect + Clone, + T::Scalar: Clone + { + type Scalar = T::Scalar; + + fn cam16_from_unclamped( + cam16: $name, + parameters: crate::cam16::BakedParameters, + ) -> 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 FromCam16Unclamped> for Cam16 + where + Self: Cam16FromUnclamped>, + { + type Scalar = >>::Scalar; + + fn from_cam16_unclamped( + cam16: $name, + parameters: crate::cam16::BakedParameters, + ) -> Self { + Self::cam16_from_unclamped(cam16, parameters) + } + } + )+ + }; +} + +impl_from_cam16_partial!(Cam16Jmh, Cam16Jch, Cam16Jsh, Cam16Qmh, Cam16Qch, Cam16Qsh); + impl GetHue for Cam16 where T: Clone, diff --git a/palette/src/cam16/math/chromaticity.rs b/palette/src/cam16/math/chromaticity.rs index cc380daa8..de392b587 100644 --- a/palette/src/cam16/math/chromaticity.rs +++ b/palette/src/cam16/math/chromaticity.rs @@ -4,7 +4,7 @@ use crate::{ math::{self, DependentParameters}, BakedParameters, }, - num::{Arithmetics, PartialCmp, Real, Sqrt, Zero}, + num::{Arithmetics, FromScalar, PartialCmp, Real, Sqrt, Zero}, }; /// One the apparent chromatic intensity metrics of CAM16. @@ -27,10 +27,10 @@ impl ChromaticityType { pub(crate) fn into_cam16( self, lightness: T, - parameters: BakedParameters, + parameters: BakedParameters, ) -> (T, T, T) where - T: Real + Zero + Arithmetics + Sqrt + PartialCmp + Clone, + T: Real + FromScalar + Zero + Arithmetics + Sqrt + PartialCmp + Clone, T::Mask: LazySelect + Clone, { let DependentParameters { c, a_w, f_l_4, .. } = parameters.inner; @@ -40,15 +40,15 @@ impl ChromaticityType { ChromaticityType::Chroma(chroma) => { let colorfulness = lazy_select! { if is_black.clone() => T::zero(), - else => math::chroma_to_colorfulness(chroma.clone(), f_l_4) + else => math::chroma_to_colorfulness(chroma.clone(), T::from_scalar(f_l_4)) }; let saturation = lazy_select! { if is_black.clone() => T::zero(), else => math::chroma_to_saturation( chroma.clone(), lightness, - c, - a_w, + T::from_scalar(c), + T::from_scalar(a_w), ) }; let chroma = is_black.select(T::zero(), chroma); @@ -58,15 +58,15 @@ impl ChromaticityType { ChromaticityType::Colorfulness(colorfulness) => { let chroma = lazy_select! { if is_black.clone() => T::zero(), - else => math::colorfulness_to_chroma(colorfulness.clone(), f_l_4) + else => math::colorfulness_to_chroma(colorfulness.clone(), T::from_scalar(f_l_4)) }; let saturation = lazy_select! { if is_black.clone() => T::zero(), else => math::chroma_to_saturation( chroma.clone(), lightness, - c, - a_w, + T::from_scalar(c), + T::from_scalar(a_w), ) }; let colorfulness = is_black.select(T::zero(), colorfulness); @@ -79,13 +79,13 @@ impl ChromaticityType { else => math::saturation_to_chroma( saturation.clone(), lightness, - c, - a_w, + T::from_scalar(c), + T::from_scalar(a_w), ) }; let colorfulness = lazy_select! { if is_black.clone() => T::zero(), - else => math::chroma_to_colorfulness(chroma.clone(), f_l_4) + else => math::chroma_to_colorfulness(chroma.clone(), T::from_scalar(f_l_4)) }; let saturation = is_black.select(T::zero(), saturation); diff --git a/palette/src/cam16/math/luminance.rs b/palette/src/cam16/math/luminance.rs index ef93d6cf5..815e6cab6 100644 --- a/palette/src/cam16/math/luminance.rs +++ b/palette/src/cam16/math/luminance.rs @@ -1,7 +1,7 @@ use crate::{ bool_mask::LazySelect, cam16::BakedParameters, - num::{Arithmetics, PartialCmp, Real, Sqrt, Zero}, + num::{Arithmetics, FromScalar, PartialCmp, Real, Sqrt, Zero}, }; /// One the apparent luminance metrics of CAM16. #[derive(Clone, Copy, PartialEq, Eq, Debug)] @@ -16,9 +16,9 @@ pub(crate) enum LuminanceType { } impl LuminanceType { - pub(crate) fn into_cam16(self, parameters: BakedParameters) -> (T, T) + pub(crate) fn into_cam16(self, parameters: BakedParameters) -> (T, T) where - T: Real + Zero + Arithmetics + Sqrt + PartialCmp + Clone, + T: Real + FromScalar + Zero + Arithmetics + Sqrt + PartialCmp + Clone, T::Mask: LazySelect + Clone, { let parameters = parameters.inner; @@ -30,9 +30,9 @@ impl LuminanceType { if is_black => T::zero(), else => crate::cam16::math::lightness_to_brightness( lightness.clone(), - parameters.c, - parameters.a_w, - parameters.f_l_4, + T::from_scalar(parameters.c), + T::from_scalar(parameters.a_w), + T::from_scalar(parameters.f_l_4), ) }; @@ -44,9 +44,9 @@ impl LuminanceType { if is_black => T::zero(), else => crate::cam16::math::brightness_to_lightness( brightness.clone(), - parameters.c, - parameters.a_w, - parameters.f_l_4, + T::from_scalar(parameters.c), + T::from_scalar(parameters.a_w), + T::from_scalar(parameters.f_l_4), ) }; diff --git a/palette/src/cam16/parameters.rs b/palette/src/cam16/parameters.rs index 846424069..9c81cfe9b 100644 --- a/palette/src/cam16/parameters.rs +++ b/palette/src/cam16/parameters.rs @@ -160,7 +160,7 @@ impl Parameters, f64> { /// Derived from [`Parameters`], the `BakedParameters` can help reducing the /// amount of repeated work required for converting multiple colors. pub struct BakedParameters { - pub(super) inner: super::math::DependentParameters, + pub(crate) inner: super::math::DependentParameters, white_point: PhantomData, } diff --git a/palette/src/cam16/partial.rs b/palette/src/cam16/partial.rs index 60efcd627..f13a3ecd9 100644 --- a/palette/src/cam16/partial.rs +++ b/palette/src/cam16/partial.rs @@ -20,12 +20,11 @@ macro_rules! make_partial_cam16 { #[doc = concat!("Partial CIE CAM16, with ", stringify!($luminance), " and ", stringify!($chromaticity), ", and helper types.")] pub mod $module { use crate::{ - angle::{RealAngle, SignedAngle}, - bool_mask::{HasBoolMask, LazySelect}, - cam16::{BakedParameters, Cam16, WhitePointParameter}, + bool_mask::HasBoolMask, + cam16::{BakedParameters, Cam16, WhitePointParameter, Cam16FromUnclamped, IntoCam16Unclamped, Cam16IntoUnclamped}, convert::FromColorUnclamped, hues::{Cam16Hue, Cam16HueIter}, - num::{Abs, Arithmetics, FromScalar, One, PartialCmp, Powf, Real, Signum, Sqrt, Trigonometry, Zero}, + num::{FromScalar, Zero}, Alpha, Xyz, }; @@ -179,11 +178,11 @@ macro_rules! make_partial_cam16 { parameters: impl Into>, ) -> Self where - WpParam: WhitePointParameter, - T: Real + FromScalar + Arithmetics + Powf + Sqrt + Abs + Signum + Trigonometry + RealAngle + Clone, - T::Scalar: Clone, + Xyz: IntoCam16Unclamped, + T: FromScalar, + WpParam: WhitePointParameter, { - crate::cam16::math::xyz_to_cam16(color.with_white_point(), parameters.into().inner).into() + color.into_cam16_unclamped(parameters.into()) } /// Construct an XYZ color from these CIE CAM16 attributes, under the @@ -219,25 +218,11 @@ macro_rules! make_partial_cam16 { parameters: impl Into>, ) -> Xyz where + Self: Cam16IntoUnclamped, Scalar = T::Scalar>, WpParam: WhitePointParameter, - T: Real - + FromScalar - + One - + Zero - + Sqrt - + Powf - + Abs - + Signum - + Arithmetics - + Trigonometry - + RealAngle - + SignedAngle - + PartialCmp - + Clone, - T::Mask: LazySelect + Clone, - T::Scalar: Clone, + T: FromScalar, { - crate::cam16::math::cam16_to_xyz(self.into_dynamic(), parameters.into().inner).with_white_point() + self.cam16_into_unclamped(parameters.into()) } /// Create a partial set of CIE CAM16 attributes. @@ -272,37 +257,18 @@ macro_rules! make_partial_cam16 { /// assert_relative_eq!(cam16, reconstructed, epsilon = 0.0000000000001); /// ``` #[inline] - pub fn into_full(self, parameters: impl Into>) -> Cam16 + pub fn into_full(self, parameters: impl Into>) -> Cam16 where - WpParam: WhitePointParameter, - T: Real + Zero + Arithmetics + Sqrt + PartialCmp + Clone, - T::Mask: LazySelect + Clone, + Self: IntoCam16Unclamped, Scalar = T::Scalar>, + T: FromScalar { - let parameters = parameters.into(); - let ( - luminance, - chromaticity, - hue, - ) = self.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, - } + self.into_cam16_unclamped(parameters.into()) } // Turn the chromaticity and luminance into dynamically decided // attributes, to help conversion to a full set of attributes. #[inline(always)] - fn into_dynamic(self) -> (LuminanceType, ChromaticityType, Cam16Hue) { + pub(crate) fn into_dynamic(self) -> (LuminanceType, ChromaticityType, Cam16Hue) { ( LuminanceType::$luminance_ty(self.$luminance), ChromaticityType::$chromaticity_ty(self.$chromaticity), @@ -383,9 +349,9 @@ macro_rules! make_partial_cam16 { parameters: impl Into>, ) -> Self where - WpParam: WhitePointParameter, - T: Real + FromScalar + Arithmetics + Powf + Sqrt + Abs + Signum + Trigonometry + RealAngle + Clone, - T::Scalar: Clone, + Xyz: IntoCam16Unclamped, Scalar = T::Scalar>, + T: FromScalar, + WpParam: WhitePointParameter, { let Alpha { color, alpha } = color; @@ -428,23 +394,9 @@ macro_rules! make_partial_cam16 { parameters: impl Into>, ) -> Alpha, A> where + $name: Cam16IntoUnclamped, Scalar = T::Scalar>, WpParam: WhitePointParameter, - T: Real - + FromScalar - + One - + Zero - + Sqrt - + Powf - + Abs - + Signum - + Arithmetics - + Trigonometry - + RealAngle - + SignedAngle - + PartialCmp - + Clone, - T::Mask: LazySelect + Clone, - T::Scalar: Clone, + T: FromScalar, { let Alpha { color, alpha } = self; @@ -490,12 +442,12 @@ macro_rules! make_partial_cam16 { #[inline] pub fn into_full( self, - parameters: impl Into>, + parameters: impl Into>, ) -> Alpha, A> where + $name: IntoCam16Unclamped, Scalar = T::Scalar>, WpParam: WhitePointParameter, - T: Real + Zero + Arithmetics + Sqrt + PartialCmp + Clone, - T::Mask: LazySelect + Clone, + T: FromScalar, { let Alpha { color, alpha } = self; @@ -520,6 +472,18 @@ macro_rules! make_partial_cam16 { } } + impl Cam16FromUnclamped> for $name + where + Xyz: IntoCam16Unclamped>, + WpParam: WhitePointParameter, + { + type Scalar = as IntoCam16Unclamped>>::Scalar; + + fn cam16_from_unclamped(color: Xyz, parameters: BakedParameters) -> Self { + color.into_cam16_unclamped(parameters).into() + } + } + impl From> for $name { #[inline] fn from(value: Cam16) -> Self { diff --git a/palette/src/xyz.rs b/palette/src/xyz.rs index 07f9c4c3d..3139aadd0 100644 --- a/palette/src/xyz.rs +++ b/palette/src/xyz.rs @@ -3,12 +3,20 @@ use core::{marker::PhantomData, ops::Mul}; use crate::{ + angle::{RealAngle, SignedAngle}, bool_mask::{HasBoolMask, LazySelect}, + cam16::{ + Cam16, Cam16IntoUnclamped, Cam16Jch, Cam16Jmh, Cam16Jsh, Cam16Qch, Cam16Qmh, Cam16Qsh, + FromCam16Unclamped, WhitePointParameter, + }, convert::{FromColorUnclamped, IntoColorUnclamped}, encoding::IntoLinear, luma::LumaStandard, matrix::{matrix_map, multiply_rgb_to_xyz, multiply_xyz, rgb_to_xyz_matrix}, - num::{Arithmetics, FromScalar, IsValidDivisor, One, PartialCmp, Powi, Real, Recip, Zero}, + num::{ + Abs, Arithmetics, FromScalar, IsValidDivisor, One, PartialCmp, Powf, Powi, Real, Recip, + Signum, Sqrt, Trigonometry, Zero, + }, oklab, rgb::{Primaries, Rgb, RgbSpace, RgbStandard}, stimulus::{Stimulus, StimulusColor}, @@ -318,6 +326,61 @@ where } } +impl FromCam16Unclamped> for Xyz +where + WpParam: WhitePointParameter, + T: FromScalar, + Cam16Jch: Cam16IntoUnclamped, +{ + type Scalar = T::Scalar; + + fn from_cam16_unclamped( + cam16: Cam16, + parameters: crate::cam16::BakedParameters, + ) -> Self { + Cam16Jch::from(cam16).cam16_into_unclamped(parameters) + } +} + +macro_rules! impl_from_cam16_partial { + ($($name: ident),+) => { + $( + impl FromCam16Unclamped> for Xyz + where + WpParam: WhitePointParameter, + T: Real + + FromScalar + + One + + Zero + + Sqrt + + Powf + + Abs + + Signum + + Arithmetics + + Trigonometry + + RealAngle + + SignedAngle + + PartialCmp + + Clone, + T::Mask: LazySelect + Clone, + T::Scalar: Clone, + { + type Scalar = T::Scalar; + + fn from_cam16_unclamped( + cam16: $name, + parameters: crate::cam16::BakedParameters, + ) -> Self { + crate::cam16::math::cam16_to_xyz(cam16.into_dynamic(), parameters.inner) + .with_white_point() + } + } + )+ + }; +} + +impl_from_cam16_partial!(Cam16Jmh, Cam16Jch, Cam16Jsh, Cam16Qmh, Cam16Qch, Cam16Qsh); + impl_tuple_conversion!(Xyz as (T, T, T)); impl_is_within_bounds! {