use cgmath::num_traits::ToPrimitive;
use std::cmp::Ordering;
use std::ops::{Add, Mul};
#[derive(Copy, Clone, Debug)]
pub struct RgbColor<T = f32> {
pub r: T,
pub g: T,
pub b: T,
}
impl<T> RgbColor<T> {
pub const fn new(r: T, g: T, b: T) -> Self {
Self { r, g, b }
}
}
impl Mul<f32> for RgbColor<f32> {
type Output = RgbColor<f32>;
fn mul(self, rhs: f32) -> Self::Output {
RgbColor::new(self.r * rhs, self.g * rhs, self.b * rhs)
}
}
#[cfg(feature = "egui")]
impl From<egui::Rgba> for RgbColor {
fn from(value: egui::Rgba) -> Self {
RgbColor {
r: value.r(),
g: value.g(),
b: value.b(),
}
}
}
#[cfg(feature = "egui")]
impl From<RgbColor> for egui::Rgba {
fn from(value: RgbColor) -> egui::Rgba {
egui::Rgba::from_rgb(value.r, value.g, value.b)
}
}
#[cfg(feature = "egui")]
impl From<RgbColor> for egui::ecolor::Hsva {
fn from(value: RgbColor) -> egui::ecolor::Hsva {
Into::<egui::Rgba>::into(value).into()
}
}
#[cfg(feature = "egui")]
impl From<egui::ecolor::Hsva> for RgbColor {
fn from(value: egui::ecolor::Hsva) -> Self {
Into::<egui::Rgba>::into(value).into()
}
}
impl Add<RgbColor<f32>> for RgbColor<f32> {
type Output = RgbColor<f32>;
fn add(self, rhs: RgbColor<f32>) -> Self::Output {
RgbColor::new(self.r + rhs.r, self.g + rhs.g, self.b + rhs.b)
}
}
impl RgbColor {
pub const BLACK: RgbColor = RgbColor::new(0., 0., 0.);
pub const WHITE: RgbColor = RgbColor::new(1., 1., 1.);
pub const RED: RgbColor = RgbColor::new(1., 0., 0.);
pub const GREEN: RgbColor = RgbColor::new(0., 1., 0.);
pub const BLUE: RgbColor = RgbColor::new(0., 0., 1.);
}
impl From<RgbColor<f32>> for RgbColor<u8> {
fn from(value: RgbColor<f32>) -> Self {
RgbColor {
r: (value.r * 255.).round().clamp(0., 255.).to_u8().unwrap(),
g: (value.g * 255.).round().clamp(0., 255.).to_u8().unwrap(),
b: (value.b * 255.).round().clamp(0., 255.).to_u8().unwrap(),
}
}
}
impl From<[f32; 3]> for RgbColor {
fn from([r, g, b]: [f32; 3]) -> Self {
RgbColor::new(r, g, b)
}
}
#[derive(Debug)]
pub struct Gradient {
stops: Vec<(f32, RgbColor)>,
}
impl Gradient {
pub fn new(stops: impl IntoIterator<Item = (f32, impl Into<RgbColor>)>) -> Self {
let mut gradient = Gradient {
stops: stops.into_iter().map(|(k, v)| (k, v.into())).collect(),
};
gradient.sort();
gradient
}
fn sort(&mut self) {
self.stops
.sort_by(|(a, _), (b, _)| a.partial_cmp(b).unwrap())
}
fn bisect(&self, x: f32) -> Option<usize> {
let mut lo = 0;
let mut hi = self.stops.len();
while lo < hi {
let mid = (lo + hi) / 2;
match self.stops[mid].0.partial_cmp(&x)? {
Ordering::Less => lo = mid + 1,
Ordering::Equal => lo = mid + 1,
Ordering::Greater => hi = mid,
}
}
Some(lo)
}
fn sample_at(&self, x: f32) -> Option<RgbColor> {
let insertion_point = self.bisect(x)?;
Some(match insertion_point {
0 => self.stops.first()?.1,
n if n == self.stops.len() => self.stops.last()?.1,
n => {
let (t0, c0) = *self.stops.get(n - 1)?;
let (t1, c1) = *self.stops.get(n)?;
c0 + (c1 + c0 * -1.0_f32) * ((x - t0) / (t1 - t0))
}
})
}
pub fn linear_eval(&self, n: usize) -> Vec<RgbColor> {
(0..n)
.map(|idx| (idx as f32) / (n - 1) as f32)
.map(|t| self.sample_at(t).unwrap())
.collect()
}
}
impl IntoIterator for Gradient {
type Item = (f32, RgbColor);
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.stops.into_iter()
}
}
impl<'a> IntoIterator for &'a Gradient {
type Item = (f32, RgbColor);
type IntoIter = std::iter::Copied<std::slice::Iter<'a, Self::Item>>;
fn into_iter(self) -> Self::IntoIter {
self.stops.iter().copied()
}
}
#[cfg(feature = "egui")]
impl From<&egui_colorgradient::Gradient> for Gradient {
fn from(value: &egui_colorgradient::Gradient) -> Self {
Self::new(value.stops.iter().copied())
}
}