1use crate::{
2 A98Rgb, DisplayP3, Hsl, HueInterpolation, Hwb, InterpolationSpace, Lab, Lch, LinearRgb, Oklab, Oklch, ProphotoRgb,
3 Rec2020, Srgb, XyzD50, XyzD65,
4};
5
6pub trait ColorMix<T, U>: Sized
13where
14 T: Into<Self>,
15 U: Into<Self>,
16{
17 fn mix(first: T, second: U, percentage: f64) -> Self;
18}
19
20macro_rules! impl_color_mix {
21 ($($ty:ident => $space:expr),+ $(,)?) => {
22 $(impl<T, U> ColorMix<T, U> for $ty
23 where
24 Self: From<T> + From<U>,
25 {
26 fn mix(first: T, second: U, percentage: f64) -> Self {
27 let space = $space;
28 let channels = |color: Self| space.channels(color.into());
29 space.color(space.mix(channels(first.into()), channels(second.into()), percentage)).into()
30 }
31 })+
32 };
33}
34
35impl_color_mix! {
36 Srgb => InterpolationSpace::Srgb,
37 LinearRgb => InterpolationSpace::SrgbLinear,
38 A98Rgb => InterpolationSpace::A98Rgb,
39 DisplayP3 => InterpolationSpace::DisplayP3,
40 ProphotoRgb => InterpolationSpace::ProphotoRgb,
41 Rec2020 => InterpolationSpace::Rec2020,
42 Lab => InterpolationSpace::Lab,
43 Oklab => InterpolationSpace::Oklab,
44 XyzD50 => InterpolationSpace::XyzD50,
45 XyzD65 => InterpolationSpace::XyzD65,
46 Hsl => InterpolationSpace::Hsl(HueInterpolation::Shorter),
47 Hwb => InterpolationSpace::Hwb(HueInterpolation::Shorter),
48 Lch => InterpolationSpace::Lch(HueInterpolation::Shorter),
49 Oklch => InterpolationSpace::Oklch(HueInterpolation::Shorter),
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55 use crate::*;
56
57 macro_rules! assert_close_to {
58 ($a: expr, $b: expr) => {
59 assert!($a.close_to($b, COLOR_EPSILON), "Expected {:?} to be (closely) equal to {:?}", $a, $b);
60 };
61 }
62
63 #[test]
64 fn test_basic_mix() {
65 let red = Srgb::from_u8(255, 0, 0, 100.0);
66 let blue = Srgb::from_u8(0, 0, 255, 100.0);
67 assert_close_to!(Srgb::mix(red, blue, 50.0), Srgb::new(0.5, 0.0, 0.5, 100.0));
68 }
69
70 #[test]
71 fn test_mix_named_in_oklab() {
72 assert_close_to!(
73 Oklch::mix(Named::Rebeccapurple, Named::Hotpink, 50.0),
74 Oklch::new(0.5842845967725198, 0.17868573405015944, 327.6838446374328, 100.0)
75 );
76 }
77
78 #[test]
79 fn test_alpha_mixing() {
80 let color1 = Srgb::from_u8(255, 0, 0, 80.0);
81 let color2 = Srgb::from_u8(0, 0, 255, 40.0);
82
83 let mixed = Srgb::mix(color1, color2, 50.0);
84 assert_eq!(mixed.to_u8(), [170, 0, 85]);
85 assert_eq!(mixed.alpha, 60.0);
86 }
87
88 #[test]
89 fn test_hwb_mix() {
90 let red = Hwb::new(0.0, 0.0, 0.0, 100.0);
93 let blue = Hwb::new(240.0, 0.0, 0.0, 100.0);
94 let mixed = Hwb::mix(red, blue, 50.0);
95 assert_close_to!(mixed, Hwb::new(300.0, 0.0, 0.0, 100.0));
96 }
97
98 #[test]
99 fn test_a98_rgb_mix() {
100 let c1 = A98Rgb::new(1.0, 0.0, 0.0, 100.0);
101 let c2 = A98Rgb::new(0.0, 0.0, 1.0, 100.0);
102 let mixed = A98Rgb::mix(c1, c2, 50.0);
103 assert_close_to!(mixed, A98Rgb::new(0.5, 0.0, 0.5, 100.0));
104 }
105
106 #[test]
108 fn test_premultiplied_alpha_lab() {
109 let c1 = Lab::new(10.0, 20.0, 30.0, 40.0);
110 let c2 = Lab::new(50.0, 60.0, 70.0, 80.0);
111 let mixed = Lab::mix(c1, c2, 50.0);
112 assert_close_to!(mixed, Lab::new(36.666664, 46.666664, 56.666664, 60.0));
113 }
114
115 #[test]
117 fn test_premultiplied_alpha_lab_25_75() {
118 let c1 = Lab::new(10.0, 20.0, 30.0, 40.0);
119 let c2 = Lab::new(50.0, 60.0, 70.0, 80.0);
120 let mixed = Lab::mix(c1, c2, 75.0);
122 assert_close_to!(mixed, Lab::new(44.285713, 54.285717, 64.28571, 70.0));
123 }
124
125 #[test]
127 fn test_premultiplied_alpha_oklch() {
128 let c1 = Oklch::new(0.1, 0.2, 30.0, 40.0);
129 let c2 = Oklch::new(0.5, 0.6, 70.0, 80.0);
130 let mixed = Oklch::mix(c1, c2, 50.0);
131 assert_close_to!(mixed, Oklch::new(0.36666664, 0.46666664, 50.0, 60.0));
132 }
133
134 #[test]
136 fn test_premultiplied_alpha_opaque_same_as_simple() {
137 let c1 = Lab::new(10.0, 20.0, 30.0, 100.0);
138 let c2 = Lab::new(50.0, 60.0, 70.0, 100.0);
139 let mixed = Lab::mix(c1, c2, 50.0);
140 assert_close_to!(mixed, Lab::new(30.0, 40.0, 50.0, 100.0));
141 }
142}