1use crate::color::PremultipliedColor;
8
9#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
11pub enum BlendMode {
12 Clear,
13 Source,
14 Destination,
15 #[default]
16 SourceOver,
17 DestinationOver,
18 SourceIn,
19 DestinationIn,
20 SourceOut,
21 DestinationOut,
22 SourceAtop,
23 DestinationAtop,
24 Xor,
25 Plus,
27 Multiply,
28 Screen,
29 Overlay,
30 Darken,
31 Lighten,
32 HardLight,
33 SoftLight,
35 Difference,
37 Exclusion,
39 ColorDodge,
41 ColorBurn,
43}
44
45impl BlendMode {
46 fn porter_duff(self, sa: f32, da: f32) -> Option<(f32, f32)> {
49 let f = match self {
50 BlendMode::Clear => (0.0, 0.0),
51 BlendMode::Source => (1.0, 0.0),
52 BlendMode::Destination => (0.0, 1.0),
53 BlendMode::SourceOver => (1.0, 1.0 - sa),
54 BlendMode::DestinationOver => (1.0 - da, 1.0),
55 BlendMode::SourceIn => (da, 0.0),
56 BlendMode::DestinationIn => (0.0, sa),
57 BlendMode::SourceOut => (1.0 - da, 0.0),
58 BlendMode::DestinationOut => (0.0, 1.0 - sa),
59 BlendMode::SourceAtop => (da, 1.0 - sa),
60 BlendMode::DestinationAtop => (1.0 - da, sa),
61 BlendMode::Xor => (1.0 - da, 1.0 - sa),
62 BlendMode::Plus => (1.0, 1.0),
63 _ => return None,
64 };
65 Some(f)
66 }
67}
68
69pub(crate) fn blend(
74 mode: BlendMode,
75 src: PremultipliedColor,
76 dst: PremultipliedColor,
77) -> PremultipliedColor {
78 if let Some((fa, fb)) = mode.porter_duff(src.a, dst.a) {
79 let mut out = PremultipliedColor {
80 r: src.r * fa + dst.r * fb,
81 g: src.g * fa + dst.g * fb,
82 b: src.b * fa + dst.b * fb,
83 a: src.a * fa + dst.a * fb,
84 };
85 out.r = out.r.clamp(0.0, 1.0);
87 out.g = out.g.clamp(0.0, 1.0);
88 out.b = out.b.clamp(0.0, 1.0);
89 out.a = out.a.clamp(0.0, 1.0);
90 return out;
91 }
92
93 let sa = src.a;
96 let da = dst.a;
97 let unpre = |c: f32, a: f32| if a > 0.0 { c / a } else { 0.0 };
98 let cs = (unpre(src.r, sa), unpre(src.g, sa), unpre(src.b, sa));
99 let cb = (unpre(dst.r, da), unpre(dst.g, da), unpre(dst.b, da));
100
101 let blend_ch = |s: f32, b: f32| -> f32 {
102 match mode {
103 BlendMode::Multiply => s * b,
104 BlendMode::Screen => s + b - s * b,
105 BlendMode::Darken => s.min(b),
106 BlendMode::Lighten => s.max(b),
107 BlendMode::Overlay => hard_light(b, s),
108 BlendMode::HardLight => hard_light(s, b),
109 BlendMode::SoftLight => soft_light(b, s),
110 BlendMode::Difference => (s - b).abs(),
111 BlendMode::Exclusion => s + b - 2.0 * s * b,
112 BlendMode::ColorDodge => color_dodge(b, s),
113 BlendMode::ColorBurn => color_burn(b, s),
114 _ => s,
115 }
116 };
117
118 let ao = sa + da * (1.0 - sa);
119 let mix = |s: f32, b: f32| -> f32 {
120 sa * (1.0 - da) * s + sa * da * blend_ch(s, b) + (1.0 - sa) * da * b
122 };
123
124 PremultipliedColor {
125 r: mix(cs.0, cb.0).clamp(0.0, 1.0),
126 g: mix(cs.1, cb.1).clamp(0.0, 1.0),
127 b: mix(cs.2, cb.2).clamp(0.0, 1.0),
128 a: ao.clamp(0.0, 1.0),
129 }
130}
131
132#[inline]
133fn hard_light(s: f32, b: f32) -> f32 {
134 if s <= 0.5 {
135 2.0 * s * b
136 } else {
137 1.0 - 2.0 * (1.0 - s) * (1.0 - b)
138 }
139}
140
141#[inline]
143fn soft_light(b: f32, s: f32) -> f32 {
144 if s <= 0.5 {
145 b - (1.0 - 2.0 * s) * b * (1.0 - b)
146 } else {
147 let d = if b <= 0.25 {
148 ((16.0 * b - 12.0) * b + 4.0) * b
149 } else {
150 b.sqrt()
151 };
152 b + (2.0 * s - 1.0) * (d - b)
153 }
154}
155
156#[inline]
158fn color_dodge(b: f32, s: f32) -> f32 {
159 if b <= 0.0 {
160 0.0
161 } else if s >= 1.0 {
162 1.0
163 } else {
164 (b / (1.0 - s)).min(1.0)
165 }
166}
167
168#[inline]
170fn color_burn(b: f32, s: f32) -> f32 {
171 if b >= 1.0 {
172 1.0
173 } else if s <= 0.0 {
174 0.0
175 } else {
176 1.0 - ((1.0 - b) / s).min(1.0)
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183 use crate::color::Color;
184
185 #[test]
186 fn source_over_opaque_replaces() {
187 let src = Color::from_rgba8(255, 0, 0, 255).premultiply();
188 let dst = Color::from_rgba8(0, 0, 255, 255).premultiply();
189 let out = blend(BlendMode::SourceOver, src, dst);
190 assert!((out.r - 1.0).abs() < 1e-3 && out.b < 1e-3);
191 }
192
193 #[test]
194 fn clear_zeroes() {
195 let src = Color::WHITE.premultiply();
196 let dst = Color::WHITE.premultiply();
197 let out = blend(BlendMode::Clear, src, dst);
198 assert!(out.a < 1e-6);
199 }
200
201 #[test]
202 fn difference_of_equal_is_zero() {
203 let c = Color::from_rgba8(200, 100, 50, 255).premultiply();
205 let out = blend(BlendMode::Difference, c, c);
206 assert!(out.r < 1e-3 && out.g < 1e-3 && out.b < 1e-3, "{out:?}");
207 }
208
209 #[test]
210 fn color_dodge_white_source_saturates() {
211 let src = Color::WHITE.premultiply();
212 let dst = Color::from_rgba8(128, 128, 128, 255).premultiply();
213 let out = blend(BlendMode::ColorDodge, src, dst);
214 assert!(out.r > 0.99, "{out:?}");
215 }
216}