#[cfg(feature = "munsell")]
mod munsell;
#[cfg(feature = "munsell")]
pub use munsell::*;
#[cfg(feature = "cfi")]
mod ces_data;
#[cfg(feature = "cfi")]
pub use ces_data::{CES, N_CFI};
#[cfg(feature = "cri")]
mod tcs;
#[cfg(feature = "cri")]
pub use tcs::{N_TCS, TCS};
use std::{
borrow::Cow,
ops::{Add, AddAssign, Mul},
};
use approx::AbsDiffEq;
use nalgebra::SVector;
use crate::{
error::Error,
lab::CieLab,
math::Gaussian,
observer::Observer,
spectrum::wavelength,
spectrum::{wavelengths, Spectrum, NS, SPECTRUM_WAVELENGTH_RANGE},
traits::{Filter, Light},
};
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Colorant(pub(crate) Spectrum);
impl Colorant {
pub fn new(spectrum: Spectrum) -> Result<Self, Error> {
if spectrum.as_array().iter().any(|v| !(0.0..=1.0).contains(v)) {
Err(Error::OutOfRange {
name: "Colorant Spectral Value".into(),
low: 0.0,
high: 1.0,
})
} else {
Ok(Self(spectrum))
}
}
pub fn gray(gval: f64) -> Self {
Self(Spectrum(SVector::<f64, NS>::repeat(gval.clamp(0.0, 1.0))))
}
pub fn white() -> Self {
Self::gray(1.0)
}
pub fn black() -> Self {
Self::gray(0.0)
}
pub fn top_hat(center: f64, width: f64) -> Self {
let [center_m, width_m] = wavelengths([center, width]);
let left = center_m - width_m / 2.0;
let right = center_m + width_m / 2.0;
let data = SVector::<f64, NS>::from_fn(|i, _j| {
let w = wavelength(i + SPECTRUM_WAVELENGTH_RANGE.start());
if w < left - f64::EPSILON || w > right + f64::EPSILON {
0.0
} else {
1.0
}
});
Self(Spectrum(data))
}
pub fn gaussian(center: f64, sigma: f64) -> Self {
let [center_m, width_m] = wavelengths([center, sigma]);
let gauss = Gaussian::new(center_m, width_m);
let data = SVector::<f64, NS>::from_fn(|i, _j| {
gauss.peak_one(wavelength(i + SPECTRUM_WAVELENGTH_RANGE.start()))
});
Self(Spectrum(data))
}
pub fn cielab(&self, illuminant_opt: Option<&dyn Light>, obs_opt: Option<Observer>) -> CieLab {
let illuminant = illuminant_opt.unwrap_or(&crate::illuminant::D65);
let obs = obs_opt.unwrap_or_default();
let rxyz = obs.rel_xyz(illuminant, self);
CieLab::from_rxyz(rxyz)
}
}
#[test]
fn test_colorant_cielab() {
use approx::assert_abs_diff_eq;
let colorant = Colorant::white();
let [l, a, b] = colorant.cielab(None, None).to_array();
assert_abs_diff_eq!(l, 100.0, epsilon = 1E-4); assert_abs_diff_eq!(a, 0.0, epsilon = 1E-4); assert_abs_diff_eq!(b, 0.0, epsilon = 1E-4); }
impl TryFrom<Spectrum> for Colorant {
type Error = Error;
fn try_from(spectrum: Spectrum) -> Result<Self, Self::Error> {
Self::new(spectrum)
}
}
impl<F> From<F> for Colorant
where
F: Fn(f64) -> f64,
{
fn from(f: F) -> Self {
let data = SVector::from_fn(|i, _j| {
let x = i as f64 / (NS - 1) as f64;
f(x).clamp(0.0, 1.0)
});
Colorant(Spectrum(data))
}
}
impl Filter for Colorant {
fn spectrum(&self) -> Cow<'_, Spectrum> {
Cow::Borrowed(&self.0)
}
}
impl Add for Colorant {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
let mut spectrum = self.0 + rhs.0;
spectrum.clamp(0.0, 1.0);
Colorant(spectrum)
}
}
impl Mul<f64> for Colorant {
type Output = Self;
fn mul(self, rhs: f64) -> Self::Output {
let mut spectrum = self.0 * rhs;
spectrum.clamp(0.0, 1.0);
Self(spectrum)
}
}
impl Mul<Colorant> for f64 {
type Output = Colorant;
fn mul(self, rhs: Colorant) -> Self::Output {
rhs.mul(self)
}
}
impl Mul<Colorant> for Colorant {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
Self(self.0 * rhs.0) }
}
impl Mul<&Colorant> for &Colorant {
type Output = Colorant;
fn mul(self, rhs: &Colorant) -> Self::Output {
Colorant(self.0 * rhs.0) }
}
impl AddAssign<&Self> for Colorant {
fn add_assign(&mut self, rhs: &Self) {
self.0 += rhs.0;
self.0.clamp(0.0, 1.0);
}
}
impl AbsDiffEq for Colorant {
type Epsilon = f64;
fn default_epsilon() -> Self::Epsilon {
f64::default_epsilon()
}
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
self.spectrum().abs_diff_eq(&other.spectrum(), epsilon)
}
}