#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum HueFixup {
#[default]
Shorter,
Longer,
Increasing,
Decreasing,
Raw,
}
#[inline]
fn normalize_hue(h: f64) -> f64 {
let m = h % 360.0;
if m < 0.0 {
m + 360.0
} else {
m
}
}
fn apply_with<F: Fn(f64) -> f64>(hues: &[f64], delta_fn: F) -> Vec<f64> {
let mut deltas: Vec<f64> = Vec::with_capacity(hues.len());
for (idx, &h) in hues.iter().enumerate() {
if h.is_nan() {
deltas.push(f64::NAN);
continue;
}
let normalized = normalize_hue(h);
if idx == 0 || hues[idx - 1].is_nan() {
deltas.push(normalized);
} else {
let prev = normalize_hue(hues[idx - 1]);
deltas.push(delta_fn(normalized - prev));
}
}
let mut acc: Vec<f64> = Vec::with_capacity(deltas.len());
for d in deltas {
if acc.is_empty() || d.is_nan() || acc.last().is_some_and(|v| v.is_nan()) {
acc.push(d);
} else {
let prev = *acc.last().expect("non-empty");
acc.push(d + prev);
}
}
acc
}
pub(crate) fn apply(hues: &[f64], strategy: HueFixup) -> Vec<f64> {
match strategy {
HueFixup::Shorter => fixup_hue_shorter(hues),
HueFixup::Longer => fixup_hue_longer(hues),
HueFixup::Increasing => fixup_hue_increasing(hues),
HueFixup::Decreasing => fixup_hue_decreasing(hues),
HueFixup::Raw => hues.to_vec(),
}
}
pub fn fixup_hue_shorter(hues: &[f64]) -> Vec<f64> {
apply_with(hues, |d| {
if d.abs() <= 180.0 {
d
} else {
d - 360.0 * d.signum()
}
})
}
pub fn fixup_hue_longer(hues: &[f64]) -> Vec<f64> {
apply_with(hues, |d| {
if d.abs() >= 180.0 || d == 0.0 {
d
} else {
d - 360.0 * d.signum()
}
})
}
pub fn fixup_hue_increasing(hues: &[f64]) -> Vec<f64> {
apply_with(hues, |d| if d >= 0.0 { d } else { d + 360.0 })
}
pub fn fixup_hue_decreasing(hues: &[f64]) -> Vec<f64> {
apply_with(hues, |d| if d <= 0.0 { d } else { d - 360.0 })
}
pub fn fixup_alpha(alphas: &[f64]) -> Vec<f64> {
let any_defined = alphas.iter().any(|a| !a.is_nan());
if !any_defined {
return alphas.to_vec();
}
alphas
.iter()
.map(|&a| if a.is_nan() { 1.0 } else { a })
.collect()
}