egui_colorgradient/
gradient.rs1use egui::epaint::{Color32, Hsva, Rgba};
2use std::cmp::Ordering;
3use std::fmt::{Display, Formatter};
4
5#[derive(Copy, Clone, Debug, PartialEq)]
7pub enum InterpolationMethod {
8 Constant,
11 Linear,
14}
15
16impl Display for InterpolationMethod {
17 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
18 write!(
19 f,
20 "{}",
21 match self {
22 Self::Linear => "linear",
23 Self::Constant => "constant",
24 }
25 )
26 }
27}
28
29pub struct ColorInterpolator {
31 method: InterpolationMethod,
32 keys: Vec<(f32, Rgba)>,
33}
34
35impl ColorInterpolator {
36 fn new(
37 keys: impl IntoIterator<Item = (f32, impl Into<Rgba>)>,
38 method: InterpolationMethod,
39 ) -> Self {
40 let keys: Vec<_> = keys.into_iter().map(|(k, v)| (k, v.into())).collect();
41 let mut result = Self { keys, method };
42 result.sort();
43 result
44 }
45
46 fn sort(&mut self) {
47 self.keys
48 .sort_by(|(a, _), (b, _)| a.partial_cmp(b).unwrap());
49 }
50
51 fn bisect(&self, x: f32) -> Option<usize> {
53 let mut lo = 0;
54 let mut hi = self.keys.len();
55 while lo < hi {
56 let mid = (lo + hi) / 2;
57 match self.keys[mid].0.partial_cmp(&x)? {
58 Ordering::Less => lo = mid + 1,
59 Ordering::Equal => lo = mid + 1,
60 Ordering::Greater => hi = mid,
61 }
62 }
63
64 Some(lo)
65 }
66
67 pub fn sample_at(&self, x: f32) -> Option<Rgba> {
71 Some(match self.method {
72 InterpolationMethod::Constant => {
73 let insertion_point = self.bisect(x)?;
74 match insertion_point {
75 0 => self.keys.first()?.1,
76 n => self.keys.get(n - 1)?.1,
77 }
78 }
79 InterpolationMethod::Linear => {
80 let insertion_point = self.bisect(x)?;
81 match insertion_point {
82 0 => self.keys.first()?.1,
83 n if n == self.keys.len() => self.keys.last()?.1,
84 n => {
85 let (t0, c0) = *self.keys.get(n - 1)?;
86 let (t1, c1) = *self.keys.get(n)?;
87
88 c0 + (c1 + c0 * -1.0_f32) * ((x - t0) / (t1 - t0))
89 }
90 }
91 }
92 })
93 }
94}
95
96fn argsort_by<T, F>(data: &[T], mut f: F) -> Vec<usize>
97where
98 F: FnMut(T, T) -> Ordering,
99 T: Copy,
100{
101 let mut indices = (0..data.len()).collect::<Vec<_>>();
102 indices.sort_by(|&a, &b| f(data[a], data[b]));
103 indices
104}
105
106pub struct Gradient {
108 pub stops: Vec<(f32, Hsva)>,
109 pub interpolation_method: InterpolationMethod,
110}
111
112impl Gradient {
113 pub fn new(
115 interpolation_method: InterpolationMethod,
116 stops: impl IntoIterator<Item = (f32, impl Into<Hsva>)>,
117 ) -> Self {
118 Self {
119 interpolation_method,
120 stops: stops.into_iter().map(|(k, v)| (k, v.into())).collect(),
121 }
122 }
123
124 pub fn interpolator(&self) -> ColorInterpolator {
126 ColorInterpolator::new(self.stops.iter().copied(), self.interpolation_method)
127 }
128
129 pub fn interpolator_opaque(&self) -> ColorInterpolator {
132 ColorInterpolator::new(
133 self.stops.iter().map(|(t, c)| (*t, c.to_opaque())),
134 self.interpolation_method,
135 )
136 }
137
138 pub fn argsort(&self) -> Vec<usize> {
142 argsort_by(&self.stops, |(a, _), (b, _)| a.partial_cmp(&b).unwrap())
143 }
144
145 pub fn sort(&mut self) {
147 self.stops
148 .sort_by(|(a, _), (b, _)| a.partial_cmp(b).unwrap())
149 }
150
151 pub fn linear_eval(&self, n: usize, opaque: bool) -> Vec<Color32> {
161 let interp = match opaque {
162 false => self.interpolator(),
163 true => self.interpolator_opaque(),
164 };
165 (0..n)
166 .map(|idx| (idx as f32) / (n - 1) as f32)
167 .map(|t| interp.sample_at(t).unwrap().into())
168 .collect()
169 }
170}
171
172impl Default for Gradient {
173 fn default() -> Self {
174 Self {
175 stops: vec![(0., Color32::BLACK.into()), (1., Color32::WHITE.into())],
176 interpolation_method: InterpolationMethod::Linear,
177 }
178 }
179}