feat(palette): add palette expansion via color interpolation

Add `Color::euclidian_distance` and `Rgb::lerp`, then use them in a new
`Palette<T>` type whose `expand` method adds colors between close
pairs. Split `palette.rs` into a module, with `NORD_PALETTE` in its own
file.
This commit is contained in:
2026-08-28 13:33:52 +02:00
parent 2ecc34075f
commit e93bc547f2
4 changed files with 229 additions and 23 deletions
+56 -3
View File
@@ -1,7 +1,14 @@
use std::sync::LazyLock;
use wide::f64x4;
pub trait Color {}
pub trait Color {
/// Computes the Euclidean distance between `self` and `other`.
///
/// This method finds the difference of each component, squares each
/// difference, sums the squares, then takes the square root of the
/// sum.
fn euclidian_distance(&self, other: &Self) -> f64;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Rgb {
@@ -10,7 +17,36 @@ pub struct Rgb {
pub b: u8,
}
impl Color for Rgb {}
impl Color for Rgb {
fn euclidian_distance(&self, other: &Self) -> f64 {
let diff_r = (self.r as f64) - (other.r as f64);
let diff_g = (self.g as f64) - (other.g as f64);
let diff_b = (self.b as f64) - (other.b as f64);
(diff_r * diff_r + diff_g * diff_g + diff_b * diff_b).sqrt()
}
}
impl Rgb {
/// Blends `self` and `other` by `alpha`.
///
/// This method finds a color between `self` and `other`. An `alpha`
/// of `0.0` returns `self`. An `alpha` of `1.0` returns `other`. A
/// value between `0.0` and `1.0` mixes the two colors in that
/// proportion.
///
/// The name `lerp` is short for "linear interpolation," a common term
/// in graphics code.
pub fn lerp(&self, other: &Rgb, alpha: f64) -> Rgb {
let r = (1.0 - alpha) * self.r as f64 + alpha * other.r as f64;
let g = (1.0 - alpha) * self.g as f64 + alpha * other.g as f64;
let b = (1.0 - alpha) * self.b as f64 + alpha * other.b as f64;
Rgb {
r: r as u8,
g: g as u8,
b: b as u8,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Lab {
@@ -19,7 +55,14 @@ pub struct Lab {
pub b: f64,
}
impl Color for Lab {}
impl Color for Lab {
fn euclidian_distance(&self, other: &Self) -> f64 {
let diff_l = self.l - other.l;
let diff_a = self.a - other.a;
let diff_b = self.b - other.b;
(diff_l * diff_l + diff_a * diff_a + diff_b * diff_b).sqrt()
}
}
/// Constants for the sRGB transfer function. `srgb_to_linear` uses these
/// to convert a normalized (`0.0..=1.0`) sRGB channel value to linear-light
@@ -264,6 +307,16 @@ impl RgbPlanar {
}
}
impl From<&[Rgb]> for RgbPlanar {
fn from(value: &[Rgb]) -> Self {
// let collected: Vec<(u8, u8, u8)> = value.iter().map(|rgb| (rgb.r, rgb.g, rgb.b)).collect();
let r: Vec<f64> = value.iter().map(|rgb| rgb.r as f64).collect();
let g: Vec<f64> = value.iter().map(|rgb| rgb.g as f64).collect();
let b: Vec<f64> = value.iter().map(|rgb| rgb.b as f64).collect();
Self { r, g, b }
}
}
impl PlanarColor<Rgb> for RgbPlanar {
fn len(&self) -> usize {
self.r.len()