use crate::space::{Lab, Xyz};
pub const GRAPHIC_ARTS_KL: f64 = 1.0;
pub const GRAPHIC_ARTS_K1: f64 = 0.045;
pub const GRAPHIC_ARTS_K2: f64 = 0.015;
pub const TEXTILES_KL: f64 = 2.0;
pub const TEXTILES_K1: f64 = 0.048;
pub const TEXTILES_K2: f64 = 0.014;
pub fn calculate(reference: impl Into<Xyz>, sample: impl Into<Xyz>) -> f64 {
calculate_parametric(reference, sample, GRAPHIC_ARTS_KL, GRAPHIC_ARTS_K1, GRAPHIC_ARTS_K2)
}
pub fn calculate_parametric(reference: impl Into<Xyz>, sample: impl Into<Xyz>, kl: f64, k1: f64, k2: f64) -> f64 {
let ref_lab = Lab::from(reference.into());
let smp_lab = Lab::from(sample.into());
let dl = ref_lab.l() - smp_lab.l();
let da = ref_lab.a() - smp_lab.a();
let db = ref_lab.b() - smp_lab.b();
let c1 = (ref_lab.a().powi(2) + ref_lab.b().powi(2)).sqrt();
let c2 = (smp_lab.a().powi(2) + smp_lab.b().powi(2)).sqrt();
let dc = c1 - c2;
let dh_sq = da * da + db * db - dc * dc;
let dh_sq = dh_sq.max(0.0);
let sl = 1.0;
let sc = 1.0 + k1 * c1;
let sh = 1.0 + k2 * c1;
let term_l = dl / (kl * sl);
let term_c = dc / sc;
let term_h = dh_sq / (sh * sh);
(term_l * term_l + term_c * term_c + term_h).sqrt()
}
pub fn calculate_textiles(reference: impl Into<Xyz>, sample: impl Into<Xyz>) -> f64 {
calculate_parametric(reference, sample, TEXTILES_KL, TEXTILES_K1, TEXTILES_K2)
}
#[cfg(test)]
mod test {
use super::*;
mod calculate {
use super::*;
#[test]
fn it_returns_zero_for_identical_colors() {
let color = Xyz::new(0.4, 0.5, 0.3);
assert_eq!(calculate(color, color), 0.0);
}
#[test]
fn it_returns_positive_for_different_colors() {
let a = Xyz::new(0.0, 0.0, 0.0);
let b = Xyz::new(0.9505, 1.0, 1.089);
assert!(calculate(a, b) > 0.0);
}
#[test]
fn it_is_not_order_independent() {
let a = Xyz::new(0.1, 0.2, 0.3);
let b = Xyz::new(0.5, 0.6, 0.7);
let forward = calculate(a, b);
let reverse = calculate(b, a);
assert!((forward - reverse).abs() > 1e-10 || forward == reverse);
}
#[test]
fn it_increases_with_greater_difference() {
let reference = Xyz::new(0.9505, 1.0, 1.089);
let near = Xyz::new(0.8, 0.9, 0.95);
let far = Xyz::new(0.2, 0.3, 0.25);
assert!(calculate(reference, far) > calculate(reference, near));
}
}
mod calculate_parametric {
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn it_matches_graphic_arts_with_same_params() {
let a = Xyz::new(0.1, 0.2, 0.3);
let b = Xyz::new(0.5, 0.6, 0.7);
assert_eq!(
calculate_parametric(a, b, GRAPHIC_ARTS_KL, GRAPHIC_ARTS_K1, GRAPHIC_ARTS_K2),
calculate(a, b)
);
}
#[test]
fn it_matches_textiles_with_same_params() {
let a = Xyz::new(0.1, 0.2, 0.3);
let b = Xyz::new(0.5, 0.6, 0.7);
assert_eq!(
calculate_parametric(a, b, TEXTILES_KL, TEXTILES_K1, TEXTILES_K2),
calculate_textiles(a, b)
);
}
}
mod calculate_textiles {
use super::*;
#[test]
fn it_returns_zero_for_identical_colors() {
let color = Xyz::new(0.4, 0.5, 0.3);
assert_eq!(calculate_textiles(color, color), 0.0);
}
#[test]
fn it_differs_from_graphic_arts() {
let a = Xyz::new(0.1, 0.2, 0.3);
let b = Xyz::new(0.5, 0.6, 0.7);
let graphic = calculate(a, b);
let textile = calculate_textiles(a, b);
assert!((graphic - textile).abs() > 1e-10);
}
}
}