1use cgmath::num_traits::ToPrimitive;
7use std::cmp::Ordering;
8use std::ops::{Add, Mul};
9
10#[derive(Copy, Clone, Debug)]
12pub struct RgbColor<T = f32> {
13 pub r: T,
15 pub g: T,
17 pub b: T,
19}
20
21impl<T> RgbColor<T> {
22 pub const fn new(r: T, g: T, b: T) -> Self {
39 Self { r, g, b }
40 }
41}
42
43impl Mul<f32> for RgbColor<f32> {
44 type Output = RgbColor<f32>;
45 fn mul(self, rhs: f32) -> Self::Output {
46 RgbColor::new(self.r * rhs, self.g * rhs, self.b * rhs)
47 }
48}
49
50#[cfg(feature = "egui")]
51impl From<egui::Rgba> for RgbColor {
52 fn from(value: egui::Rgba) -> Self {
53 RgbColor {
54 r: value.r(),
55 g: value.g(),
56 b: value.b(),
57 }
58 }
59}
60
61#[cfg(feature = "egui")]
62impl From<RgbColor> for egui::Rgba {
63 fn from(value: RgbColor) -> egui::Rgba {
64 egui::Rgba::from_rgb(value.r, value.g, value.b)
65 }
66}
67
68#[cfg(feature = "egui")]
69impl From<RgbColor> for egui::ecolor::Hsva {
70 fn from(value: RgbColor) -> egui::ecolor::Hsva {
71 Into::<egui::Rgba>::into(value).into()
72 }
73}
74
75#[cfg(feature = "egui")]
76impl From<egui::ecolor::Hsva> for RgbColor {
77 fn from(value: egui::ecolor::Hsva) -> Self {
78 Into::<egui::Rgba>::into(value).into()
79 }
80}
81
82impl Add<RgbColor<f32>> for RgbColor<f32> {
83 type Output = RgbColor<f32>;
84 fn add(self, rhs: RgbColor<f32>) -> Self::Output {
85 RgbColor::new(self.r + rhs.r, self.g + rhs.g, self.b + rhs.b)
86 }
87}
88
89impl RgbColor {
90 pub const BLACK: RgbColor = RgbColor::new(0., 0., 0.);
91 pub const WHITE: RgbColor = RgbColor::new(1., 1., 1.);
92 pub const RED: RgbColor = RgbColor::new(1., 0., 0.);
93 pub const GREEN: RgbColor = RgbColor::new(0., 1., 0.);
94 pub const BLUE: RgbColor = RgbColor::new(0., 0., 1.);
95}
96
97impl From<RgbColor<f32>> for RgbColor<u8> {
98 fn from(value: RgbColor<f32>) -> Self {
99 RgbColor {
100 r: (value.r * 255.).round().clamp(0., 255.).to_u8().unwrap(),
101 g: (value.g * 255.).round().clamp(0., 255.).to_u8().unwrap(),
102 b: (value.b * 255.).round().clamp(0., 255.).to_u8().unwrap(),
103 }
104 }
105}
106
107impl From<[f32; 3]> for RgbColor {
108 fn from([r, g, b]: [f32; 3]) -> Self {
109 RgbColor::new(r, g, b)
110 }
111}
112
113#[derive(Debug)]
133pub struct Gradient {
134 stops: Vec<(f32, RgbColor)>,
135}
136
137impl Gradient {
138 pub fn new(stops: impl IntoIterator<Item = (f32, impl Into<RgbColor>)>) -> Self {
160 let mut gradient = Gradient {
161 stops: stops.into_iter().map(|(k, v)| (k, v.into())).collect(),
162 };
163 gradient.sort();
164 gradient
165 }
166
167 fn sort(&mut self) {
169 self.stops
170 .sort_by(|(a, _), (b, _)| a.partial_cmp(b).unwrap())
171 }
172
173 fn bisect(&self, x: f32) -> Option<usize> {
175 let mut lo = 0;
176 let mut hi = self.stops.len();
177 while lo < hi {
178 let mid = (lo + hi) / 2;
179 match self.stops[mid].0.partial_cmp(&x)? {
180 Ordering::Less => lo = mid + 1,
181 Ordering::Equal => lo = mid + 1,
182 Ordering::Greater => hi = mid,
183 }
184 }
185
186 Some(lo)
187 }
188
189 fn sample_at(&self, x: f32) -> Option<RgbColor> {
193 let insertion_point = self.bisect(x)?;
194 Some(match insertion_point {
195 0 => self.stops.first()?.1,
196 n if n == self.stops.len() => self.stops.last()?.1,
197 n => {
198 let (t0, c0) = *self.stops.get(n - 1)?;
199 let (t1, c1) = *self.stops.get(n)?;
200
201 c0 + (c1 + c0 * -1.0_f32) * ((x - t0) / (t1 - t0))
202 }
203 })
204 }
205
206 pub fn linear_eval(&self, n: usize) -> Vec<RgbColor> {
233 (0..n)
234 .map(|idx| (idx as f32) / (n - 1) as f32)
235 .map(|t| self.sample_at(t).unwrap())
236 .collect()
237 }
238}
239
240impl IntoIterator for Gradient {
241 type Item = (f32, RgbColor);
242 type IntoIter = std::vec::IntoIter<Self::Item>;
243 fn into_iter(self) -> Self::IntoIter {
244 self.stops.into_iter()
245 }
246}
247
248impl<'a> IntoIterator for &'a Gradient {
249 type Item = (f32, RgbColor);
250 type IntoIter = std::iter::Copied<std::slice::Iter<'a, Self::Item>>;
251 fn into_iter(self) -> Self::IntoIter {
252 self.stops.iter().copied()
253 }
254}
255
256#[cfg(feature = "egui")]
257impl From<&egui_colorgradient::Gradient> for Gradient {
258 fn from(value: &egui_colorgradient::Gradient) -> Self {
259 Self::new(value.stops.iter().copied())
260 }
261}