Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::charts_color::Rgb; ← from charts.color ^1.0.0 · built alongside by fune
use super::charts_color_linear_to_srgb::linear_to_srgb;
use super::charts_color_parse_hex::parse_hex;
use super::charts_color_srgb_to_linear::srgb_to_linear;
use super::charts_color_to_hex::to_hex;
use super::math_pow::pow; ← from math.pow ^1.0.0 · built alongside by fune
// Björn Ottosson's matrices, "A perceptual color space for image processing"
// (2020), https://bottosson.github.io/posts/oklab/, applied to linear sRGB.
fn to_oklab(hex: &str) -> [f64; 3] {
let c = parse_hex(hex);
let r = srgb_to_linear(c.r);
let g = srgb_to_linear(c.g);
let b = srgb_to_linear(c.b);
let l = pow(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b, 1.0 / 3.0);
let m = pow(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b, 1.0 / 3.0);
let s = pow(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b, 1.0 / 3.0);
[
0.2104542553 * l + 0.7936177850 * m - 0.0040720468 * s,
1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s,
0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s,
]
}
fn from_oklab(lab: [f64; 3]) -> String {
let [cl, ca, cb] = lab;
let l0 = cl + 0.3963377774 * ca + 0.2158037573 * cb;
let m0 = cl - 0.1055613458 * ca - 0.0638541728 * cb;
let s0 = cl - 0.0894841775 * ca - 1.2914855480 * cb;
let l = l0 * l0 * l0;
let m = m0 * m0 * m0;
let s = s0 * s0 * s0;
to_hex(&Rgb {
r: linear_to_srgb(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s),
g: linear_to_srgb(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s),
b: linear_to_srgb(-0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s),
})
}
/// The colour a fraction t of the way along a ramp of evenly spaced stops,
/// mixed in OKLab so equal steps look equal.
///
/// # Panics
/// Panics on fewer than 2 stops, a bad hex colour, or t outside 0..=1.
pub fn interpolate_color(ramp: &[String], t: f64) -> String {
if ramp.len() < 2 {
panic!("ramp needs at least 2 colours, received {}", ramp.len());
}
if !t.is_finite() || t < 0.0 || t > 1.0 {
panic!("t must be between 0 and 1, received {}", t);
}
let position = t * (ramp.len() - 1) as f64;
let mut i = position.floor() as usize;
if i > ramp.len() - 2 {
i = ramp.len() - 2;
}
let u = position - i as f64;
let a = to_oklab(&ramp[i]);
let b = to_oklab(&ramp[i + 1]);
from_oklab([a[0] + (b[0] - a[0]) * u, a[1] + (b[1] - a[1]) * u, a[2] + (b[2] - a[2]) * u])
}
pub fn fune_vector(args: &[Value]) -> Value {
let ramp: Vec<String> = args[0].as_arr().iter().map(|v| v.as_str().to_string()).collect();
Value::str(&interpolate_color(&ramp, args[1].as_f64()))
}