use crate::{style::Rgb, Vec2, XY};
pub trait Interpolator {
fn interpolate(&self, pos: Vec2, size: Vec2) -> Rgb<f32>;
}
pub type Dynterpolator = Box<dyn Interpolator + Send + Sync>;
impl Interpolator for Dynterpolator {
fn interpolate(&self, pos: Vec2, size: Vec2) -> Rgb<f32> {
(**self).interpolate(pos, size)
}
}
pub struct Linear {
points: Vec<(f32, Rgb<f32>)>,
}
fn sort_points(points: &mut [(f32, Rgb<f32>)]) {
points.sort_by(|(time_a, _), (time_b, _)| time_a.partial_cmp(time_b).unwrap());
}
impl Linear {
pub fn new(mut points: Vec<(f32, Rgb<f32>)>) -> Self {
sort_points(&mut points);
Self { points }
}
pub fn simple<S, E>(start: S, end: E) -> Self
where
S: Into<Rgb<f32>>,
E: Into<Rgb<f32>>,
{
let start = start.into();
let end = end.into();
Self::evenly_spaced(&[start, end])
}
pub fn mirror(mut self) -> Self {
self.rescale(|t| 1.0 - t);
self
}
pub fn normalize(&mut self) {
if self.points.is_empty() {
return;
}
if self.points.len() == 1 {
self.points[0].0 = 0f32;
return;
}
let start = self.points[0].0;
let end = self.points.last().unwrap().0;
if start == end {
let step = (self.points.len() as f32 - 1f32).recip();
for (i, &mut (ref mut time, _)) in self.points.iter_mut().enumerate() {
*time = step * i as f32;
}
} else {
self.rescale(|x| (x - start) / (end - start));
}
}
pub fn rescale<F>(&mut self, mut f: F)
where
F: FnMut(f32) -> f32,
{
for &mut (ref mut time, _) in &mut self.points {
*time = f(*time);
}
sort_points(&mut self.points);
}
pub fn evenly_spaced<R: Copy + Into<Rgb<f32>>>(colors: &[R]) -> Self {
let step = 1f32 / (colors.len() - 1) as f32;
let colors = colors.iter().copied().map(Into::into).enumerate();
let points = colors.map(|(i, color)| (step * i as f32, color)).collect();
Self { points }
}
pub fn black_to_white() -> Self {
Self::simple(Rgb::black(), Rgb::white())
}
pub fn rainbow() -> Self {
let mut res = Self::new(vec![
(4.0, Rgb::violet().into()),
(4.7, Rgb::blue().into()),
(4.9, Rgb::cyan().into()),
(5.3, Rgb::green().into()),
(5.75, Rgb::yellow().into()),
(6.1, Rgb::orange().into()),
(6.9, Rgb::red().into()),
]);
res.normalize();
res.mirror()
}
pub fn interpolate(&self, x: f32) -> Rgb<f32> {
if self.points.is_empty() {
return Rgb::black().as_f32();
}
if self.points.len() == 1 {
return self.points[0].1;
}
if x <= self.points[0].0 {
return self.points[0].1;
}
let last = self.points.last().unwrap();
if x >= last.0 {
return last.1;
}
let mut last = self.points[0];
for point in self.points() {
if x > point.0 {
last = *point;
continue;
}
let d = point.0 - last.0;
let x = if d == 0f32 { 0f32 } else { (x - last.0) / d };
return Rgb::zip(last.1, point.1).interpolate(x);
}
panic!("X has an invalid value (NaN?): {x:?}");
}
pub fn points(&self) -> &[(f32, Rgb<f32>)] {
&self.points
}
}
impl From<(Rgb<f32>, Rgb<f32>)> for Linear {
fn from((start, end): (Rgb<f32>, Rgb<f32>)) -> Self {
Self::evenly_spaced(&[start, end])
}
}
impl From<[Rgb<f32>; 2]> for Linear {
fn from([start, end]: [Rgb<f32>; 2]) -> Self {
Self::evenly_spaced(&[start, end])
}
}
impl From<(Rgb<u8>, Rgb<u8>)> for Linear {
fn from((start, end): (Rgb<u8>, Rgb<u8>)) -> Self {
Self::evenly_spaced(&[start.as_f32(), end.as_f32()])
}
}
impl From<[Rgb<u8>; 2]> for Linear {
fn from([start, end]: [Rgb<u8>; 2]) -> Self {
Self::evenly_spaced(&[start.as_f32(), end.as_f32()])
}
}
pub struct Radial {
pub center: XY<f32>,
pub gradient: Linear,
}
impl Interpolator for Radial {
fn interpolate(&self, pos: Vec2, size: Vec2) -> Rgb<f32> {
let size_f32 = size.map(|x| x as f32);
let to_corner = self.center.map(|x| 0.5f32 + (x - 0.5f32).abs()) * size_f32;
let max_distance = (to_corner.map(|x| x as isize).sq_norm() as f32)
.sqrt()
.max(1.0);
let center = (self.center * size_f32).map(|x| x as isize);
let sq_dist = (center - pos.signed()).sq_norm();
let dist = (sq_dist as f32).sqrt();
self.gradient.interpolate(dist / max_distance)
}
}
pub struct Angled {
pub angle_rad: f32,
pub gradient: Linear,
}
impl Interpolator for Angled {
fn interpolate(&self, mut pos: Vec2, mut size: Vec2) -> Rgb<f32> {
use std::f32::consts::{FRAC_PI_2, PI, TAU};
let mut angle = self.angle_rad;
while angle < 0f32 {
angle += TAU;
}
while angle >= TAU {
angle -= TAU;
}
match angle {
_ if angle < FRAC_PI_2 => (),
_ if angle < PI => {
pos = Vec2::new(size.y - pos.y, pos.x);
size = size.swap();
angle -= FRAC_PI_2;
}
_ if angle < PI + FRAC_PI_2 => {
pos = size - pos;
angle -= PI;
}
_ => {
pos = Vec2::new(pos.y, size.x - pos.x);
size = size.swap();
angle -= PI + FRAC_PI_2;
}
}
let d = pos.map(|x| x as f32).rotated(angle).y;
let max = size.map(|x| x as f32).rotated(angle).y.max(1.0);
self.gradient.interpolate(d / max)
}
}
pub struct Bilinear {
pub top_left: Rgb<f32>,
pub bottom_left: Rgb<f32>,
pub top_right: Rgb<f32>,
pub bottom_right: Rgb<f32>,
}
impl Interpolator for Bilinear {
fn interpolate(&self, pos: Vec2, size: Vec2) -> Rgb<f32> {
if !Vec2::new(2, 2).fits_in(size) {
return self.top_left;
}
let pos = pos.map(|x| x as f32) / size.map(|x| (x - 1) as f32);
let top = Rgb::zip(self.top_left, self.top_right).interpolate(pos.x);
let bottom = Rgb::zip(self.bottom_left, self.bottom_right).interpolate(pos.x);
Rgb::zip(top, bottom).interpolate(pos.y)
}
}