#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct Triple {
pub r: f64,
pub g: f64,
pub b: f64,
}
fn lum(c: Triple) -> f64 {
0.3 * c.r + 0.59 * c.g + 0.11 * c.b
}
fn sat(c: Triple) -> f64 {
let max = c.r.max(c.g).max(c.b);
let min = c.r.min(c.g).min(c.b);
max - min
}
fn clip_color(c: Triple) -> Triple {
let l = lum(c);
let n = c.r.min(c.g).min(c.b);
let x = c.r.max(c.g).max(c.b);
let mut r = c.r;
let mut g = c.g;
let mut b = c.b;
if n < 0.0 {
r = l + ((r - l) * l) / (l - n);
g = l + ((g - l) * l) / (l - n);
b = l + ((b - l) * l) / (l - n);
}
if x > 1.0 {
r = l + ((r - l) * (1.0 - l)) / (x - l);
g = l + ((g - l) * (1.0 - l)) / (x - l);
b = l + ((b - l) * (1.0 - l)) / (x - l);
}
Triple { r, g, b }
}
fn set_lum(c: Triple, l: f64) -> Triple {
let d = l - lum(c);
clip_color(Triple {
r: c.r + d,
g: c.g + d,
b: c.b + d,
})
}
fn set_sat(c: Triple, s: f64) -> Triple {
let mut idx = [(0, c.r), (1, c.g), (2, c.b)];
idx.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
let (min_i, _) = idx[0];
let (mid_i, mid_v) = idx[1];
let (max_i, max_v) = idx[2];
let mut out = [0.0f64; 3];
if max_v > idx[0].1 {
out[mid_i] = (mid_v - idx[0].1) * s / (max_v - idx[0].1);
out[max_i] = s;
} else {
out[mid_i] = 0.0;
out[max_i] = 0.0;
}
out[min_i] = 0.0;
Triple {
r: out[0],
g: out[1],
b: out[2],
}
}
pub(crate) fn hue(b: Triple, s: Triple) -> Triple {
set_lum(set_sat(s, sat(b)), lum(b))
}
pub(crate) fn saturation(b: Triple, s: Triple) -> Triple {
set_lum(set_sat(b, sat(s)), lum(b))
}
pub(crate) fn color(b: Triple, s: Triple) -> Triple {
set_lum(s, lum(b))
}
pub(crate) fn luminosity(b: Triple, s: Triple) -> Triple {
set_lum(b, lum(s))
}
#[cfg(test)]
mod tests {
use super::*;
fn t(r: f64, g: f64, b: f64) -> Triple {
Triple { r, g, b }
}
#[test]
fn lum_pure_green_uses_spec_weights() {
assert!((lum(t(0.0, 1.0, 0.0)) - 0.59).abs() < 1e-15);
}
#[test]
fn sat_full_red_is_one() {
assert!((sat(t(1.0, 0.0, 0.0)) - 1.0).abs() < 1e-15);
}
#[test]
fn set_sat_grey_stays_grey() {
let out = set_sat(t(0.5, 0.5, 0.5), 0.7);
assert_eq!(out, t(0.0, 0.0, 0.0));
}
}