1use crate::color::Color;
2use crate::math::Vector2;
3
4pub trait Lerp {
6 fn lerp(self, other: Self, t: f32) -> Self;
7}
8
9#[inline]
10fn clamp01(t: f32) -> f32 {
11 t.clamp(0.0, 1.0)
12}
13
14impl Lerp for f32 {
15 #[inline]
16 fn lerp(self, other: Self, t: f32) -> Self {
17 let t = clamp01(t);
18 if self == other {
19 return self;
20 }
21 self + (other - self) * t
22 }
23}
24
25impl Lerp for u16 {
26 #[inline]
27 fn lerp(self, other: Self, t: f32) -> Self {
28 let t = clamp01(t);
29 if self == other {
30 return self;
31 }
32 let v = (self as f32)
33 .lerp(other as f32, t)
34 .round()
35 .clamp(u16::MIN as f32, u16::MAX as f32);
36 v as u16
37 }
38}
39
40impl Lerp for Vector2 {
41 #[inline]
42 fn lerp(self, other: Self, t: f32) -> Self {
43 let t = clamp01(t);
44 if self == other {
45 return self;
46 }
47 Self {
48 x: self.x.lerp(other.x, t),
49 y: self.y.lerp(other.y, t),
50 }
51 }
52}
53
54impl Lerp for macroquad::prelude::Vec2 {
55 #[inline]
56 fn lerp(self, other: Self, t: f32) -> Self {
57 let t = clamp01(t);
58 if self == other {
59 return self;
60 }
61 Self::new(
62 self.x.lerp(other.x, t),
63 self.y.lerp(other.y, t),
64 )
65 }
66}
67
68impl Lerp for (f32, f32, f32, f32) {
69 #[inline]
70 fn lerp(self, other: Self, t: f32) -> Self {
71 let t = clamp01(t);
72 if self == other {
73 return self;
74 }
75 (
76 self.0.lerp(other.0, t),
77 self.1.lerp(other.1, t),
78 self.2.lerp(other.2, t),
79 self.3.lerp(other.3, t),
80 )
81 }
82}
83
84impl Lerp for (u16, u16, u16, u16) {
85 #[inline]
86 fn lerp(self, other: Self, t: f32) -> Self {
87 let t = clamp01(t);
88 if self == other {
89 return self;
90 }
91 (
92 self.0.lerp(other.0, t),
93 self.1.lerp(other.1, t),
94 self.2.lerp(other.2, t),
95 self.3.lerp(other.3, t),
96 )
97 }
98}
99
100impl Lerp for Color {
101 #[inline]
102 fn lerp(self, other: Self, t: f32) -> Self {
103 let t = clamp01(t);
104 if self == other {
105 return self;
106 }
107 Color {
108 r: self.r.lerp(other.r, t),
109 g: self.g.lerp(other.g, t),
110 b: self.b.lerp(other.b, t),
111 a: self.a.lerp(other.a, t),
112 }
113 }
114}
115
116impl Color {
117 #[inline]
119 pub fn lerp_srgb(self, other: Self, t: f32) -> Self {
120 if self == other {
121 return self;
122 }
123
124 let t = clamp01(t);
125 if t == 0.0 {
126 return self;
127 }
128 if t == 1.0 {
129 return other;
130 }
131 let a = self.a.lerp(other.a, t);
132
133 let r0 = srgb_to_linear(channel_to_unit(self.r));
134 let g0 = srgb_to_linear(channel_to_unit(self.g));
135 let b0 = srgb_to_linear(channel_to_unit(self.b));
136
137 let r1 = srgb_to_linear(channel_to_unit(other.r));
138 let g1 = srgb_to_linear(channel_to_unit(other.g));
139 let b1 = srgb_to_linear(channel_to_unit(other.b));
140
141 let r = linear_to_srgb(r0.lerp(r1, t));
142 let g = linear_to_srgb(g0.lerp(g1, t));
143 let b = linear_to_srgb(b0.lerp(b1, t));
144
145 Color::rgba(unit_to_channel(r), unit_to_channel(g), unit_to_channel(b), a)
146 }
147
148 #[inline]
150 pub fn lerp_oklab(self, other: Self, t: f32) -> Self {
151 if self == other {
152 return self;
153 }
154
155 let t = clamp01(t);
156 if t == 0.0 {
157 return self;
158 }
159 if t == 1.0 {
160 return other;
161 }
162 let a = self.a.lerp(other.a, t);
163
164 let c0 = rgb_to_oklab(self);
165 let c1 = rgb_to_oklab(other);
166
167 let l = c0.0.lerp(c1.0, t);
168 let a_lab = c0.1.lerp(c1.1, t);
169 let b_lab = c0.2.lerp(c1.2, t);
170
171 let (r, g, b) = oklab_to_rgb(l, a_lab, b_lab);
172 Color::rgba(unit_to_channel(r), unit_to_channel(g), unit_to_channel(b), a)
173 }
174}
175
176#[inline]
177fn channel_to_unit(v: f32) -> f32 {
178 (v / 255.0).clamp(0.0, 1.0)
179}
180
181#[inline]
182fn unit_to_channel(v: f32) -> f32 {
183 (v.clamp(0.0, 1.0) * 255.0).clamp(0.0, 255.0)
184}
185
186#[inline]
187fn srgb_to_linear(v: f32) -> f32 {
188 if v <= 0.04045 {
189 v / 12.92
190 } else {
191 ((v + 0.055) / 1.055).powf(2.4)
192 }
193}
194
195#[inline]
196fn linear_to_srgb(v: f32) -> f32 {
197 if v <= 0.003_130_8 {
198 12.92 * v
199 } else {
200 1.055 * v.powf(1.0 / 2.4) - 0.055
201 }
202}
203
204#[inline]
205fn rgb_to_oklab(color: Color) -> (f32, f32, f32) {
206 let r = srgb_to_linear(channel_to_unit(color.r));
207 let g = srgb_to_linear(channel_to_unit(color.g));
208 let b = srgb_to_linear(channel_to_unit(color.b));
209
210 let l = 0.412_221_46 * r + 0.536_332_55 * g + 0.051_445_995 * b;
211 let m = 0.211_903_5 * r + 0.680_699_5 * g + 0.107_396_96 * b;
212 let s = 0.088_302_46 * r + 0.281_718_85 * g + 0.629_978_7 * b;
213
214 let l_ = l.cbrt();
215 let m_ = m.cbrt();
216 let s_ = s.cbrt();
217
218 (
219 0.210_454_26 * l_ + 0.793_617_8 * m_ - 0.004_072_047 * s_,
220 1.977_998_5 * l_ - 2.428_592_2 * m_ + 0.450_593_7 * s_,
221 0.025_904_037 * l_ + 0.782_771_77 * m_ - 0.808_675_77 * s_,
222 )
223}
224
225#[inline]
226fn oklab_to_rgb(l: f32, a: f32, b: f32) -> (f32, f32, f32) {
227 let l_ = l + 0.396_337_78 * a + 0.215_803_76 * b;
228 let m_ = l - 0.105_561_346 * a - 0.063_854_17 * b;
229 let s_ = l - 0.089_484_18 * a - 1.291_485_5 * b;
230
231 let l3 = l_ * l_ * l_;
232 let m3 = m_ * m_ * m_;
233 let s3 = s_ * s_ * s_;
234
235 let r = 4.076_741_7 * l3 - 3.307_711_6 * m3 + 0.230_969_94 * s3;
236 let g = -1.268_438 * l3 + 2.609_757_4 * m3 - 0.341_319_38 * s3;
237 let b = -0.004_196_086_3 * l3 - 0.703_418_6 * m3 + 1.707_614_7 * s3;
238
239 (
240 linear_to_srgb(r).clamp(0.0, 1.0),
241 linear_to_srgb(g).clamp(0.0, 1.0),
242 linear_to_srgb(b).clamp(0.0, 1.0),
243 )
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249
250 fn assert_close(a: f32, b: f32) {
251 assert!(
252 (a - b).abs() <= 0.001,
253 "expected {} ~= {} (delta {})",
254 a,
255 b,
256 (a - b).abs()
257 );
258 }
259
260 fn generic_lerp<T: Lerp>(a: T, b: T, t: f32) -> T {
261 a.lerp(b, t)
262 }
263
264 #[test]
265 fn test_f32_lerp_clamps_and_identity() {
266 assert_eq!(generic_lerp(10.0_f32, 20.0_f32, -2.0), 10.0);
267 assert_eq!(generic_lerp(10.0_f32, 20.0_f32, 2.0), 20.0);
268 assert_eq!(generic_lerp(7.5_f32, 7.5_f32, 0.25), 7.5);
269 assert_close(generic_lerp(10.0_f32, 20.0_f32, 0.25), 12.5);
270 }
271
272 #[test]
273 fn test_u16_lerp_clamps_rounds_and_identity() {
274 assert_eq!(10_u16.lerp(20, -1.0), 10);
275 assert_eq!(10_u16.lerp(20, 2.0), 20);
276 assert_eq!(10_u16.lerp(20, 0.49), 15);
277 assert_eq!(42_u16.lerp(42, 0.66), 42);
278 }
279
280 #[test]
281 fn test_vector2_and_vec2_lerp() {
282 let a = Vector2::new(0.0, 10.0);
283 let b = Vector2::new(20.0, 30.0);
284 let v = a.lerp(b, 0.5);
285 assert_close(v.x, 10.0);
286 assert_close(v.y, 20.0);
287
288 let mq_a = macroquad::prelude::Vec2::new(0.0, 10.0);
289 let mq_b = macroquad::prelude::Vec2::new(20.0, 30.0);
290 let mq_v = mq_a.lerp(mq_b, 0.5);
291 assert_close(mq_v.x, 10.0);
292 assert_close(mq_v.y, 20.0);
293 }
294
295 #[test]
296 fn test_tuple_lerp() {
297 let tf = (0.0_f32, 1.0_f32, 2.0_f32, 3.0_f32).lerp((4.0, 5.0, 6.0, 7.0), 0.5);
298 assert_close(tf.0, 2.0);
299 assert_close(tf.1, 3.0);
300 assert_close(tf.2, 4.0);
301 assert_close(tf.3, 5.0);
302
303 let tu = (0_u16, 10_u16, 20_u16, 30_u16).lerp((10, 20, 30, 40), 0.5);
304 assert_eq!(tu, (5, 15, 25, 35));
305 }
306
307 #[test]
308 fn test_color_lerp_linear_and_alpha() {
309 let a = Color::rgba(10.0, 20.0, 30.0, 40.0);
310 let b = Color::rgba(110.0, 220.0, 130.0, 240.0);
311 let mid = a.lerp(b, 0.5);
312
313 assert_close(mid.r, 60.0);
314 assert_close(mid.g, 120.0);
315 assert_close(mid.b, 80.0);
316 assert_close(mid.a, 140.0);
317
318 assert_eq!(a.lerp(b, -1.0), a);
319 assert_eq!(a.lerp(b, 2.0), b);
320 }
321
322 #[test]
323 fn test_color_lerp_srgb_clamps_and_is_brighter_midpoint_than_linear() {
324 let black = Color::rgba(0.0, 0.0, 0.0, 10.0);
325 let white = Color::rgba(255.0, 255.0, 255.0, 210.0);
326
327 let linear_mid = black.lerp(white, 0.5);
328 let srgb_mid = black.lerp_srgb(white, 0.5);
329
330 assert!(srgb_mid.r > linear_mid.r, "sRGB midpoint should be perceptually brighter");
331 assert_close(srgb_mid.a, 110.0);
332
333 assert_eq!(black.lerp_srgb(white, -0.5), black);
334 assert_eq!(black.lerp_srgb(white, 1.5), white);
335 assert_eq!(black.lerp_srgb(black, 0.42), black);
336 }
337
338 #[test]
339 fn test_color_lerp_oklab_clamps_alpha_and_identity() {
340 let a = Color::rgba(255.0, 0.0, 0.0, 10.0);
341 let b = Color::rgba(0.0, 0.0, 255.0, 250.0);
342 let mid = a.lerp_oklab(b, 0.5);
343
344 assert_close(mid.a, 130.0);
345 assert!(mid.r >= 0.0 && mid.r <= 255.0);
346 assert!(mid.g >= 0.0 && mid.g <= 255.0);
347 assert!(mid.b >= 0.0 && mid.b <= 255.0);
348
349 assert_eq!(a.lerp_oklab(b, -0.5), a);
350 assert_eq!(a.lerp_oklab(b, 1.5), b);
351 assert_eq!(a.lerp_oklab(a, 0.42), a);
352 }
353}