colr 0.1.0

Type-safe, zero-cost color science library with compile-time color space transforms
Documentation
//! Spectral color spaces.
//!
//! Extends the core ColorSpace vocabulary with physical quantity markers
//! that distinguish reflectance, radiance, transmittance, and bispectral
//! (fluorescent) encodings. Mutual exclusivity between kinds is enforced
//! structurally via SpectralSpace::Kind.
//!
//! Storage compatibility for spectral spaces is declared by concrete
//! implementations. Spectral storage is [f32; BANDS] for a specific BANDS
//! value -- no blanket Asserts impl is possible across all band counts.
//! Concrete spectral spaces implement Asserts<[f32; BANDS]> directly.

use crate::ColorSpace;

/// Identifies the physical quantity encoded by a spectral color space.
///
/// Implement with one of `IsReflectance`, `IsRadiance`, `IsTransmittance`,
/// or `IsBispectral`. Mutual exclusivity is enforced structurally; a space
/// can only declare one `Kind`.
pub trait SpectralKind: 'static {}

/// Spectral kind marker for surface reflectance rho(lambda) in [0, 1].
#[derive(Debug, Clone, Copy)]
pub struct IsReflectance;

/// Spectral kind marker for radiance Phi(lambda), unbounded above.
#[derive(Debug, Clone, Copy)]
pub struct IsRadiance;

/// Spectral kind marker for transmittance tau(lambda) in [0, 1].
#[derive(Debug, Clone, Copy)]
pub struct IsTransmittance;

/// Spectral kind marker for bispectral (fluorescent) reflectance.
#[derive(Debug, Clone, Copy)]
pub struct IsBispectral;

impl SpectralKind for IsReflectance {}
impl SpectralKind for IsRadiance {}
impl SpectralKind for IsTransmittance {}
impl SpectralKind for IsBispectral {}

/// A spectral color space with an associated physical quantity kind.
///
/// Mutual exclusivity between kinds is enforced structurally; a space can
/// only declare one `Kind`. The ergonomic marker traits (`Reflectance`,
/// `Radiance`, `Transmittance`, `Bispectral`) are derived via blanket impls.
pub trait SpectralSpace: ColorSpace {
    /// The physical quantity this space encodes.
    type Kind: SpectralKind;
}

/// Marks a spectral ColorSpace as encoding surface reflectance rho(lambda)
/// in [0, 1]. Not interchangeable with Radiance or Transmittance even when
/// carried by the same storage type.
pub trait Reflectance: SpectralSpace<Kind = IsReflectance> {}
impl<Sp> Reflectance for Sp where Sp: SpectralSpace<Kind = IsReflectance> {}

/// Marks a spectral ColorSpace as encoding radiance Phi(lambda), unbounded
/// above. Used for emissive sources such as illuminants and displays.
pub trait Radiance: SpectralSpace<Kind = IsRadiance> {}
impl<Sp> Radiance for Sp where Sp: SpectralSpace<Kind = IsRadiance> {}

/// Marks a spectral ColorSpace as encoding transmittance tau(lambda) in
/// [0, 1]. Used for filters, colored glass, and dye layers.
pub trait Transmittance: SpectralSpace<Kind = IsTransmittance> {}
impl<Sp> Transmittance for Sp where Sp: SpectralSpace<Kind = IsTransmittance> {}

/// Marks a spectral ColorSpace as encoding bispectral (fluorescent)
/// reflectance. The representation is a Donaldson matrix M of shape N x N
/// where `M[i][j]` is the fraction absorbed at band `i` and re-emitted at
/// band `j`. Converting to XYZ requires `M * cmf` rather than the dot product
/// `cmf . rho` used for Reflectance.
pub trait Bispectral: SpectralSpace<Kind = IsBispectral> {}
impl<Sp> Bispectral for Sp where Sp: SpectralSpace<Kind = IsBispectral> {}

/// A fixed spectral sampling grid with CIE 1931 color matching functions.
///
/// BANDS is a const generic parameter making all CMF array lengths type-safe.
/// Two crates defining spectral spaces on different grids cannot accidentally
/// interoperate -- the grid type is part of the conversion contract.
///
/// Conversion to CIE XYZ lives in a ConvertFrom impl. This trait carries
/// only the sampling data.
pub trait WavelengthGrid<const BANDS: usize>: 'static {
    /// Start wavelength in nanometers.
    const START_NM: f32;

    /// Wavelength step in nanometers.
    const STEP_NM: f32;

    /// CIE 1931 2-degree observer x-bar(lambda) at each sample wavelength.
    const CMF_X: &'static [f32; BANDS];

    /// CIE 1931 2-degree observer y-bar(lambda) at each sample wavelength.
    const CMF_Y: &'static [f32; BANDS];

    /// CIE 1931 2-degree observer z-bar(lambda) at each sample wavelength.
    const CMF_Z: &'static [f32; BANDS];
}