use crate::difference::extract::{extract, mode_shape, normalize_hue, HueDiffKind, ModeShape};
use crate::Color;
pub(crate) type Weights = [f64; 4];
pub fn difference_euclidean(mode: &str) -> impl Fn(&Color, &Color) -> f64 {
difference_euclidean_with(mode, [1.0, 1.0, 1.0, 0.0])
}
pub fn difference_euclidean_with(mode: &str, weights: Weights) -> impl Fn(&Color, &Color) -> f64 {
let mode = mode.to_string();
let shape = mode_shape(&mode);
move |std, smp| euclidean(*std, *smp, &mode, shape, weights)
}
pub fn difference_ok() -> impl Fn(&Color, &Color) -> f64 {
difference_euclidean("oklab")
}
pub fn difference_euclidean_xyz() -> impl Fn(&Color, &Color) -> f64 {
difference_euclidean("xyz65")
}
pub(crate) fn euclidean(
std: Color,
smp: Color,
mode: &str,
shape: ModeShape,
weights: Weights,
) -> f64 {
let s = extract(std, mode);
let t = extract(smp, mode);
let mut sum = 0.0;
for (idx, ch) in shape.channels.iter().enumerate() {
let raw_delta = if ch.is_hue {
hue_delta(shape.hue_diff, &s, &t, idx)
} else {
s[idx] - t[idx]
};
let delta = if raw_delta.is_nan() { 0.0 } else { raw_delta };
sum += weights[idx] * delta * delta;
}
sum.sqrt()
}
fn hue_delta(kind: Option<HueDiffKind>, s: &[f64; 3], t: &[f64; 3], hue_idx: usize) -> f64 {
let kind = match kind {
Some(k) => k,
None => return s[hue_idx] - t[hue_idx],
};
let h_std = s[hue_idx];
let h_smp = t[hue_idx];
if h_std.is_nan() || h_smp.is_nan() {
return 0.0;
}
match kind {
HueDiffKind::Chroma => {
let c_std = s[1];
let c_smp = t[1];
if c_std == 0.0 || c_smp == 0.0 {
return 0.0;
}
polar_sin_term(h_std, h_smp) * 2.0 * (c_std * c_smp).sqrt()
}
HueDiffKind::Saturation => {
let s_std = s[1];
let s_smp = t[1];
if s_std == 0.0 || s_smp == 0.0 {
return 0.0;
}
polar_sin_term(h_std, h_smp) * 2.0 * (s_std * s_smp).sqrt()
}
HueDiffKind::Naive => {
let std_h = normalize_hue(h_std);
let smp_h = normalize_hue(h_smp);
if (smp_h - std_h).abs() > 180.0 {
std_h - (smp_h - 360.0 * (smp_h - std_h).signum())
} else {
smp_h - std_h
}
}
}
}
#[inline]
fn polar_sin_term(h_std: f64, h_smp: f64) -> f64 {
let std_h = normalize_hue(h_std);
let smp_h = normalize_hue(h_smp);
let theta_deg = (smp_h - std_h + 360.0) / 2.0;
theta_deg.to_radians().sin()
}