use std::f64::consts::PI;
use crate::math::{self, distance};
use crate::observer::Observer::Cie1964;
use crate::xyz::RelXYZ;
use crate::{
cam::{CamTransforms, CieCam02, TM30VC},
colorant::{CES, N_CFI},
illuminant::Illuminant,
};
use super::CCT;
pub const N_ANGLE_BIN: usize = 16;
#[cfg_attr(target_arch = "wasm32", wasm_bindgen::prelude::wasm_bindgen)]
pub struct CFI {
jabp_ts: [[f64; 3]; N_CFI],
jabp_rs: [[f64; 3]; N_CFI],
cct: CCT,
}
impl CFI {
pub fn new(illuminant: &Illuminant) -> Result<Self, crate::Error> {
let cct = illuminant.cct()?;
let ref_illuminant = Illuminant::cfi_reference(cct.t())?;
let vc = TM30VC;
let xyzn_r = Cie1964.xyz(&ref_illuminant, None).set_illuminance(100.0);
let xyzn_t = Cie1964.xyz(illuminant, None).set_illuminance(100.0);
let mut jabp_ts = [[0f64; 3]; N_CFI];
let mut jabp_rs = [[0f64; 3]; N_CFI];
for (i, cfi_ces) in CES.iter().enumerate() {
let xyz_t = Cie1964.xyz(illuminant, Some(cfi_ces));
let rxyz_t = RelXYZ::from_xyz(xyz_t, xyzn_t)?;
let jabp_t = CieCam02::from_xyz(rxyz_t, vc).jab_prime();
jabp_ts[i] = jabp_t;
let xyz_r = Cie1964.xyz(&ref_illuminant, Some(cfi_ces));
let rxyz_r = RelXYZ::from_xyz(xyz_r, xyzn_r)?;
let jabp_r = CieCam02::from_xyz(rxyz_r, vc).jab_prime();
jabp_rs[i] = jabp_r;
}
Ok(CFI {
jabp_ts,
jabp_rs,
cct,
})
}
pub fn jabp_ts(&self) -> &[[f64; 3]; N_CFI] {
&self.jabp_ts
}
pub fn jabp_rs(&self) -> &[[f64; 3]; N_CFI] {
&self.jabp_rs
}
pub fn chroma_ts(&self) -> [f64; N_CFI] {
compute_chroma(&self.jabp_ts)
}
pub fn chroma_rs(&self) -> [f64; N_CFI] {
compute_chroma(&self.jabp_rs)
}
pub fn hue_angle_bin_ts(&self) -> [f64; N_CFI] {
compute_hue_angle_bin(&self.jabp_ts)
}
pub fn hue_angle_bin_rs(&self) -> [f64; N_CFI] {
compute_hue_angle_bin(&self.jabp_rs)
}
pub fn chroma_shift(&self) -> [f64; N_CFI] {
let ct = self.chroma_ts();
let cr = self.chroma_rs();
let mut result = [0f64; N_CFI];
for i in 0..N_CFI {
result[i] = if cr[i] == 0. {
f64::NAN
} else {
(ct[i] - cr[i]) / cr[i]
};
}
result
}
pub fn hue_shift(&self) -> [f64; N_CFI] {
let ht = self.hue_angle_bin_ts();
let hr = self.hue_angle_bin_rs();
let mut result = [0f64; N_CFI];
for i in 0..N_CFI {
let mut dh = ht[i] - hr[i];
if dh > PI {
dh -= 2. * PI;
} else if dh <= -PI {
dh += 2. * PI;
}
result[i] = dh;
}
result
}
pub fn hue_angle_bin_index(hue_angle_rad: f64) -> usize {
let phi = hue_angle_rad.rem_euclid(2. * PI);
(phi / (2. * PI) * N_ANGLE_BIN as f64) as usize
}
pub fn jabp_average_ts(&self) -> [[f64; 3]; N_ANGLE_BIN] {
let mut hue_angle_bins = [([0f64, 0f64, 0f64], 0f64); N_ANGLE_BIN];
for i in 0..N_CFI {
let [jt, at, bt] = self.jabp_ts[i];
let [_jr, ar, br] = self.jabp_rs[i];
let mut phi = f64::atan2(br, ar);
if phi < 0. {
phi += 2. * PI;
}
let i = (phi / (2. * PI) * N_ANGLE_BIN as f64) as usize;
hue_angle_bins[i].0[0] += jt;
hue_angle_bins[i].0[1] += at;
hue_angle_bins[i].0[2] += bt;
hue_angle_bins[i].1 += 1.; }
hue_angle_bins.map(|([j, a, b], n)| {
if n == 0. {
[f64::NAN, f64::NAN, f64::NAN]
} else {
[j / n, a / n, b / n]
}
})
}
pub fn jabp_average_rs(&self) -> [[f64; 3]; N_ANGLE_BIN] {
let mut hue_angle_bins = [([0f64, 0f64, 0f64], 0f64); N_ANGLE_BIN];
for i in 0..N_CFI {
let [jr, ar, br] = self.jabp_rs[i];
let mut phi = f64::atan2(br, ar);
if phi < 0. {
phi += 2. * PI;
}
let i = (phi / (2. * PI) * N_ANGLE_BIN as f64) as usize;
hue_angle_bins[i].0[0] += jr;
hue_angle_bins[i].0[1] += ar;
hue_angle_bins[i].0[2] += br;
hue_angle_bins[i].1 += 1.;
}
hue_angle_bins.map(|([j, a, b], n)| {
if n == 0. {
[f64::NAN, f64::NAN, f64::NAN]
} else {
[j / n, a / n, b / n]
}
})
}
pub fn normalized_ab_average(&self) -> ([[f64; 2]; N_ANGLE_BIN], [[f64; 2]; N_ANGLE_BIN]) {
let jabt_hj = self.jabp_average_ts();
let jabr_hj = self.jabp_average_rs();
compute_normalized_ab_average(&jabt_hj, &jabr_hj)
}
pub fn normalized_chroma_average_ts(&self) -> [f64; N_ANGLE_BIN] {
let jabt_hj = self.jabp_average_ts();
compute_normalized_chroma_average(&jabt_hj)
}
pub fn normalized_chroma_average_rs(&self) -> [f64; N_ANGLE_BIN] {
let jabr_hj = self.jabp_average_rs();
compute_normalized_chroma_average(&jabr_hj)
}
pub fn normalized_chroma_average(&self) -> [f64; N_ANGLE_BIN] {
let ct = self.normalized_chroma_average_ts();
let cr = self.normalized_chroma_average_rs();
let mut ctn = [0f64; N_ANGLE_BIN];
for i in 0..N_ANGLE_BIN {
ctn[i] = if cr[i].is_nan() || cr[i] == 0. {
f64::NAN
} else {
ct[i] / cr[i]
};
}
ctn
}
pub fn hue_angle_bin_average_samples_ts(&self) -> [f64; N_ANGLE_BIN] {
compute_hue_angle_bin_average(&self.jabp_average_ts())
}
pub fn hue_angle_bin_average_samples_rs(&self) -> [f64; N_ANGLE_BIN] {
compute_hue_angle_bin_average(&self.jabp_average_rs())
}
pub fn color_gamut_index(&self) -> f64 {
let origin = [0.; 2];
let av_samples_t = self.jabp_average_ts();
let av_samples_r = self.jabp_average_rs();
let mut at = 0f64;
let mut ar = 0f64;
for i in 0..N_ANGLE_BIN {
let area_t = math::compute_triangle_area(
&[av_samples_t[i][1], av_samples_t[i][2]],
&[
av_samples_t[(i + 1) % N_ANGLE_BIN][1],
av_samples_t[(i + 1) % N_ANGLE_BIN][2],
],
&origin,
);
let area_r = math::compute_triangle_area(
&[av_samples_r[i][1], av_samples_r[i][2]],
&[
av_samples_r[(i + 1) % N_ANGLE_BIN][1],
av_samples_r[(i + 1) % N_ANGLE_BIN][2],
],
&origin,
);
at += area_t;
ar += area_r;
}
100. * at / ar
}
#[deprecated(since = "0.0.8", note = "use `color_gamut_index()` instead")]
pub fn rg(&self) -> f64 {
self.color_gamut_index()
}
pub fn special_color_fidelity_indices(&self) -> [f64; N_CFI] {
let mut cfis = [0f64; N_CFI];
for (i, (jabd, jabr)) in self.jabp_ts.iter().zip(self.jabp_rs.iter()).enumerate() {
let de = distance(jabd, jabr);
cfis[i] = rf_from_de(de);
}
cfis
}
pub fn color_fidelity_index(&self) -> f64 {
let mut sum = 0.0;
for (jabp_t, jabp_r) in self.jabp_ts.iter().zip(self.jabp_rs.iter()) {
let de = distance(jabp_t, jabp_r);
sum += de;
}
rf_from_de(sum / N_CFI as f64)
}
pub fn local_color_fidelity_indices(&self) -> [f64; N_ANGLE_BIN] {
let mut sum_de = [0f64; N_ANGLE_BIN];
let mut count = [0usize; N_ANGLE_BIN];
for i in 0..N_CFI {
let [_jr, ar, br] = self.jabp_rs[i];
let mut phi = f64::atan2(br, ar);
if phi < 0. {
phi += 2. * PI;
}
let bin = (phi / (2. * PI) * N_ANGLE_BIN as f64) as usize;
sum_de[bin] += distance(&self.jabp_ts[i], &self.jabp_rs[i]);
count[bin] += 1;
}
let mut result = [0f64; N_ANGLE_BIN];
for j in 0..N_ANGLE_BIN {
result[j] = if count[j] == 0 {
f64::NAN
} else {
rf_from_de(sum_de[j] / count[j] as f64)
};
}
result
}
pub fn chroma_shift_indices(&self) -> [f64; N_ANGLE_BIN] {
let ct = self.normalized_chroma_average_ts();
let cr = self.normalized_chroma_average_rs();
let mut result = [0f64; N_ANGLE_BIN];
for j in 0..N_ANGLE_BIN {
result[j] = if cr[j].is_nan() || cr[j] == 0. {
f64::NAN
} else {
(ct[j] - cr[j]) / cr[j]
};
}
result
}
pub fn hue_shift_indices(&self) -> [f64; N_ANGLE_BIN] {
let ht = self.hue_angle_bin_average_samples_ts();
let hr = self.hue_angle_bin_average_samples_rs();
let mut result = [0f64; N_ANGLE_BIN];
for j in 0..N_ANGLE_BIN {
let mut dh = ht[j] - hr[j];
if dh > PI {
dh -= 2. * PI;
} else if dh <= -PI {
dh += 2. * PI;
}
result[j] = dh;
}
result
}
pub fn cct(&self) -> CCT {
self.cct
}
}
fn compute_hue_angle_bin(jab: &[[f64; 3]; N_CFI]) -> [f64; N_CFI] {
jab.map(|[_j, a, b]| {
let mut h = f64::atan2(b, a);
if h < 0. {
h += 2. * PI;
}
h
})
}
fn compute_hue_angle_bin_average(jab: &[[f64; 3]; N_ANGLE_BIN]) -> [f64; N_ANGLE_BIN] {
jab.map(|[_j, a, b]| {
let mut h = f64::atan2(b, a);
if h < 0. {
h += 2. * PI;
}
h
})
}
fn compute_chroma(jab: &[[f64; 3]; N_CFI]) -> [f64; N_CFI] {
jab.map(|[_j, a, b]| f64::sqrt(a * a + b * b))
}
fn compute_normalized_chroma_average(jab_hj: &[[f64; 3]; N_ANGLE_BIN]) -> [f64; N_ANGLE_BIN] {
jab_hj.map(|[_j, a, b]| f64::sqrt(a * a + b * b))
}
fn compute_normalized_ab_average(
jabt: &[[f64; 3]; N_ANGLE_BIN],
jabr: &[[f64; 3]; N_ANGLE_BIN],
) -> ([[f64; 2]; N_ANGLE_BIN], [[f64; 2]; N_ANGLE_BIN]) {
let ht_hj = compute_hue_angle_bin_average(jabt);
let hr_hj = compute_hue_angle_bin_average(jabr);
let ct = compute_normalized_chroma_average(jabt);
let cr = compute_normalized_chroma_average(jabr);
let mut ctn = [0f64; N_ANGLE_BIN];
for i in 0..N_ANGLE_BIN {
if cr[i] == 0. {
ctn[i] = f64::INFINITY;
} else {
ctn[i] = ct[i] / (cr[i] + 1e-308);
}
}
let mut jabtn_hj = [[0f64; 2]; N_ANGLE_BIN];
let mut jabrn_hj = [[0f64; 2]; N_ANGLE_BIN];
for i in 0..N_ANGLE_BIN {
let c = ctn[i];
let ht = ht_hj[i];
let hr = hr_hj[i];
jabtn_hj[i] = [c * f64::cos(ht), c * f64::sin(ht)];
jabrn_hj[i] = [f64::cos(hr), f64::sin(hr)];
}
(jabtn_hj, jabrn_hj)
}
#[allow(dead_code)]
fn compute_hue_bin_edges() -> [f64; N_ANGLE_BIN + 1] {
let dh = 360. / N_ANGLE_BIN as f64;
let mut hbe = [0f64; N_ANGLE_BIN + 1];
#[allow(clippy::needless_range_loop)]
for i in 0..=N_ANGLE_BIN {
let m = i as f64;
hbe[i] = dh * m;
}
hbe
}
const CF: f64 = 6.73;
pub fn rf_from_de(de: f64) -> f64 {
10.0 * (((100.0 - CF * de) / 10.0).exp() + 1.0).ln()
}
#[cfg(test)]
mod tests {
use super::CFI;
use crate::{colorant::N_CFI, illuminant::D65};
#[cfg(feature = "cie-illuminants")]
use crate::illuminant::{F1, F12, F2};
use approx::assert_abs_diff_eq;
#[test]
fn test_cfi() {
let cfi = CFI::new(&D65).unwrap();
let cfis = cfi.special_color_fidelity_indices();
assert_eq!(cfis.len(), N_CFI);
for &cfi in cfis.iter() {
assert_abs_diff_eq!(cfi, 100.0, epsilon = 0.01);
}
assert_abs_diff_eq!(cfi.color_fidelity_index(), 100.0, epsilon = 0.01);
assert!(cfi.color_fidelity_index() >= 0.0);
for &rf in cfi.local_color_fidelity_indices().iter() {
assert_abs_diff_eq!(rf, 100.0, epsilon = 0.01);
}
for &rcs in cfi.chroma_shift_indices().iter() {
assert_abs_diff_eq!(rcs, 0.0, epsilon = 1e-4);
}
for &rhs in cfi.hue_shift_indices().iter() {
assert_abs_diff_eq!(rhs, 0.0, epsilon = 1e-4);
}
}
#[test]
#[cfg(feature = "cie-illuminants")]
fn test_fl1() {
let cfi = CFI::new(&F1).unwrap();
assert_abs_diff_eq!(cfi.cct().t(), 6425.0, epsilon = 6.0);
assert_abs_diff_eq!(cfi.cct().d(), 0.0072, epsilon = 0.0002);
assert_abs_diff_eq!(cfi.color_fidelity_index(), 80.68, epsilon = 0.5);
assert_abs_diff_eq!(cfi.color_gamut_index(), 89.83, epsilon = 0.5);
}
#[test]
#[cfg(feature = "cie-illuminants")]
fn test_fl2() {
let cfi = CFI::new(&F2).unwrap();
assert_abs_diff_eq!(cfi.cct().t(), 4225.0, epsilon = 1.0);
assert_abs_diff_eq!(cfi.cct().d(), 0.0019, epsilon = 0.0002);
assert_abs_diff_eq!(cfi.color_fidelity_index(), 70.21, epsilon = 0.5);
assert_abs_diff_eq!(cfi.color_gamut_index(), 86.44, epsilon = 0.5);
}
#[test]
#[cfg(feature = "cie-illuminants")]
fn test_fl12() {
let cfi = CFI::new(&F12).unwrap();
assert_abs_diff_eq!(cfi.cct().t(), 3003.0, epsilon = 4.0);
assert_abs_diff_eq!(cfi.cct().d(), 0.0001, epsilon = 0.0002);
assert_abs_diff_eq!(cfi.color_fidelity_index(), 77.7, epsilon = 0.5);
assert_abs_diff_eq!(cfi.color_gamut_index(), 102.4, epsilon = 0.5);
}
#[test]
#[allow(unused)]
#[cfg(feature = "cie-illuminants")]
fn test_fl12_jab_from_tm30_20() {
use crate::illuminant::cfi::N_ANGLE_BIN;
const WANT: [[f64; 6]; N_CFI] = [
[85.93, 17.36, 0.22, 86.14, 16.84, 1.68],
[63.00, 30.81, 2.78, 63.29, 31.27, 4.79],
[31.04, 12.48, 1.98, 32.02, 15.27, 2.45],
[69.99, 19.87, 4.35, 70.01, 20.42, 6.61],
[48.78, 28.66, 6.02, 51.47, 33.95, 9.86],
[50.69, 16.32, 5.43, 51.48, 18.06, 7.00],
[42.98, 28.02, 10.65, 43.89, 32.10, 10.92],
[40.97, 23.91, 9.23, 42.99, 29.99, 10.06],
[28.93, 4.48, 1.06, 29.06, 3.34, 0.63],
[76.03, 29.39, 17.98, 75.81, 29.43, 18.15],
[57.23, 28.93, 17.29, 58.66, 28.22, 19.37],
[64.90, 29.26, 20.30, 64.89, 30.10, 20.89],
[44.49, 17.81, 15.40, 43.94, 18.85, 14.46],
[74.40, 5.70, 6.35, 74.23, 6.44, 5.91],
[71.82, 12.27, 12.31, 71.82, 13.00, 10.48],
[47.58, 12.49, 17.56, 48.39, 17.68, 16.30],
[49.03, 12.26, 17.37, 50.08, 14.83, 17.07],
[56.73, 10.24, 14.86, 56.73, 11.95, 13.26],
[70.74, 13.39, 22.92, 71.56, 13.59, 23.22],
[67.05, 17.94, 29.68, 67.41, 19.85, 27.49],
[86.65, 9.39, 29.19, 86.12, 13.62, 27.92],
[78.73, 11.61, 30.54, 78.64, 14.99, 28.65],
[91.67, 1.39, 13.45, 91.50, 3.70, 12.08],
[90.93, 2.14, 25.54, 90.33, 6.16, 24.05],
[72.92, 1.30, 34.03, 71.45, 7.84, 32.53],
[89.96, 2.34, 30.77, 89.48, 6.48, 28.38],
[73.52, -0.71, 16.78, 73.38, 1.34, 14.65],
[42.42, -2.10, 16.30, 41.96, 0.20, 15.27],
[86.95, -2.83, 32.65, 86.62, 1.84, 28.25],
[64.26, -0.53, 21.73, 64.52, 1.91, 16.31],
[88.51, -3.31, 31.96, 88.31, 0.30, 28.19],
[75.57, -7.12, 33.96, 75.15, -0.70, 30.91],
[89.61, -2.57, 23.01, 89.63, 0.12, 18.81],
[63.27, -8.21, 30.06, 62.03, -3.31, 27.82],
[43.23, -5.88, 16.36, 42.44, -2.70, 14.94],
[80.57, -0.88, 9.76, 80.75, -0.00, 6.75],
[49.78, -10.80, 20.82, 47.99, -5.14, 18.40],
[86.17, -4.81, 21.04, 86.33, -2.65, 15.65],
[35.37, -1.48, 7.55, 35.44, -1.91, 6.63],
[50.32, -3.49, 14.09, 50.31, -4.04, 12.64],
[87.91, -4.28, 10.28, 88.28, -3.06, 7.93],
[69.90, -20.64, 29.92, 68.42, -16.68, 27.39],
[72.58, -18.96, 30.97, 72.18, -17.35, 27.79],
[28.81, -1.08, 1.47, 28.84, -0.74, 1.27],
[58.60, -19.62, 21.97, 58.26, -18.18, 19.45],
[66.62, -14.92, 18.06, 66.54, -14.38, 15.35],
[69.30, -10.74, 13.99, 70.20, -8.98, 10.79],
[77.71, -19.95, 19.00, 77.38, -19.87, 13.73],
[46.29, -17.60, 15.69, 46.25, -17.46, 12.82],
[54.74, -17.73, 9.45, 54.69, -18.06, 6.88],
[58.64, -14.82, 8.25, 58.64, -15.81, 5.65],
[40.83, -18.89, 9.16, 41.19, -21.19, 7.26],
[49.04, -29.01, 6.63, 49.42, -30.76, 3.49],
[86.80, -16.62, 1.40, 87.01, -16.12, -0.70],
[83.41, -17.82, 0.49, 83.58, -17.92, -2.30],
[70.51, -29.29, -0.75, 70.87, -30.20, -3.81],
[57.01, -30.73, -0.31, 57.35, -31.89, -4.17],
[53.10, -30.69, -3.05, 53.65, -31.46, -6.27],
[86.90, -15.20, -2.23, 86.85, -15.89, -3.25],
[88.24, -6.32, -1.55, 88.42, -6.65, -2.77],
[76.76, -13.21, -4.70, 76.92, -14.47, -5.57],
[66.65, -5.73, -0.95, 67.33, -6.05, -4.24],
[34.09, 3.67, -1.30, 34.80, -1.97, -2.75],
[57.44, -26.33, -13.56, 58.35, -28.72, -14.80],
[39.40, -14.70, -10.20, 40.07, -18.16, -10.38],
[42.93, -21.05, -16.66, 44.49, -25.08, -17.06],
[46.15, -21.37, -18.18, 47.72, -25.38, -18.46],
[63.40, -13.47, -15.51, 64.01, -16.94, -15.20],
[53.26, -2.83, -10.11, 53.62, -7.01, -10.45],
[71.80, -13.47, -21.66, 72.54, -17.17, -21.01],
[34.66, -10.98, -18.65, 35.26, -14.05, -20.43],
[76.70, -4.53, -8.08, 76.90, -5.93, -8.13],
[57.11, -15.83, -26.54, 57.75, -19.84, -25.72],
[27.84, -1.32, -3.26, 28.50, -0.93, -3.68],
[32.49, -7.86, -20.67, 33.36, -10.99, -21.74],
[47.96, -7.41, -31.12, 48.46, -12.37, -30.21],
[43.45, -8.16, -23.94, 45.11, -7.32, -22.91],
[44.16, -5.88, -30.54, 44.68, -10.44, -29.80],
[73.23, -2.47, -20.69, 74.24, -2.66, -19.19],
[65.46, 3.02, -23.15, 66.50, 1.77, -21.95],
[43.11, 1.88, -28.81, 45.17, 5.56, -25.74],
[87.27, 2.34, -9.89, 87.57, 1.45, -9.24],
[72.97, 4.69, -17.25, 73.49, 3.76, -15.44],
[28.35, 6.97, -5.59, 28.46, 4.49, -6.05],
[40.98, 5.59, -12.55, 41.96, 6.99, -11.55],
[64.55, 5.93, -24.09, 66.65, 11.03, -19.67],
[40.81, 16.46, -19.00, 40.62, 14.24, -19.03],
[63.31, 19.69, -16.37, 63.37, 18.17, -14.77],
[38.17, 17.04, -15.53, 39.25, 17.82, -12.89],
[44.97, 21.49, -15.49, 44.87, 20.37, -13.14],
[80.57, 3.47, 0.29, 80.92, 3.91, -1.57],
[55.59, 13.25, -9.30, 57.49, 16.69, -6.95],
[67.27, 12.21, -6.53, 68.29, 14.10, -5.37],
[38.47, 18.50, -10.03, 40.85, 23.48, -5.81],
[70.48, 17.28, -5.24, 71.34, 18.53, -4.33],
[78.62, 16.87, -4.92, 79.23, 17.53, -3.42],
[51.06, 26.57, -6.52, 53.25, 23.14, -2.36],
[65.79, 30.88, -1.85, 65.86, 29.97, 0.54],
[53.91, 30.88, -1.38, 54.35, 32.52, 0.69],
];
let cfi = CFI::new(&F12).unwrap();
for (i, (&[jt, at, bt], &[jr, ar, br])) in
cfi.jabp_ts().iter().zip(cfi.jabp_rs().iter()).enumerate()
{
assert_abs_diff_eq!(jt, WANT[i][0], epsilon = 0.2);
assert_abs_diff_eq!(at, WANT[i][1], epsilon = 0.2);
assert_abs_diff_eq!(bt, WANT[i][2], epsilon = 0.2);
assert_abs_diff_eq!(jr, WANT[i][3], epsilon = 0.2);
assert_abs_diff_eq!(ar, WANT[i][4], epsilon = 0.2);
assert_abs_diff_eq!(br, WANT[i][5], epsilon = 0.2);
}
const WANT_BINS: [[[f64; 2]; 2]; N_ANGLE_BIN] = [
[[22.153, 3.499], [23.975, 5.022]], [[19.408, 15.312], [20.531, 15.080]], [[12.471, 24.093], [14.806, 22.932]], [[-0.487, 24.623], [2.988, 21.852]], [[-5.216, 18.212], [-2.614, 15.741]], [[-14.324, 19.399], [-12.719, 17.006]], [[-18.774, 17.349], [-18.664, 13.276]], [[-20.113, 8.375], [-21.455, 5.820]], [[-21.937, -1.306], [-22.563, -3.723]], [[-15.568, -10.944], [-18.141, -11.843]], [[-7.404, -15.288], [-10.994, -15.747]], [[-5.048, -21.910], [-6.744, -21.155]], [[2.983, -19.776], [3.135, -18.094]], [[8.740, -15.310], [9.188, -14.075]], [[17.868, -14.172], [18.262, -11.938]], [[15.816, -5.492], [16.781, -3.811]], ];
let av_samples_t = cfi.jabp_average_ts();
for (i, &[_, at, bt]) in av_samples_t.iter().enumerate() {
use approx::abs_diff_eq;
let [[at_w, bt_w], [_ar_w, _br_w]] = WANT_BINS[i];
assert!(
abs_diff_eq!(at, at_w, epsilon = 1.0),
"at failed at index {i}: got {at}, want {at_w}"
);
let bt_epsilon = if i == 3 || i == 4 { 1.5 } else { 1.0 };
assert!(
abs_diff_eq!(bt, bt_w, epsilon = bt_epsilon),
"bt failed at index {i}: got {bt}, want {bt_w}"
);
}
}
#[test]
#[cfg(feature = "cie-illuminants")]
fn test_local_metrics_fl1() {
let cfi = CFI::new(&F1).unwrap();
let rf_hj_want = [
64.52, 76.27, 70.52, 81.72, 86.43, 91.88, 88.93, 81.30, 86.87, 79.56, 82.99, 90.61,
86.35, 76.16, 69.85, 76.14,
];
for (j, (&got, &want)) in cfi
.local_color_fidelity_indices()
.iter()
.zip(rf_hj_want.iter())
.enumerate()
{
assert!(
approx::abs_diff_eq!(got, want, epsilon = 10.0),
"Rf,hj bin {j}: got {got:.3}, want {want:.3}"
);
}
let rcs_hj_want = [
-0.20, -0.13, -0.07, 0.02, 0.07, 0.01, -0.05, -0.10, -0.11, -0.07, -0.01, 0.04, 0.07,
0.02, -0.09, -0.10,
];
for (j, (&got, &want)) in cfi
.chroma_shift_indices()
.iter()
.zip(rcs_hj_want.iter())
.enumerate()
{
assert!(
approx::abs_diff_eq!(got, want, epsilon = 0.05),
"Rcs,hj bin {j}: got {got:.4}, want {want:.4}"
);
}
let rhs_hj_want = [
-0.0248, 0.0843, 0.1564, 0.1126, 0.0579, -0.0427, -0.0474, -0.0511, 0.0252, 0.0975,
0.0985, 0.0302, -0.0833, -0.1430, -0.2770, -0.1015,
];
for (j, (&got, &want)) in cfi
.hue_shift_indices()
.iter()
.zip(rhs_hj_want.iter())
.enumerate()
{
assert!(
approx::abs_diff_eq!(got, want, epsilon = 0.04),
"Rhs,hj bin {j}: got {got:.4}, want {want:.4}"
);
}
}
#[test]
#[cfg(feature = "cie-illuminants")]
fn test_local_metrics_fl2() {
let cfi = CFI::new(&F2).unwrap();
let rf_hj_want = [
60.20, 61.28, 52.52, 68.30, 79.50, 87.51, 76.74, 72.71, 76.13, 62.27, 69.57, 76.48,
81.32, 71.36, 63.60, 65.27,
];
for (j, (&got, &want)) in cfi
.local_color_fidelity_indices()
.iter()
.zip(rf_hj_want.iter())
.enumerate()
{
assert!(
approx::abs_diff_eq!(got, want, epsilon = 10.0),
"Rf,hj bin {j}: got {got:.3}, want {want:.3}"
);
}
let rcs_hj_want = [
-0.25, -0.18, -0.09, 0.05, 0.11, 0.04, -0.08, -0.15, -0.17, -0.15, -0.04, 0.05, 0.11,
0.07, -0.06, -0.16,
];
for (j, (&got, &want)) in cfi
.chroma_shift_indices()
.iter()
.zip(rcs_hj_want.iter())
.enumerate()
{
assert!(
approx::abs_diff_eq!(got, want, epsilon = 0.05),
"Rcs,hj bin {j}: got {got:.4}, want {want:.4}"
);
}
let rhs_hj_want = [
-0.0221, 0.1400, 0.2444, 0.1963, 0.0915, -0.0673, -0.1226, -0.0848, 0.0061, 0.1659,
0.1906, 0.1147, -0.0806, -0.1467, -0.2635, -0.1703,
];
for (j, (&got, &want)) in cfi
.hue_shift_indices()
.iter()
.zip(rhs_hj_want.iter())
.enumerate()
{
assert!(
approx::abs_diff_eq!(got, want, epsilon = 0.04),
"Rhs,hj bin {j}: got {got:.4}, want {want:.4}"
);
}
}
#[test]
#[cfg(feature = "cie-illuminants")]
fn test_local_metrics_fl12() {
let cfi = CFI::new(&F12).unwrap();
let rf_hj_want = [
78.222, 86.843, 80.612, 68.521, 72.478, 79.408, 72.551, 79.812, 82.356, 77.682, 74.540,
80.448, 82.391, 76.766, 79.243, 76.808,
];
for (j, (&got, &want)) in cfi
.local_color_fidelity_indices()
.iter()
.zip(rf_hj_want.iter())
.enumerate()
{
assert!(
approx::abs_diff_eq!(got, want, epsilon = 10.0),
"Rf,hj bin {j}: got {got:.3}, want {want:.3}"
);
}
let rcs_hj_want = [
-0.08510, -0.03160, -0.01216, 0.09247, 0.18373, 0.13567, 0.10277, -0.03677, -0.04746,
-0.12181, -0.12371, 0.01845, 0.08819, 0.04632, 0.04186, -0.03594,
];
for (j, (&got, &want)) in cfi
.chroma_shift_indices()
.iter()
.zip(rcs_hj_want.iter())
.enumerate()
{
assert!(
approx::abs_diff_eq!(got, want, epsilon = 0.05),
"Rcs,hj bin {j}: got {got:.5}, want {want:.5}"
);
}
let rhs_hj_want = [
-0.04646, 0.03206, 0.09473, 0.17904, 0.12972, 0.00023, -0.14516, -0.12447, -0.09830,
0.03149, 0.14218, 0.08155, -0.02598, -0.06301, -0.09517, -0.10676,
];
for (j, (&got, &want)) in cfi
.hue_shift_indices()
.iter()
.zip(rhs_hj_want.iter())
.enumerate()
{
assert!(
approx::abs_diff_eq!(got, want, epsilon = 0.04),
"Rhs,hj bin {j}: got {got:.5}, want {want:.5}"
);
}
}
}