colr 0.2.0

A general purpose, extensible color type unifying color models and their operations at the type level.
//! Chromatic adaptation between XYZ illuminants.

use crate::Color;
use crate::chromatic_adaptation::ChromaticAdaptation;
use crate::illuminant::Illuminant;
use crate::math::Mat3;
use crate::model::Xyz;

/// Compute the XYZ-to-XYZ adaptation matrix from `src_xyz` to `dst_xyz`.
///
/// Takes XYZ tristimulus values directly rather than xy chromaticities.
/// Use [`Illuminant::WHITE_POINT_XYZ`] to avoid floating-point rounding
/// that occurs when deriving XYZ from xy, which can cause slight drift
/// from specification-defined adaptation matrices.
///
/// For convenience when only xy chromaticities are available, use [`adapt_xy`].
///
/// [`Illuminant::WHITE_POINT_XYZ`]: crate::illuminant::Illuminant::WHITE_POINT_XYZ
pub const fn adapt<A: ChromaticAdaptation>(src_xyz: [f32; 3], dst_xyz: [f32; 3]) -> Mat3 {
    let m = Mat3::from_rows(A::M);
    let m_inv = Mat3::invert(&m);

    let cs = m.apply(src_xyz);
    let cd = m.apply(dst_xyz);

    let s = [cd[0] / cs[0], cd[1] / cs[1], cd[2] / cs[2]];

    let sm = Mat3::from_rows([
        [s[0] * A::M[0][0], s[0] * A::M[0][1], s[0] * A::M[0][2]],
        [s[1] * A::M[1][0], s[1] * A::M[1][1], s[1] * A::M[1][2]],
        [s[2] * A::M[2][0], s[2] * A::M[2][1], s[2] * A::M[2][2]],
    ]);

    Mat3::mul(&m_inv, &sm)
}

impl<W1: Illuminant> Color<[f32; 3], Xyz<W1>> {
    /// Chromatically adapt from illuminant `W1` to `W2` using adaptation method `CA`.
    pub fn adapt<W2, CA>(self) -> Color<[f32; 3], Xyz<W2>>
    where
        W2: Illuminant<Observer = <W1 as Illuminant>::Observer>,
        CA: ChromaticAdaptation,
    {
        const fn matrix<W1: Illuminant, W2: Illuminant, CA: ChromaticAdaptation>() -> Mat3 {
            adapt::<CA>(W1::WHITE_POINT_XYZ, W2::WHITE_POINT_XYZ)
        }
        let [x, y, z] = self.inner();
        Color::new(const { matrix::<W1, W2, CA>() }.apply([x, y, z]))
    }
}

impl<W1: Illuminant> Color<[f32; 4], Xyz<W1>> {
    /// Chromatically adapt from illuminant `W1` to `W2` using adaptation method `CA`.
    pub fn adapt<W2, CA>(self) -> Color<[f32; 4], Xyz<W2>>
    where
        W2: Illuminant<Observer = <W1 as Illuminant>::Observer>,
        CA: ChromaticAdaptation,
    {
        const fn matrix<W1: Illuminant, W2: Illuminant, CA: ChromaticAdaptation>() -> Mat3 {
            adapt::<CA>(W1::WHITE_POINT_XYZ, W2::WHITE_POINT_XYZ)
        }
        let [x, y, z, a] = self.inner();
        let out = const { matrix::<W1, W2, CA>() }.apply([x, y, z]);
        Color::new([out[0], out[1], out[2], a])
    }
}

#[cfg(feature = "glam")]
impl<W1: Illuminant> Color<glam::Vec3A, Xyz<W1>> {
    /// Chromatically adapt from illuminant `W1` to `W2` using adaptation method `CA`.
    pub fn adapt<W2, CA>(self) -> Color<glam::Vec3A, Xyz<W2>>
    where
        W2: Illuminant<Observer = <W1 as Illuminant>::Observer>,
        CA: ChromaticAdaptation,
        Xyz<W2>: BackingStore<glam::Vec3A>,
    {
        const fn matrix<W1: Illuminant, W2: Illuminant, CA: ChromaticAdaptation>() -> Mat3 {
            adapt::<CA>(W1::WHITE_POINT_XYZ, W2::WHITE_POINT_XYZ)
        }
        let [x, y, z] = self.inner().to_array();
        let out = const { matrix::<W1, W2, CA>() }.apply([x, y, z]);
        Color::new(glam::Vec3A::from_array(out))
    }
}

#[cfg(feature = "glam")]
impl<W1: Illuminant> Color<glam::Vec4, Xyz<W1>> {
    /// Chromatically adapt from illuminant `W1` to `W2` using adaptation method `CA`.
    pub fn adapt<W2, CA>(self) -> Color<glam::Vec4, Xyz<W2>>
    where
        W2: Illuminant<Observer = <W1 as Illuminant>::Observer>,
        CA: ChromaticAdaptation,
        Xyz<W2>: BackingStore<glam::Vec4>,
    {
        const fn matrix<W1: Illuminant, W2: Illuminant, CA: ChromaticAdaptation>() -> Mat3 {
            adapt::<CA>(W1::WHITE_POINT_XYZ, W2::WHITE_POINT_XYZ)
        }
        let [x, y, z, a] = self.inner().to_array();
        let out = const { matrix::<W1, W2, CA>() }.apply([x, y, z]);
        Color::new(glam::Vec4::new(out[0], out[1], out[2], a))
    }
}