1use libm::powf;
4
5use crate::rgb::RGB;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub struct Gradient {
12 from: RGB,
13 to: RGB,
14 steps: usize,
15}
16
17impl Gradient {
18 pub fn new(from: RGB, to: RGB, steps: usize) -> Self {
20 Self { from, to, steps }
21 }
22}
23
24impl IntoIterator for Gradient {
25 type Item = RGB;
26 type IntoIter = GradientIter;
27
28 fn into_iter(self) -> Self::IntoIter {
29 GradientIter {
30 gradient: self,
31 i: 0,
32 }
33 }
34}
35
36pub struct GradientIter {
38 gradient: Gradient,
39 i: usize,
40}
41
42impl Iterator for GradientIter {
43 type Item = RGB;
44
45 fn next(&mut self) -> Option<Self::Item> {
46 if self.i == self.gradient.steps {
47 return None;
48 }
49
50 let mut mix = self.i as f32 / (self.gradient.steps - 1) as f32;
51 if mix.is_nan() {
52 mix = 0.0;
53 }
54
55 self.i += 1;
56
57 let color = mix_color(self.gradient.from, self.gradient.to, mix);
58
59 Some(color)
60 }
61}
62
63fn mix_color(c1: RGB, c2: RGB, mix: f32) -> RGB {
68 let c1 = normalize_rgb(c1);
70 let c2 = normalize_rgb(c2);
71
72 let c1 = srgb_inverse_companding(c1);
74 let c2 = srgb_inverse_companding(c2);
75
76 let mut c = rgb_linear_interpolation(c1, c2, mix);
77
78 if c.r + c.g + c.b != 0.0 {
80 let gamma = 0.43;
82 let c1_bright = rgb_brightness(c1, gamma);
83 let c2_bright = rgb_brightness(c2, gamma);
84
85 let brightness = linear_interpolation(c1_bright, c2_bright, mix);
87 let intensity = powf(brightness, 1.0 / gamma);
88
89 let factor = intensity / (c.r + c.g + c.b);
90
91 c = RGB {
92 r: c.r * factor,
93 g: c.g * factor,
94 b: c.b * factor,
95 };
96 }
97
98 let c = srgb_companding(c);
100
101 normalize_back_rgb(c)
102}
103
104fn srgb_inverse_companding(c: RGB<f32>) -> RGB<f32> {
106 RGB {
107 r: srgb_inverse_color(c.r),
108 b: srgb_inverse_color(c.b),
109 g: srgb_inverse_color(c.g),
110 }
111}
112
113fn srgb_inverse_color(c: f32) -> f32 {
114 if c > 0.04045 {
115 powf((c + 0.055) / 1.055, 2.4)
116 } else {
117 c / 12.92
118 }
119}
120
121fn normalize_rgb(c: RGB) -> RGB<f32> {
122 RGB {
123 r: normalize_color(c.r),
124 g: normalize_color(c.g),
125 b: normalize_color(c.b),
126 }
127}
128
129fn normalize_back_rgb(c: RGB<f32>) -> RGB {
130 RGB {
131 r: (c.r * 255.9999) as u8,
132 g: (c.g * 255.9999) as u8,
133 b: (c.b * 255.9999) as u8,
134 }
135}
136
137fn normalize_color(c: u8) -> f32 {
138 c as f32 / 255.0
139}
140
141fn rgb_linear_interpolation(c1: RGB<f32>, c2: RGB<f32>, mix: f32) -> RGB<f32> {
142 RGB {
143 r: linear_interpolation(c1.r, c2.r, mix),
144 g: linear_interpolation(c1.g, c2.g, mix),
145 b: linear_interpolation(c1.b, c2.b, mix),
146 }
147}
148
149fn linear_interpolation(c1: f32, c2: f32, frac: f32) -> f32 {
150 (c1 * (1.0 - frac)) + (c2 * frac)
151}
152
153fn srgb_companding(c: RGB<f32>) -> RGB<f32> {
154 RGB {
155 r: srgb_apply_companding_color(c.r),
156 g: srgb_apply_companding_color(c.g),
157 b: srgb_apply_companding_color(c.b),
158 }
159}
160
161fn srgb_apply_companding_color(c: f32) -> f32 {
162 if c > 0.0031308 {
163 1.055 * powf(c, 1.0 / 2.4) - 0.055
164 } else {
165 c * 12.92
166 }
167}
168
169fn rgb_brightness(c: RGB<f32>, gamma: f32) -> f32 {
170 powf(c.r + c.g + c.b, gamma)
171}
172
173#[cfg(test)]
174mod tests {
175 use super::{mix_color, Gradient, RGB};
176
177 #[test]
178 fn mix_color_test() {
179 assert_eq!(
180 mix_color(RGB::new(0, 0, 0), RGB::new(255, 255, 255), 0.50),
181 RGB::new(123, 123, 123),
182 );
183 assert_eq!(
184 mix_color(RGB::new(0, 0, 0), RGB::new(255, 255, 255), 0.25),
185 RGB::new(56, 56, 56),
186 );
187 assert_eq!(
188 mix_color(RGB::new(0, 0, 0), RGB::new(255, 255, 255), 0.1),
189 RGB::new(14, 14, 14),
190 );
191 assert_eq!(
192 mix_color(RGB::new(0, 0, 0), RGB::new(255, 255, 255), 1.0),
193 RGB::new(255, 255, 255),
194 );
195 assert_eq!(
196 mix_color(RGB::new(0, 0, 0), RGB::new(255, 255, 255), 0.0),
197 RGB::new(0, 0, 0),
198 );
199 }
200
201 #[test]
202 fn gradient_test() {
203 test_gradient(
204 Gradient::new(RGB::new(0, 0, 0), RGB::new(255, 255, 255), 0).into_iter(),
205 &[],
206 );
207 test_gradient(
208 Gradient::new(RGB::new(0, 0, 0), RGB::new(255, 255, 255), 1).into_iter(),
209 &[RGB::new(0, 0, 0)],
210 );
211 test_gradient(
212 Gradient::new(RGB::new(0, 0, 0), RGB::new(255, 255, 255), 2).into_iter(),
213 &[RGB::new(0, 0, 0), RGB::new(255, 255, 255)],
214 );
215 test_gradient(
216 Gradient::new(RGB::new(0, 0, 0), RGB::new(255, 255, 255), 3).into_iter(),
217 &[
218 RGB::new(0, 0, 0),
219 RGB::new(123, 123, 123),
220 RGB::new(255, 255, 255),
221 ],
222 );
223 }
224
225 fn test_gradient(mut iter: impl Iterator<Item = RGB>, expected: &[RGB]) {
226 for rgb in expected {
227 let got = iter.next().unwrap();
228 assert_eq!(got, *rgb);
229 }
230
231 assert!(iter.next().is_none());
232 }
233}