use core::ops::Div;
use crate::{
convert::{FromColorUnclamped, IntoColorUnclamped, Matrix3},
lms::{
self,
matrix::{Bradford, LmsToXyz, WithLmsMatrix, XyzToLms},
Lms,
},
matrix::{multiply_3x3, multiply_3x3_and_vec3, Mat3},
num::{Arithmetics, Real, Zero},
white_point::{Any, WhitePoint},
xyz::meta::HasXyzMeta,
Xyz,
};
pub fn adaptation_matrix<T, I, O, M>(
input_wp: Option<Xyz<I, T>>,
output_wp: Option<Xyz<O, T>>,
) -> Matrix3<Xyz<I, T>, Xyz<O, T>>
where
T: Zero + Arithmetics + Clone,
I: WhitePoint<T> + HasXyzMeta<XyzMeta = I>,
O: WhitePoint<T> + HasXyzMeta<XyzMeta = O>,
M: XyzToLms<T> + LmsToXyz<T>,
Xyz<I, T>: IntoColorUnclamped<Lms<WithLmsMatrix<I, M>, T>>,
Xyz<O, T>: IntoColorUnclamped<Lms<WithLmsMatrix<O, M>, T>>,
{
let input_to_lms = Lms::<WithLmsMatrix<I, M>, T>::matrix_from_xyz();
let lms_to_output = Xyz::<O, T>::matrix_from_lms::<WithLmsMatrix<O, M>>();
let input_wp = input_wp
.unwrap_or_else(|| I::get_xyz().with_white_point())
.normalize()
.into_color_unclamped();
let output_wp = output_wp
.unwrap_or_else(|| O::get_xyz().with_white_point())
.normalize()
.into_color_unclamped();
input_to_lms
.then(diagonal_matrix(input_wp, output_wp))
.then(lms_to_output)
}
#[inline]
pub fn diagonal_matrix<T, I, O>(
input_wp: Lms<I, T>,
output_wp: Lms<O, T>,
) -> Matrix3<Lms<I, T>, Lms<O, T>>
where
T: Zero + Div<Output = T>,
{
let gain = output_wp / input_wp.with_meta();
#[rustfmt::skip]
let matrix = [
gain.long, T::zero(), T::zero(),
T::zero(), gain.medium, T::zero(),
T::zero(), T::zero(), gain.short,
];
Matrix3::from_array(matrix)
}
pub trait AdaptFromUnclamped<T>: Sized {
type Scalar;
#[must_use]
#[inline]
fn adapt_from_unclamped(input: T) -> Self
where
Bradford: LmsToXyz<Self::Scalar> + XyzToLms<Self::Scalar>,
{
Self::adapt_from_unclamped_with::<Bradford>(input)
}
#[must_use]
fn adapt_from_unclamped_with<M>(input: T) -> Self
where
M: LmsToXyz<Self::Scalar> + XyzToLms<Self::Scalar>;
}
pub trait AdaptIntoUnclamped<T>: Sized {
type Scalar;
#[must_use]
#[inline]
fn adapt_into_unclamped(self) -> T
where
Bradford: LmsToXyz<Self::Scalar> + XyzToLms<Self::Scalar>,
{
self.adapt_into_unclamped_with::<Bradford>()
}
#[must_use]
fn adapt_into_unclamped_with<M>(self) -> T
where
M: LmsToXyz<Self::Scalar> + XyzToLms<Self::Scalar>;
}
impl<T, C> AdaptIntoUnclamped<T> for C
where
T: AdaptFromUnclamped<C>,
{
type Scalar = T::Scalar;
#[inline]
fn adapt_into_unclamped_with<M>(self) -> T
where
M: LmsToXyz<Self::Scalar> + XyzToLms<Self::Scalar>,
{
T::adapt_from_unclamped_with::<M>(self)
}
}
#[deprecated(
since = "0.7.7",
note = "use the options from `palette::lms::matrix` or a custom matrix"
)]
pub enum Method {
Bradford,
VonKries,
XyzScaling,
}
#[deprecated(
since = "0.7.7",
note = "use the options from `palette::lms::matrix` or a custom matrix"
)]
pub struct ConeResponseMatrices<T> {
pub ma: Mat3<T>,
pub inv_ma: Mat3<T>,
}
#[deprecated(
since = "0.7.7",
note = "use the options from `palette::lms::matrix` or a custom matrix"
)]
#[allow(deprecated)]
pub trait TransformMatrix<T>
where
T: Zero + Arithmetics + Clone,
{
#[must_use]
fn get_cone_response(&self) -> ConeResponseMatrices<T>;
#[must_use]
fn generate_transform_matrix(
&self,
source_wp: Xyz<Any, T>,
destination_wp: Xyz<Any, T>,
) -> Mat3<T> {
let adapt = self.get_cone_response();
let resp_src: Lms<Any, T> =
multiply_3x3_and_vec3(adapt.ma.clone(), source_wp.into()).into();
let resp_dst: Lms<Any, T> =
multiply_3x3_and_vec3(adapt.ma.clone(), destination_wp.into()).into();
let resp = diagonal_matrix(resp_src, resp_dst).into_array();
let tmp = multiply_3x3(resp, adapt.ma);
multiply_3x3(adapt.inv_ma, tmp)
}
}
#[allow(deprecated)]
impl<T> TransformMatrix<T> for Method
where
T: Real + Zero + Arithmetics + Clone,
{
#[rustfmt::skip]
#[inline]
fn get_cone_response(&self) -> ConeResponseMatrices<T> {
match *self {
Method::Bradford => {
ConeResponseMatrices::<T> {
ma: lms::matrix::Bradford::xyz_to_lms_matrix(),
inv_ma: lms::matrix::Bradford::lms_to_xyz_matrix(),
}
}
Method::VonKries => {
ConeResponseMatrices::<T> {
ma: lms::matrix::VonKries::xyz_to_lms_matrix(),
inv_ma: lms::matrix::VonKries::lms_to_xyz_matrix(),
}
}
Method::XyzScaling => {
ConeResponseMatrices::<T> {
ma: lms::matrix::UnitMatrix::xyz_to_lms_matrix(),
inv_ma: lms::matrix::UnitMatrix::lms_to_xyz_matrix(),
}
}
}
}
}
#[deprecated(
since = "0.7.7",
note = "replaced by `palette::chromatic_adaptation::AdaptFromUnclamped`"
)]
#[allow(deprecated)]
pub trait AdaptFrom<S, Swp, Dwp, T>: Sized
where
T: Real + Zero + Arithmetics + Clone,
Swp: WhitePoint<T>,
Dwp: WhitePoint<T>,
{
#[must_use]
#[inline]
fn adapt_from(color: S) -> Self {
Self::adapt_from_using(color, Method::Bradford)
}
#[must_use]
fn adapt_from_using<M: TransformMatrix<T>>(color: S, method: M) -> Self;
}
#[allow(deprecated)]
impl<S, D, Swp, Dwp, T> AdaptFrom<S, Swp, Dwp, T> for D
where
T: Real + Zero + Arithmetics + Clone,
Swp: WhitePoint<T>,
Dwp: WhitePoint<T>,
S: IntoColorUnclamped<Xyz<Swp, T>>,
D: FromColorUnclamped<Xyz<Dwp, T>>,
{
#[inline]
fn adapt_from_using<M: TransformMatrix<T>>(color: S, method: M) -> D {
let src_xyz: Xyz<Swp, T> = color.into_color_unclamped();
let transform_matrix = method.generate_transform_matrix(Swp::get_xyz(), Dwp::get_xyz());
let dst_xyz: Xyz<Dwp, T> = multiply_3x3_and_vec3(transform_matrix, src_xyz.into()).into();
D::from_color_unclamped(dst_xyz)
}
}
#[deprecated(
since = "0.7.7",
note = "replaced by `palette::chromatic_adaptation::AdaptIntoUnclamped`"
)]
#[allow(deprecated)]
pub trait AdaptInto<D, Swp, Dwp, T>: Sized
where
T: Real + Zero + Arithmetics + Clone,
Swp: WhitePoint<T>,
Dwp: WhitePoint<T>,
{
#[must_use]
#[inline]
fn adapt_into(self) -> D {
self.adapt_into_using(Method::Bradford)
}
#[must_use]
fn adapt_into_using<M: TransformMatrix<T>>(self, method: M) -> D;
}
#[allow(deprecated)]
impl<S, D, Swp, Dwp, T> AdaptInto<D, Swp, Dwp, T> for S
where
T: Real + Zero + Arithmetics + Clone,
Swp: WhitePoint<T>,
Dwp: WhitePoint<T>,
D: AdaptFrom<S, Swp, Dwp, T>,
{
#[inline]
fn adapt_into_using<M: TransformMatrix<T>>(self, method: M) -> D {
D::adapt_from_using(self, method)
}
}
#[cfg(feature = "approx")]
#[cfg(test)]
mod test {
#![allow(deprecated)]
use super::{AdaptFrom, AdaptInto, Method, TransformMatrix};
use crate::{
encoding::{Linear, Srgb},
Xyz,
};
use crate::{
rgb::Rgb,
white_point::{WhitePoint, A, C, D50, D65},
};
#[test]
fn d65_to_d50_matrix_xyz_scaling() {
let expected = [
1.0144665, 0.0000000, 0.0000000, 0.0000000, 1.0000000, 0.0000000, 0.0000000, 0.0000000,
0.7578869,
];
let xyz_scaling = Method::XyzScaling;
let computed = xyz_scaling.generate_transform_matrix(D65::get_xyz(), D50::get_xyz());
for (e, c) in expected.iter().zip(computed.iter()) {
assert_relative_eq!(e, c, epsilon = 0.0001)
}
}
#[test]
fn d65_to_d50_matrix_von_kries() {
let expected = [
1.0160803, 0.0552297, -0.0521326, 0.0060666, 0.9955661, -0.0012235, 0.0000000,
0.0000000, 0.7578869,
];
let von_kries = Method::VonKries;
let computed = von_kries.generate_transform_matrix(D65::get_xyz(), D50::get_xyz());
for (e, c) in expected.iter().zip(computed.iter()) {
assert_relative_eq!(e, c, epsilon = 0.0001)
}
}
#[test]
fn d65_to_d50_matrix_bradford() {
let expected = [
1.0478112, 0.0228866, -0.0501270, 0.0295424, 0.9904844, -0.0170491, -0.0092345,
0.0150436, 0.7521316,
];
let bradford = Method::Bradford;
let computed = bradford.generate_transform_matrix(D65::get_xyz(), D50::get_xyz());
for (e, c) in expected.iter().zip(computed.iter()) {
assert_relative_eq!(e, c, epsilon = 0.0001)
}
}
#[test]
fn chromatic_adaptation_from_a_to_c() {
let input_a = Xyz::<A, f32>::new(0.315756, 0.162732, 0.015905);
let expected_bradford = Xyz::<C, f32>::new(0.257963, 0.139776, 0.058825);
let expected_vonkries = Xyz::<C, f32>::new(0.268446, 0.159139, 0.052843);
let expected_xyz_scaling = Xyz::<C, f32>::new(0.281868, 0.162732, 0.052844);
let computed_bradford: Xyz<C, f32> = Xyz::adapt_from(input_a);
assert_relative_eq!(expected_bradford, computed_bradford, epsilon = 0.0001);
let computed_vonkries: Xyz<C, f32> = Xyz::adapt_from_using(input_a, Method::VonKries);
assert_relative_eq!(expected_vonkries, computed_vonkries, epsilon = 0.0001);
let computed_xyz_scaling: Xyz<C, _> = Xyz::adapt_from_using(input_a, Method::XyzScaling);
assert_relative_eq!(expected_xyz_scaling, computed_xyz_scaling, epsilon = 0.0001);
}
#[test]
fn chromatic_adaptation_into_a_to_c() {
let input_a = Xyz::<A, f32>::new(0.315756, 0.162732, 0.015905);
let expected_bradford = Xyz::<C, f32>::new(0.257963, 0.139776, 0.058825);
let expected_vonkries = Xyz::<C, f32>::new(0.268446, 0.159139, 0.052843);
let expected_xyz_scaling = Xyz::<C, f32>::new(0.281868, 0.162732, 0.052844);
let computed_bradford: Xyz<C, f32> = input_a.adapt_into();
assert_relative_eq!(expected_bradford, computed_bradford, epsilon = 0.0001);
let computed_vonkries: Xyz<C, f32> = input_a.adapt_into_using(Method::VonKries);
assert_relative_eq!(expected_vonkries, computed_vonkries, epsilon = 0.0001);
let computed_xyz_scaling: Xyz<C, _> = input_a.adapt_into_using(Method::XyzScaling);
assert_relative_eq!(expected_xyz_scaling, computed_xyz_scaling, epsilon = 0.0001);
}
#[test]
fn d65_to_d50() {
let input: Rgb<Linear<Srgb>> = Rgb::new(1.0, 1.0, 1.0);
let expected: Rgb<Linear<(Srgb, D50)>> = Rgb::new(1.0, 1.0, 1.0);
let computed: Rgb<Linear<(Srgb, D50)>> = input.adapt_into();
assert_relative_eq!(expected, computed, epsilon = 0.000001);
}
}