Skip to main content

ferroui_base/rendering/composition/animations/
interpolators.rs

1//! Interpolation of the value types composition animations can animate.
2
3use crate::media::{Color, HslColor};
4use crate::numerics::{Quaternion, Vector2, Vector3, Vector4};
5use crate::{RelativePoint, RelativeScalar, Vector, Vector3D};
6use std::any::Any;
7
8/// An interface to define interpolation logic for a particular type
9pub trait IInterpolator<T> {
10    fn interpolate(&self, from: T, to: T, progress: f32) -> T;
11}
12
13macro_rules! instance {
14    ($ty:ident) => {
15        impl $ty {
16            /// The shared instance.
17            pub fn instance() -> &'static $ty {
18                static INSTANCE: $ty = $ty;
19                &INSTANCE
20            }
21        }
22    };
23}
24
25/// Interpolates single-precision scalars.
26#[derive(Clone, Copy, Debug, Default)]
27pub struct ScalarInterpolator;
28
29impl IInterpolator<f32> for ScalarInterpolator {
30    fn interpolate(&self, from: f32, to: f32, progress: f32) -> f32 {
31        from + (to - from) * progress
32    }
33}
34
35instance!(ScalarInterpolator);
36
37/// Interpolates double-precision scalars.
38#[derive(Clone, Copy, Debug, Default)]
39pub struct DoubleInterpolator;
40
41impl IInterpolator<f64> for DoubleInterpolator {
42    fn interpolate(&self, from: f64, to: f64, progress: f32) -> f64 {
43        from + (to - from) * progress as f64
44    }
45}
46
47instance!(DoubleInterpolator);
48
49/// Interpolates [`Vector2`] values.
50#[derive(Clone, Copy, Debug, Default)]
51pub struct Vector2Interpolator;
52
53impl IInterpolator<Vector2> for Vector2Interpolator {
54    fn interpolate(&self, from: Vector2, to: Vector2, progress: f32) -> Vector2 {
55        Vector2::lerp(from, to, progress)
56    }
57}
58
59instance!(Vector2Interpolator);
60
61/// Interpolates [`Vector`] values.
62#[derive(Clone, Copy, Debug, Default)]
63pub struct VectorInterpolator;
64
65impl IInterpolator<Vector> for VectorInterpolator {
66    fn interpolate(&self, from: Vector, to: Vector, progress: f32) -> Vector {
67        Vector::new(
68            DoubleInterpolator::instance().interpolate(from.x, to.x, progress),
69            DoubleInterpolator::instance().interpolate(from.y, to.y, progress),
70        )
71    }
72}
73
74instance!(VectorInterpolator);
75
76/// Interpolates [`Vector3`] values.
77#[derive(Clone, Copy, Debug, Default)]
78pub struct Vector3Interpolator;
79
80impl IInterpolator<Vector3> for Vector3Interpolator {
81    fn interpolate(&self, from: Vector3, to: Vector3, progress: f32) -> Vector3 {
82        Vector3::lerp(from, to, progress)
83    }
84}
85
86instance!(Vector3Interpolator);
87
88/// Interpolates [`Vector3D`] values.
89#[derive(Clone, Copy, Debug, Default)]
90pub struct Vector3DInterpolator;
91
92impl IInterpolator<Vector3D> for Vector3DInterpolator {
93    fn interpolate(&self, from: Vector3D, to: Vector3D, progress: f32) -> Vector3D {
94        Vector3D::new(
95            DoubleInterpolator::instance().interpolate(from.x, to.x, progress),
96            DoubleInterpolator::instance().interpolate(from.y, to.y, progress),
97            DoubleInterpolator::instance().interpolate(from.z, to.z, progress),
98        )
99    }
100}
101
102instance!(Vector3DInterpolator);
103
104/// Interpolates [`Vector4`] values.
105#[derive(Clone, Copy, Debug, Default)]
106pub struct Vector4Interpolator;
107
108impl IInterpolator<Vector4> for Vector4Interpolator {
109    fn interpolate(&self, from: Vector4, to: Vector4, progress: f32) -> Vector4 {
110        Vector4::lerp(from, to, progress)
111    }
112}
113
114instance!(Vector4Interpolator);
115
116/// Interpolates [`Quaternion`] values (a normalized linear interpolation).
117#[derive(Clone, Copy, Debug, Default)]
118pub struct QuaternionInterpolator;
119
120impl IInterpolator<Quaternion> for QuaternionInterpolator {
121    fn interpolate(&self, from: Quaternion, to: Quaternion, progress: f32) -> Quaternion {
122        Quaternion::lerp(from, to, progress)
123    }
124}
125
126instance!(QuaternionInterpolator);
127
128/// Interpolates [`Color`] values per ARGB channel.
129#[derive(Clone, Copy, Debug, Default)]
130pub struct ColorInterpolator;
131
132impl ColorInterpolator {
133    fn lerp(a: f32, b: f32, p: f32) -> u8 {
134        // The conversion truncates; the value is already within the byte range
135        // (a NaN becomes zero).
136        ((a * (1.0 - p)) + (b * p)).clamp(0.0, 255.0) as u8
137    }
138
139    /// Interpolates each of the alpha, red, green and blue channels linearly.
140    pub fn lerp_rgb(to: Color, from: Color, progress: f32) -> Color {
141        Color::new(
142            Self::lerp(to.a as f32, from.a as f32, progress),
143            Self::lerp(to.r as f32, from.r as f32, progress),
144            Self::lerp(to.g as f32, from.g as f32, progress),
145            Self::lerp(to.b as f32, from.b as f32, progress),
146        )
147    }
148}
149
150impl IInterpolator<Color> for ColorInterpolator {
151    fn interpolate(&self, from: Color, to: Color, progress: f32) -> Color {
152        Self::lerp_rgb(from, to, progress)
153    }
154}
155
156instance!(ColorInterpolator);
157
158/// The colour space a colour animation interpolates in (WinUI
159/// `CompositionColorSpace`, the type of
160/// `ColorKeyFrameAnimation.InterpolationColorSpace`).
161#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
162pub enum CompositionColorSpace {
163    /// The compositor chooses: here the gamma-encoded RGB space, as `Rgb`.
164    // Deviation (winui-composition.md): WinUI does not document what `Auto`
165    // picks; the port interpolates in RGB, the behaviour before the property
166    // existed.
167    #[default]
168    Auto,
169    /// Hue, saturation and lightness.
170    Hsl,
171    /// The red, green and blue channels as stored (gamma-encoded sRGB).
172    Rgb,
173    /// Hue, saturation and lightness of the linear-light colour.
174    HslLinear,
175    /// The red, green and blue channels in linear light.
176    RgbLinear,
177}
178
179/// Linear interpolation of `f64` values.
180fn lerp_f64(a: f64, b: f64, p: f64) -> f64 {
181    (a * (1.0 - p)) + (b * p)
182}
183
184/// The sRGB transfer function: a gamma-encoded channel (0..1) in linear light.
185fn srgb_to_linear(c: f64) -> f64 {
186    if c <= 0.04045 {
187        c / 12.92
188    } else {
189        ((c + 0.055) / 1.055).powf(2.4)
190    }
191}
192
193/// The inverse of [`srgb_to_linear`].
194fn linear_to_srgb(c: f64) -> f64 {
195    let c = c.clamp(0.0, 1.0);
196    if c <= 0.003_130_8 {
197        c * 12.92
198    } else {
199        1.055 * c.powf(1.0 / 2.4) - 0.055
200    }
201}
202
203/// A channel in 0..1 as a byte, rounded.
204fn unit_to_byte(c: f64) -> u8 {
205    (c.clamp(0.0, 1.0) * 255.0).round() as u8
206}
207
208/// Hue (degrees, 0..360), saturation and lightness of an RGB colour whose
209/// channels are in 0..1.
210fn rgb_to_hsl(r: f64, g: f64, b: f64) -> (f64, f64, f64) {
211    let max = r.max(g).max(b);
212    let min = r.min(g).min(b);
213    let l = (max + min) / 2.0;
214    let chroma = max - min;
215    if chroma <= 0.0 {
216        return (0.0, 0.0, l);
217    }
218    let s = chroma / (1.0 - (2.0 * l - 1.0).abs());
219    let h = if max == r {
220        60.0 * (((g - b) / chroma).rem_euclid(6.0))
221    } else if max == g {
222        60.0 * (((b - r) / chroma) + 2.0)
223    } else {
224        60.0 * (((r - g) / chroma) + 4.0)
225    };
226    (h, s.clamp(0.0, 1.0), l)
227}
228
229/// The RGB channels (0..1) of a hue (degrees, wrapped), saturation and
230/// lightness (clamped to 0..1).
231fn hsl_to_rgb(h: f64, s: f64, l: f64) -> (f64, f64, f64) {
232    let h = h.rem_euclid(360.0);
233    let s = s.clamp(0.0, 1.0);
234    let l = l.clamp(0.0, 1.0);
235    let chroma = (1.0 - (2.0 * l - 1.0).abs()) * s;
236    let h1 = h / 60.0;
237    let x = chroma * (1.0 - ((h1 % 2.0) - 1.0).abs());
238    let (r1, g1, b1) = if h1 < 1.0 {
239        (chroma, x, 0.0)
240    } else if h1 < 2.0 {
241        (x, chroma, 0.0)
242    } else if h1 < 3.0 {
243        (0.0, chroma, x)
244    } else if h1 < 4.0 {
245        (0.0, x, chroma)
246    } else if h1 < 5.0 {
247        (x, 0.0, chroma)
248    } else {
249        (chroma, 0.0, x)
250    };
251    let m = l - chroma / 2.0;
252    (r1 + m, g1 + m, b1 + m)
253}
254
255/// The hue to start from and the change of hue to interpolate: the change
256/// takes the shorter way round, and the hue of an achromatic colour (no
257/// saturation) is that of the other colour.
258fn hue_path(from_hue: f64, from_saturation: f64, to_hue: f64, to_saturation: f64) -> (f64, f64) {
259    let (from_hue, to_hue) = match (from_saturation == 0.0, to_saturation == 0.0) {
260        (true, false) => (to_hue, to_hue),
261        (false, true) => (from_hue, from_hue),
262        _ => (from_hue, to_hue),
263    };
264    let mut delta = to_hue - from_hue;
265    if delta > 180.0 {
266        delta -= 360.0;
267    } else if delta < -180.0 {
268        delta += 360.0;
269    }
270    (from_hue, delta)
271}
272
273impl ColorInterpolator {
274    /// Interpolates in the HSL colour space, giving `from` at progress 0 and
275    /// `to` at progress 1 (the expression function `ColorLerpHSL` and the
276    /// `Hsl` colour space of a colour animation). Saturation, lightness and
277    /// alpha are interpolated linearly; the hue takes the shorter way around
278    /// the colour wheel, and the hue of an achromatic colour (black, white,
279    /// greys) is that of the other colour, so a grey turns into a colour
280    /// without passing through other hues.
281    //
282    // Deviation (winui-composition.md): WinUI does not say which way the hue
283    // turns or what an achromatic colour's hue is; this is the choice of the
284    // port.
285    pub fn lerp_hsl(from: Color, to: Color, progress: f32) -> Color {
286        let a = from.to_hsl();
287        let b = to.to_hsl();
288        let p = progress as f64;
289        let (from_hue, delta) = hue_path(a.h, a.s, b.h, b.s);
290        HslColor::hsl_to_rgb(
291            from_hue + delta * p,
292            lerp_f64(a.s, b.s, p),
293            lerp_f64(a.l, b.l, p),
294            lerp_f64(a.a, b.a, p),
295        )
296    }
297
298    /// Interpolates the red, green and blue channels in linear light (the
299    /// `RgbLinear` colour space): the channels are decoded with the sRGB
300    /// transfer function, interpolated, and encoded again. Alpha is
301    /// interpolated linearly.
302    pub fn lerp_rgb_linear(from: Color, to: Color, progress: f32) -> Color {
303        let p = progress as f64;
304        let channel = |a: u8, b: u8| {
305            let a = srgb_to_linear(a as f64 / 255.0);
306            let b = srgb_to_linear(b as f64 / 255.0);
307            unit_to_byte(linear_to_srgb(lerp_f64(a, b, p)))
308        };
309        let alpha = unit_to_byte(lerp_f64(from.a as f64 / 255.0, to.a as f64 / 255.0, p));
310        Color::new(alpha, channel(from.r, to.r), channel(from.g, to.g), channel(from.b, to.b))
311    }
312
313    /// Interpolates in the HSL colour space of the linear-light colour (the
314    /// `HslLinear` colour space): the channels are decoded with the sRGB
315    /// transfer function, the colour is interpolated in HSL as
316    /// [`lerp_hsl`](Self::lerp_hsl) does, and the result is encoded again.
317    pub fn lerp_hsl_linear(from: Color, to: Color, progress: f32) -> Color {
318        let p = progress as f64;
319        let linear = |c: Color| {
320            rgb_to_hsl(
321                srgb_to_linear(c.r as f64 / 255.0),
322                srgb_to_linear(c.g as f64 / 255.0),
323                srgb_to_linear(c.b as f64 / 255.0),
324            )
325        };
326        let (ah, as_, al) = linear(from);
327        let (bh, bs, bl) = linear(to);
328        let (from_hue, delta) = hue_path(ah, as_, bh, bs);
329        let (r, g, b) = hsl_to_rgb(from_hue + delta * p, lerp_f64(as_, bs, p), lerp_f64(al, bl, p));
330        let alpha = unit_to_byte(lerp_f64(from.a as f64 / 255.0, to.a as f64 / 255.0, p));
331        Color::new(
332            alpha,
333            unit_to_byte(linear_to_srgb(r)),
334            unit_to_byte(linear_to_srgb(g)),
335            unit_to_byte(linear_to_srgb(b)),
336        )
337    }
338}
339
340/// Interpolates [`Color`] values in the HSL colour space
341/// ([`ColorInterpolator::lerp_hsl`]).
342#[derive(Clone, Copy, Debug, Default)]
343pub struct ColorHslInterpolator;
344
345impl IInterpolator<Color> for ColorHslInterpolator {
346    fn interpolate(&self, from: Color, to: Color, progress: f32) -> Color {
347        ColorInterpolator::lerp_hsl(from, to, progress)
348    }
349}
350
351instance!(ColorHslInterpolator);
352
353/// Interpolates [`Color`] values in linear-light RGB
354/// ([`ColorInterpolator::lerp_rgb_linear`]).
355#[derive(Clone, Copy, Debug, Default)]
356pub struct ColorRgbLinearInterpolator;
357
358impl IInterpolator<Color> for ColorRgbLinearInterpolator {
359    fn interpolate(&self, from: Color, to: Color, progress: f32) -> Color {
360        ColorInterpolator::lerp_rgb_linear(from, to, progress)
361    }
362}
363
364instance!(ColorRgbLinearInterpolator);
365
366/// Interpolates [`Color`] values in the HSL colour space of linear light
367/// ([`ColorInterpolator::lerp_hsl_linear`]).
368#[derive(Clone, Copy, Debug, Default)]
369pub struct ColorHslLinearInterpolator;
370
371impl IInterpolator<Color> for ColorHslLinearInterpolator {
372    fn interpolate(&self, from: Color, to: Color, progress: f32) -> Color {
373        ColorInterpolator::lerp_hsl_linear(from, to, progress)
374    }
375}
376
377instance!(ColorHslLinearInterpolator);
378
379/// The interpolator of a key frame animation of `T` values for a colour
380/// space: for colours, the interpolator of the space; for any other type, and
381/// for the RGB spaces of colours, `default`.
382pub(crate) fn interpolator_for_color_space<T: 'static>(
383    default: &'static (dyn IInterpolator<T> + Sync),
384    color_space: CompositionColorSpace,
385) -> &'static (dyn IInterpolator<T> + Sync) {
386    static HSL: &(dyn IInterpolator<Color> + Sync) = &ColorHslInterpolator;
387    static RGB_LINEAR: &(dyn IInterpolator<Color> + Sync) = &ColorRgbLinearInterpolator;
388    static HSL_LINEAR: &(dyn IInterpolator<Color> + Sync) = &ColorHslLinearInterpolator;
389    let chosen: &'static dyn Any = match color_space {
390        CompositionColorSpace::Auto | CompositionColorSpace::Rgb => return default,
391        CompositionColorSpace::Hsl => &HSL,
392        CompositionColorSpace::RgbLinear => &RGB_LINEAR,
393        CompositionColorSpace::HslLinear => &HSL_LINEAR,
394    };
395    // The colour interpolators are of `Color` values: they are only chosen
396    // when `T` is `Color`.
397    chosen.downcast_ref::<&'static (dyn IInterpolator<T> + Sync)>().copied().unwrap_or(default)
398}
399
400/// "Interpolates" booleans: the value switches when the animation completes.
401#[derive(Clone, Copy, Debug, Default)]
402pub struct BooleanInterpolator;
403
404impl IInterpolator<bool> for BooleanInterpolator {
405    fn interpolate(&self, from: bool, to: bool, progress: f32) -> bool {
406        if progress >= 1.0 {
407            to
408        } else {
409            from
410        }
411    }
412}
413
414instance!(BooleanInterpolator);
415
416/// Interpolates [`RelativePoint`] values of the same unit; points of
417/// different units switch halfway.
418#[derive(Clone, Copy, Debug, Default)]
419pub struct RelativePointInterpolator;
420
421impl IInterpolator<RelativePoint> for RelativePointInterpolator {
422    fn interpolate(&self, from: RelativePoint, to: RelativePoint, progress: f32) -> RelativePoint {
423        if from.unit != to.unit {
424            return if progress >= 0.5 { to } else { from };
425        }
426
427        let x = DoubleInterpolator::instance().interpolate(from.point.x, to.point.x, progress);
428        let y = DoubleInterpolator::instance().interpolate(from.point.y, to.point.y, progress);
429        RelativePoint::new(x, y, from.unit)
430    }
431}
432
433instance!(RelativePointInterpolator);
434
435/// Interpolates [`RelativeScalar`] values of the same unit; scalars of
436/// different units switch halfway.
437#[derive(Clone, Copy, Debug, Default)]
438pub struct RelativeScalarInterpolator;
439
440impl IInterpolator<RelativeScalar> for RelativeScalarInterpolator {
441    fn interpolate(&self, from: RelativeScalar, to: RelativeScalar, progress: f32) -> RelativeScalar {
442        if from.unit != to.unit {
443            return if progress >= 0.5 { to } else { from };
444        }
445        RelativeScalar::new(from.scalar + (to.scalar - from.scalar) * progress as f64, from.unit)
446    }
447}
448
449instance!(RelativeScalarInterpolator);
450
451#[cfg(test)]
452mod tests {
453    use super::*;
454    use crate::RelativeUnit;
455
456    #[test]
457    fn scalars() {
458        assert_eq!(ScalarInterpolator::instance().interpolate(2.0, 4.0, 0.25), 2.5);
459        assert_eq!(ScalarInterpolator::instance().interpolate(2.0, 4.0, 1.5), 5.0);
460        assert_eq!(DoubleInterpolator::instance().interpolate(2.0, 4.0, 0.25), 2.5);
461        assert_eq!(DoubleInterpolator::instance().interpolate(4.0, 2.0, 0.5), 3.0);
462    }
463
464    #[test]
465    fn vectors() {
466        assert_eq!(
467            Vector2Interpolator::instance().interpolate(Vector2::new(0.0, 2.0), Vector2::new(4.0, 4.0), 0.5),
468            Vector2::new(2.0, 3.0)
469        );
470        assert_eq!(
471            VectorInterpolator::instance().interpolate(Vector::new(0.0, 2.0), Vector::new(4.0, 4.0), 0.5),
472            Vector::new(2.0, 3.0)
473        );
474        assert_eq!(
475            Vector3Interpolator::instance().interpolate(
476                Vector3::new(0.0, 2.0, -2.0),
477                Vector3::new(4.0, 4.0, 2.0),
478                0.5
479            ),
480            Vector3::new(2.0, 3.0, 0.0)
481        );
482        assert_eq!(
483            Vector3DInterpolator::instance().interpolate(
484                Vector3D::new(0.0, 2.0, -2.0),
485                Vector3D::new(4.0, 4.0, 2.0),
486                0.5
487            ),
488            Vector3D::new(2.0, 3.0, 0.0)
489        );
490        assert_eq!(
491            Vector4Interpolator::instance().interpolate(
492                Vector4::new(0.0, 2.0, -2.0, 1.0),
493                Vector4::new(4.0, 4.0, 2.0, 1.0),
494                0.5
495            ),
496            Vector4::new(2.0, 3.0, 0.0, 1.0)
497        );
498    }
499
500    #[test]
501    fn quaternion_is_a_normalized_lerp() {
502        let from = Quaternion::create_from_axis_angle(Vector3::UNIT_Z, 0.0);
503        let to = Quaternion::create_from_axis_angle(Vector3::UNIT_Z, 1.0);
504        let actual = QuaternionInterpolator::instance().interpolate(from, to, 0.5);
505        assert_eq!(actual, Quaternion::lerp(from, to, 0.5));
506        assert!((actual.length() - 1.0).abs() < 1e-6);
507    }
508
509    #[test]
510    fn color_channels_are_interpolated_and_truncated() {
511        let from = Color::from_argb(255, 10, 0, 255);
512        let to = Color::from_argb(0, 200, 1, 255);
513        let i = ColorInterpolator::instance();
514        // (10 + 200) / 2 = 105; 127.5 and 0.5 truncate.
515        assert_eq!(i.interpolate(from, to, 0.5), Color::from_argb(127, 105, 0, 255));
516        assert_eq!(i.interpolate(from, to, 0.0), from);
517        assert_eq!(i.interpolate(from, to, 1.0), to);
518        // Out of range progress is clamped per channel.
519        assert_eq!(i.interpolate(from, to, 2.0), Color::from_argb(0, 255, 2, 255));
520        assert_eq!(i.interpolate(from, to, -1.0), Color::from_argb(255, 0, 0, 255));
521        assert_eq!(ColorInterpolator::lerp_rgb(from, to, 0.5), i.interpolate(from, to, 0.5));
522    }
523
524    /// Not from upstream: the colour spaces of `InterpolationColorSpace`.
525    #[test]
526    fn hsl_interpolation_turns_the_hue_the_short_way() {
527        let red = Color::from_argb(255, 255, 0, 0);
528        let blue = Color::from_argb(255, 0, 0, 255);
529        // Red (0 degrees) to blue (240 degrees) goes through magenta (300).
530        assert_eq!(ColorInterpolator::lerp_hsl(red, blue, 0.5), Color::from_argb(255, 255, 0, 255));
531        assert_eq!(ColorHslInterpolator::instance().interpolate(red, blue, 0.0), red);
532        assert_eq!(ColorHslInterpolator::instance().interpolate(red, blue, 1.0), blue);
533        // The RGB interpolation goes through a dark purple.
534        assert_eq!(ColorInterpolator::instance().interpolate(red, blue, 0.5), Color::from_argb(255, 127, 0, 127));
535        // A grey takes the hue of the other colour.
536        let grey = Color::from_argb(255, 128, 128, 128);
537        let mid = ColorInterpolator::lerp_hsl(grey, red, 0.5);
538        assert!(mid.r > mid.g && mid.g == mid.b, "{mid:?}");
539    }
540
541    #[test]
542    fn linear_rgb_interpolation_is_brighter_in_the_middle() {
543        let black = Color::from_argb(255, 0, 0, 0);
544        let white = Color::from_argb(255, 255, 255, 255);
545        // Half the light of white is 188 in sRGB.
546        let mid = ColorRgbLinearInterpolator::instance().interpolate(black, white, 0.5);
547        assert_eq!(mid, Color::from_argb(255, 188, 188, 188));
548        assert_eq!(ColorInterpolator::lerp_rgb_linear(black, white, 0.0), black);
549        assert_eq!(ColorInterpolator::lerp_rgb_linear(black, white, 1.0), white);
550        let transparent = Color::from_argb(0, 255, 0, 0);
551        assert_eq!(ColorInterpolator::lerp_rgb_linear(transparent, white, 0.5).a, 128);
552    }
553
554    #[test]
555    fn linear_hsl_interpolation_keeps_the_end_points() {
556        let red = Color::from_argb(255, 255, 0, 0);
557        let blue = Color::from_argb(255, 0, 0, 255);
558        let i = ColorHslLinearInterpolator::instance();
559        assert_eq!(i.interpolate(red, blue, 0.0), red);
560        assert_eq!(i.interpolate(red, blue, 1.0), blue);
561        assert_eq!(i.interpolate(red, blue, 0.5), Color::from_argb(255, 255, 0, 255));
562        let dark = Color::from_argb(255, 40, 80, 120);
563        assert_eq!(i.interpolate(dark, dark, 0.3), dark);
564    }
565
566    #[test]
567    fn color_space_selects_the_interpolator_of_colours_only() {
568        let red = Color::from_argb(255, 255, 0, 0);
569        let blue = Color::from_argb(255, 0, 0, 255);
570        let hsl = interpolator_for_color_space::<Color>(ColorInterpolator::instance(), CompositionColorSpace::Hsl);
571        assert_eq!(hsl.interpolate(red, blue, 0.5), Color::from_argb(255, 255, 0, 255));
572        let auto = interpolator_for_color_space::<Color>(ColorInterpolator::instance(), CompositionColorSpace::Auto);
573        assert_eq!(auto.interpolate(red, blue, 0.5), Color::from_argb(255, 127, 0, 127));
574        let scalar = interpolator_for_color_space::<f32>(ScalarInterpolator::instance(), CompositionColorSpace::Hsl);
575        assert_eq!(scalar.interpolate(2.0, 4.0, 0.5), 3.0);
576    }
577
578    #[test]
579    fn boolean_switches_at_the_end() {
580        let i = BooleanInterpolator::instance();
581        assert!(!i.interpolate(false, true, 0.0));
582        assert!(!i.interpolate(false, true, 0.999));
583        assert!(i.interpolate(false, true, 1.0));
584        assert!(i.interpolate(true, false, 0.5));
585    }
586
587    #[test]
588    fn relative_values() {
589        let p = RelativePointInterpolator::instance();
590        let from = RelativePoint::new(0.0, 1.0, RelativeUnit::Relative);
591        let to = RelativePoint::new(1.0, 0.0, RelativeUnit::Relative);
592        assert_eq!(
593            p.interpolate(from, to, 0.25),
594            RelativePoint::new(0.25, 0.75, RelativeUnit::Relative)
595        );
596        let absolute = RelativePoint::new(10.0, 20.0, RelativeUnit::Absolute);
597        assert_eq!(p.interpolate(from, absolute, 0.25), from);
598        assert_eq!(p.interpolate(from, absolute, 0.5), absolute);
599
600        let s = RelativeScalarInterpolator::instance();
601        let from = RelativeScalar::new(0.0, RelativeUnit::Relative);
602        let to = RelativeScalar::new(1.0, RelativeUnit::Relative);
603        assert_eq!(
604            s.interpolate(from, to, 0.25),
605            RelativeScalar::new(0.25, RelativeUnit::Relative)
606        );
607        let absolute = RelativeScalar::new(10.0, RelativeUnit::Absolute);
608        assert_eq!(s.interpolate(from, absolute, 0.25), from);
609        assert_eq!(s.interpolate(from, absolute, 0.75), absolute);
610    }
611}