colr 0.1.1

A general purpose, extensible color type unifying storage, channel layouts, and color spaces at the type level.
Documentation
//! CIE 1931 XYZ color space parameterized by illuminant.
//!
//! `Xyz<W>` has no layout parameter; there is only one meaningful channel
//! ordering for XYZ (X, Y, Z). Storage types assert directly against `Xyz<W>`.

use core::marker::PhantomData;

use crate::adaptation::{Bradford, adapt};
use crate::illuminant::Illuminant;
use crate::math::Mat3;
use crate::{Asserts, Color, ColorSpace, Transform};

/// CIE 1931 XYZ tristimulus space normalized to Y = 1 under illuminant W.
///
/// LUMINANCE_WEIGHTS is Some([0.0, 1.0, 0.0]) because the Y channel is
/// exactly CIE relative luminance by definition.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Xyz<W: Illuminant>(PhantomData<W>);

impl<W: Illuminant> ColorSpace for Xyz<W> {
    const CHANNELS: usize = 3;
    const LUMINANCE_WEIGHTS: Option<[f32; 3]> = Some([0.0, 1.0, 0.0]);
}

impl<W: Illuminant> Asserts<[f32; 3]> for Xyz<W> {}
impl<W: Illuminant> Asserts<[f32; 4]> for Xyz<W> {}

#[cfg(feature = "glam")]
impl<W: Illuminant> Asserts<glam::Vec4> for Xyz<W> {}
#[cfg(feature = "glam")]
impl<W: Illuminant> Asserts<glam::Vec3A> for Xyz<W> {}

// Direct XYZ-to-XYZ chromatic adaptation via Bradford.
// Bradford is mandated by ICC for all standard illuminant pairs.
impl<W1, W2> Transform<Color<[f32; 3], Xyz<W1>>> for Color<[f32; 3], Xyz<W2>>
where
    W1: Illuminant,
    W2: Illuminant,
{
    fn transform_from(src: Color<[f32; 3], Xyz<W1>>, _: &()) -> Self {
        const fn matrix<W1: Illuminant, W2: Illuminant>() -> Mat3 {
            adapt::<Bradford>(W1::WHITE_POINT_XYZ, W2::WHITE_POINT_XYZ)
        }
        let x = src.inner();
        let out = const { matrix::<W1, W2>() }.apply([x[0], x[1], x[2]]);
        Color::new_unchecked([out[0], out[1], out[2]])
    }
}

impl<W1, W2> Transform<Color<[f32; 4], Xyz<W1>>> for Color<[f32; 4], Xyz<W2>>
where
    W1: Illuminant,
    W2: Illuminant,
{
    fn transform_from(src: Color<[f32; 4], Xyz<W1>>, _: &()) -> Self {
        const fn matrix<W1: Illuminant, W2: Illuminant>() -> Mat3 {
            adapt::<Bradford>(W1::WHITE_POINT_XYZ, W2::WHITE_POINT_XYZ)
        }
        let x = src.inner();
        let out = const { matrix::<W1, W2>() }.apply([x[0], x[1], x[2]]);
        Color::new_unchecked([out[0], out[1], out[2], x[3]])
    }
}

// Lossless resize between storage sizes. Alpha set to 1.0 on expand.
impl<W: Illuminant> From<Color<[f32; 3], Xyz<W>>> for Color<[f32; 4], Xyz<W>> {
    fn from(src: Color<[f32; 3], Xyz<W>>) -> Self {
        let [x, y, z] = src.inner();
        Color::new_unchecked([x, y, z, 1.0])
    }
}

impl<W: Illuminant> From<Color<[f32; 4], Xyz<W>>> for Color<[f32; 3], Xyz<W>> {
    fn from(src: Color<[f32; 4], Xyz<W>>) -> Self {
        let [x, y, z, _] = src.inner();
        Color::new_unchecked([x, y, z])
    }
}

/// CIE XYZ under D65.
pub type XyzD65 = Xyz<crate::illuminant::D65>;

/// CIE XYZ under D50. Used by the ICC profile connection space.
pub type XyzD50 = Xyz<crate::illuminant::D50>;

/// CIE XYZ under the ACES white point.
pub type XyzAces = Xyz<crate::illuminant::AcesWhitePoint>;