use egui::epaint::{Color32, Hsva, Rgba};
use std::cmp::Ordering;
use std::fmt::{Display, Formatter};
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum InterpolationMethod {
Constant,
Linear,
}
impl Display for InterpolationMethod {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Linear => "linear",
Self::Constant => "constant",
}
)
}
}
pub struct ColorInterpolator {
method: InterpolationMethod,
keys: Vec<(f32, Rgba)>,
}
impl ColorInterpolator {
fn new(
keys: impl IntoIterator<Item = (f32, impl Into<Rgba>)>,
method: InterpolationMethod,
) -> Self {
let keys: Vec<_> = keys.into_iter().map(|(k, v)| (k, v.into())).collect();
let mut result = Self { keys, method };
result.sort();
result
}
fn sort(&mut self) {
self.keys
.sort_by(|(a, _), (b, _)| a.partial_cmp(b).unwrap());
}
fn bisect(&self, x: f32) -> Option<usize> {
let mut lo = 0;
let mut hi = self.keys.len();
while lo < hi {
let mid = (lo + hi) / 2;
match self.keys[mid].0.partial_cmp(&x)? {
Ordering::Less => lo = mid + 1,
Ordering::Equal => lo = mid + 1,
Ordering::Greater => hi = mid,
}
}
Some(lo)
}
pub fn sample_at(&self, x: f32) -> Option<Rgba> {
Some(match self.method {
InterpolationMethod::Constant => {
let insertion_point = self.bisect(x)?;
match insertion_point {
0 => self.keys.first()?.1,
n => self.keys.get(n - 1)?.1,
}
}
InterpolationMethod::Linear => {
let insertion_point = self.bisect(x)?;
match insertion_point {
0 => self.keys.first()?.1,
n if n == self.keys.len() => self.keys.last()?.1,
n => {
let (t0, c0) = *self.keys.get(n - 1)?;
let (t1, c1) = *self.keys.get(n)?;
c0 + (c1 + c0 * -1.0_f32) * ((x - t0) / (t1 - t0))
}
}
}
})
}
}
fn argsort_by<T, F>(data: &[T], mut f: F) -> Vec<usize>
where
F: FnMut(T, T) -> Ordering,
T: Copy,
{
let mut indices = (0..data.len()).collect::<Vec<_>>();
indices.sort_by(|&a, &b| f(data[a], data[b]));
indices
}
pub struct Gradient {
pub stops: Vec<(f32, Hsva)>,
pub interpolation_method: InterpolationMethod,
}
impl Gradient {
pub fn new(
interpolation_method: InterpolationMethod,
stops: impl IntoIterator<Item = (f32, impl Into<Hsva>)>,
) -> Self {
Self {
interpolation_method,
stops: stops.into_iter().map(|(k, v)| (k, v.into())).collect(),
}
}
pub fn interpolator(&self) -> ColorInterpolator {
ColorInterpolator::new(self.stops.iter().copied(), self.interpolation_method)
}
pub fn interpolator_opaque(&self) -> ColorInterpolator {
ColorInterpolator::new(
self.stops.iter().map(|(t, c)| (*t, c.to_opaque())),
self.interpolation_method,
)
}
pub fn argsort(&self) -> Vec<usize> {
argsort_by(&self.stops, |(a, _), (b, _)| a.partial_cmp(&b).unwrap())
}
pub fn sort(&mut self) {
self.stops
.sort_by(|(a, _), (b, _)| a.partial_cmp(b).unwrap())
}
pub fn linear_eval(&self, n: usize, opaque: bool) -> Vec<Color32> {
let interp = match opaque {
false => self.interpolator(),
true => self.interpolator_opaque(),
};
(0..n)
.map(|idx| (idx as f32) / (n - 1) as f32)
.map(|t| interp.sample_at(t).unwrap().into())
.collect()
}
}
impl Default for Gradient {
fn default() -> Self {
Self {
stops: vec![(0., Color32::BLACK.into()), (1., Color32::WHITE.into())],
interpolation_method: InterpolationMethod::Linear,
}
}
}