mod data;
use std::{collections::BTreeMap, sync::LazyLock};
use crate::{
cam::{CieCam16, ViewConditions},
colorant::munsell::data::{MUNSELL_MATT_DATA, MUNSELL_MATT_KEYS},
error::Error,
illuminant::{Illuminant, D65},
observer,
spectrum::{Spectrum, SPECTRUM_WAVELENGTH_RANGE},
traits::Filter,
};
pub(crate) const MATT_N: usize = 81;
pub(crate) const MATT_M: usize = 1269;
#[cfg_attr(target_arch = "wasm32", wasm_bindgen::prelude::wasm_bindgen)]
pub struct Munsell(String, Spectrum);
impl Munsell {
pub fn new(i: usize) -> Self {
let key = MUNSELL_MATT_KEYS[i];
let data: [f64; MATT_N] = MUNSELL_MATT_DATA[i * MATT_N..(i + 1) * MATT_N]
.iter()
.map(|v| *v as f64)
.collect::<Vec<f64>>()
.try_into()
.unwrap();
let spectrum = Spectrum::sprague_interpolate(
[
*SPECTRUM_WAVELENGTH_RANGE.start() as f64,
*SPECTRUM_WAVELENGTH_RANGE.end() as f64,
],
&data,
)
.unwrap();
Munsell(key.to_string(), spectrum)
}
pub fn try_new(key: impl AsRef<str>) -> Result<Munsell, Error> {
if let Some(&i) = MM_KEY_MAP.get(key.as_ref()) {
Ok(Self::new(i))
} else {
Err(Error::SpectrumNotFound(key.as_ref().to_string()))
}
}
pub fn key(&self) -> &str {
&self.0
}
pub fn spectrum(&self) -> &Spectrum {
&self.1
}
}
pub struct MunsellCollection;
impl MunsellCollection {
pub fn len() -> usize {
MATT_M
}
pub fn match_ciecam16(
colorant: &dyn Filter,
opt_illuminant: Option<&Illuminant>,
opt_vc: Option<ViewConditions>,
opt_observer: Option<observer::Observer>,
) -> Result<(String, f64), Error> {
let illuminant = opt_illuminant.unwrap_or(&D65);
let vc = opt_vc.unwrap_or_default();
let observer = opt_observer.unwrap_or_default();
let rxyz = observer.rel_xyz(illuminant, colorant);
let tgt_cam = CieCam16::from_xyz(rxyz, vc);
let mut best_key = String::new();
let mut best_delta_e = f64::MAX;
for mm in MunsellCollection.into_iter() {
let xyz_mm = observer.rel_xyz(illuminant, &mm);
let cam_mm = CieCam16::from_xyz(xyz_mm, vc);
let delta_e = tgt_cam.de_ucs(&cam_mm)?;
if delta_e < best_delta_e {
best_delta_e = delta_e;
best_key = mm.key().to_string();
}
}
Ok((best_key, best_delta_e))
}
}
pub struct MunsellIterator(usize);
impl Iterator for MunsellIterator {
type Item = Munsell;
fn next(&mut self) -> Option<Self::Item> {
if self.0 < MATT_M {
let mm = Munsell::new(self.0);
self.0 += 1;
Some(mm)
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(MATT_M, Some(MATT_M))
}
}
impl ExactSizeIterator for MunsellIterator {}
impl IntoIterator for MunsellCollection {
type Item = Munsell;
type IntoIter = MunsellIterator;
fn into_iter(self) -> Self::IntoIter {
MunsellIterator(0)
}
}
impl Filter for Munsell {
fn spectrum(&self) -> std::borrow::Cow<'_, Spectrum> {
(&self.1).into()
}
}
pub static MM_KEY_MAP: LazyLock<BTreeMap<&str, usize>> = LazyLock::new(|| {
let mut map = BTreeMap::new();
MUNSELL_MATT_KEYS
.iter()
.enumerate()
.for_each(|(value, &key)| {
map.insert(key, value);
});
map
});
#[cfg(test)]
mod test_munsell {
use super::MunsellCollection;
use crate::observer::Observer::Cie1931;
#[test]
fn test_iter() {
MunsellCollection
.into_iter()
.enumerate()
.for_each(|(i, m)| {
let lab_d65 = Cie1931.lab_d65(&m);
let key = m.0;
println!("{i} {key} {lab_d65:?}");
});
let mm = crate::colorant::MunsellCollection
.into_iter()
.last()
.unwrap();
assert_eq!(mm.0, "10RP4/12".to_string());
}
#[test]
fn test_match_ciecam16() {
let colorant = crate::colorant::Munsell::try_new("10RP4/12").unwrap();
let (key, delta_e) =
MunsellCollection::match_ciecam16(&colorant, None, None, Some(Cie1931)).unwrap();
assert_eq!(key, "10RP4/12");
approx::assert_abs_diff_eq!(delta_e, 0.0, epsilon = 1e-6);
}
#[test]
#[cfg(feature = "cri")]
fn test_match_r9() {
let r9 = &crate::colorant::tcs::TCS[8];
let (key, delta_e) = MunsellCollection::match_ciecam16(r9, None, None, None).unwrap();
assert_eq!(key, "5R4/14");
approx::assert_abs_diff_eq!(delta_e, 2.8, epsilon = 5e-2);
}
}