use approx::AbsDiffEq;
use nalgebra::Vector3;
use crate::lab::CieLab;
use super::XYZ;
#[cfg(feature = "gamut-tables")]
mod gamut;
#[cfg(feature = "gamut-tables")]
pub use gamut::RelXYZGamut;
#[cfg_attr(target_arch = "wasm32", wasm_bindgen::prelude::wasm_bindgen)]
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub struct RelXYZ {
xyz: Vector3<f64>,
white_point: XYZ,
}
impl RelXYZ {
pub fn new(xyz: [f64; 3], white_point: XYZ) -> Self {
RelXYZ {
xyz: xyz.into(),
white_point,
}
}
pub fn from_vec(xyz: Vector3<f64>, white_point: XYZ) -> Self {
RelXYZ { xyz, white_point }
}
pub fn from_xyz(xyz: XYZ, white_point: XYZ) -> Result<Self, crate::Error> {
if xyz.observer != white_point.observer {
Err(crate::Error::RequireSameObserver)
} else {
Ok(RelXYZ {
xyz: xyz.xyz,
white_point,
})
}
}
pub fn with_d65(xyz: XYZ) -> Self {
let white_point = xyz.observer.xyz_d65();
RelXYZ {
xyz: xyz.xyz,
white_point,
}
}
pub fn with_d50(xyz: XYZ) -> Self {
let white_point = xyz.observer.xyz_d50();
RelXYZ {
xyz: xyz.xyz,
white_point,
}
}
pub fn xyz(&self) -> XYZ {
XYZ::from_vec(self.xyz, self.white_point.observer)
}
pub fn white_point(&self) -> XYZ {
self.white_point
}
pub fn to_arrays(&self) -> [[f64; 3]; 2] {
[self.xyz.into(), self.white_point.xyz.into()]
}
pub fn set_illuminance(mut self, illuminance: f64) -> Self {
let yn = self.white_point.xyz.y;
if yn > f64::EPSILON && illuminance > f64::EPSILON {
let scale = illuminance / yn;
self.xyz *= scale;
self.white_point.xyz *= scale;
self
} else {
panic!("Illuminance and Y of white point must be greater than zero");
}
}
pub fn is_valid(&self) -> bool {
if !self.xyz().is_valid() {
return false;
}
let lab = CieLab::from_rxyz(*self);
let xyz_back = lab.rxyz();
self.abs_diff_eq(&xyz_back, 1E-12)
}
}
impl AbsDiffEq for RelXYZ {
type Epsilon = f64;
fn default_epsilon() -> Self::Epsilon {
f64::default_epsilon()
}
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
let xyz_eq = self.xyz.abs_diff_eq(&other.xyz, epsilon);
let xyzn_eq = self.white_point.abs_diff_eq(&other.white_point, epsilon);
xyz_eq && xyzn_eq
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{observer::Observer::Cie1931, xyz::Chromaticity};
use approx::assert_abs_diff_eq;
#[test]
fn test_new() {
let xyz = [1.0, 2.0, 3.0];
let white = XYZ::new([100.0, 100.0, 100.0], Cie1931);
let rel_xyz = RelXYZ::new(xyz, white);
assert_abs_diff_eq!(rel_xyz.xyz.x, 1.0);
assert_abs_diff_eq!(rel_xyz.xyz.y, 2.0);
assert_abs_diff_eq!(rel_xyz.xyz.z, 3.0);
assert_abs_diff_eq!(rel_xyz.white_point.xyz.x, 100.0);
}
#[test]
fn test_from_vec() {
let xyz = Vector3::new(1.0, 2.0, 3.0);
let white = XYZ::new([100.0, 100.0, 100.0], Cie1931);
let rel_xyz = RelXYZ::from_vec(xyz, white);
assert_abs_diff_eq!(rel_xyz.xyz.x, 1.0);
assert_abs_diff_eq!(rel_xyz.xyz.y, 2.0);
assert_abs_diff_eq!(rel_xyz.xyz.z, 3.0);
}
#[test]
fn test_with_d65() {
let xyz = XYZ::new([1.0, 2.0, 3.0], Cie1931);
let rel_xyz = RelXYZ::with_d65(xyz);
assert_abs_diff_eq!(rel_xyz.xyz.x, 1.0);
assert_eq!(rel_xyz.white_point, Cie1931.xyz_d65());
}
#[test]
fn test_with_d50() {
let xyz = XYZ::new([1.0, 2.0, 3.0], Cie1931);
let rel_xyz = RelXYZ::with_d50(xyz);
assert_abs_diff_eq!(rel_xyz.xyz.x, 1.0);
assert_eq!(rel_xyz.white_point, Cie1931.xyz_d50());
}
#[test]
fn test_set_illuminance() {
let white = XYZ::new([50.0, 50.0, 50.0], Cie1931);
let rel_xyz = RelXYZ::new([5.0; 3], white).set_illuminance(100.0);
assert_abs_diff_eq!(rel_xyz.xyz.y, 10.0);
assert_abs_diff_eq!(rel_xyz.white_point.xyz.y, 100.0);
}
#[test]
#[should_panic]
fn test_set_illuminance_zero() {
let xyz = XYZ::new([1.0, 2.0, 3.0], Cie1931);
let white = XYZ::new([100.0, 100.0, 100.0], Cie1931);
let rel_xyz = RelXYZ::from_xyz(xyz, white).unwrap();
rel_xyz.set_illuminance(0.0);
}
#[test]
fn test_spectral_locus_round_trip() {
use crate::{illuminant::CieIlluminant, observer::Observer::Cie1931};
let sl = Cie1931.monochromes(CieIlluminant::D65);
for (_w, rxyz) in sl {
let lab = CieLab::from_rxyz(rxyz);
let xyz_back = lab.rxyz();
approx::assert_abs_diff_eq!(rxyz, xyz_back, epsilon = 1E-6)
}
}
#[test]
fn test_spectral_locus_round_trip_print() {
use crate::{illuminant::CieIlluminant, observer::Observer::Cie1931};
let sl = Cie1931.monochromes(CieIlluminant::D65);
for (w, rxyz) in sl {
print!("{}, {:.4?}", w, rxyz.to_arrays()[0]);
let lab = CieLab::from_rxyz(rxyz);
let xyz_back = lab.rxyz();
println!("{:.4?}", xyz_back.to_arrays()[0]);
}
}
#[test]
fn test_is_valid() {
let white = XYZ::new([95.047, 100.0, 108.883], Cie1931); let valid_rel_xyz = RelXYZ::new([41.24, 21.26, 1.93], white);
assert!(valid_rel_xyz.is_valid());
let mut invalid_rel_xyz = RelXYZ::new([200.0, -50.0, 300.0], white);
assert!(!invalid_rel_xyz.is_valid());
let xyz = XYZ::from_chromaticity(Chromaticity::new(0.05, 0.05), None, None).unwrap();
invalid_rel_xyz = RelXYZ::with_d65(xyz);
assert!(!invalid_rel_xyz.is_valid());
let xyz = XYZ::from_chromaticity(Chromaticity::new(0.03, 0.85), None, None).unwrap();
invalid_rel_xyz = RelXYZ::with_d65(xyz);
assert!(!invalid_rel_xyz.is_valid());
}
}