1use super::FilterEffect;
17use super::gaussian_blur::apply_blur;
18use super::shift::offset_pixels;
19use crate::filter::context::ScratchBuffer;
20use vello_common::color::{AlphaColor, Srgb};
21use vello_common::filter::drop_shadow::DropShadow;
22use vello_common::filter_effects::EdgeMode;
23use vello_common::peniko::color::PremulRgba8;
24#[cfg(not(feature = "std"))]
25use vello_common::peniko::kurbo::common::FloatFuncs as _;
26use vello_common::pixmap::Pixmap;
27
28impl FilterEffect for DropShadow {
29 fn execute_lowp(&self, pixmap: &mut Pixmap, filter_scratch: &mut ScratchBuffer) {
30 apply_drop_shadow(
31 pixmap,
32 self.dx,
33 self.dy,
34 self.std_deviation,
35 self.n_decimations,
36 &self.kernel[..usize::from(self.kernel_size)],
37 self.color,
38 self.edge_mode,
39 self.composite_original,
40 filter_scratch,
41 );
42 }
43
44 fn execute_highp(&self, pixmap: &mut Pixmap, filter_scratch: &mut ScratchBuffer) {
45 Self::execute_lowp(self, pixmap, filter_scratch);
48 }
49}
50
51fn apply_drop_shadow(
58 pixmap: &mut Pixmap,
59 dx: f32,
60 dy: f32,
61 std_deviation: f32,
62 n_decimations: usize,
63 kernel: &[f32],
64 color: AlphaColor<Srgb>,
65 edge_mode: EdgeMode,
66 composite_original: bool,
67 filter_scratch: &mut ScratchBuffer,
68) {
69 let mut shadow_pixmap = pixmap.clone();
71
72 offset_pixels(&mut shadow_pixmap, dx, dy);
74
75 if std_deviation > 0.0 {
77 let scratch =
78 filter_scratch.get_scratch_buffer(shadow_pixmap.width(), shadow_pixmap.height());
79 apply_blur(
80 &mut shadow_pixmap,
81 scratch,
82 n_decimations,
83 kernel,
84 edge_mode,
85 );
86 }
87
88 write_colored_shadow(&shadow_pixmap, pixmap, color, composite_original);
90}
91
92fn write_colored_shadow(
98 shadow: &Pixmap,
99 dst: &mut Pixmap,
100 color: AlphaColor<Srgb>,
101 composite_original: bool,
102) {
103 let width = dst.width();
104 let height = dst.height();
105
106 let shadow_r = (color.components[0] * 255.0).round() as u8;
108 let shadow_g = (color.components[1] * 255.0).round() as u8;
109 let shadow_b = (color.components[2] * 255.0).round() as u8;
110
111 for y in 0..height {
112 for x in 0..width {
113 let alpha = shadow.sample(x, y).a;
115
116 let shadow_alpha = (u8_to_norm(alpha) * color.components[3]).min(1.0);
118 let final_alpha = norm_to_u8(shadow_alpha);
119
120 let alpha_u16 = u16::from(final_alpha);
122 let premultiply = |channel: u8| ((u16::from(channel) * alpha_u16) / 255) as u8;
123
124 let colored_shadow = PremulRgba8 {
125 r: premultiply(shadow_r),
126 g: premultiply(shadow_g),
127 b: premultiply(shadow_b),
128 a: final_alpha,
129 };
130
131 let result = if composite_original {
132 compose_src_over(dst.sample(x, y), colored_shadow)
134 } else {
135 colored_shadow
136 };
137
138 dst.set_pixel(x, y, result);
139 }
140 }
141}
142
143fn compose_src_over(src: PremulRgba8, dst: PremulRgba8) -> PremulRgba8 {
150 let src_a = u8_to_norm(src.a);
151
152 PremulRgba8 {
153 r: src_over_channel(src.r, dst.r, src_a),
154 g: src_over_channel(src.g, dst.g, src_a),
155 b: src_over_channel(src.b, dst.b, src_a),
156 a: src_over_channel(src.a, dst.a, src_a),
157 }
158}
159
160#[inline]
164fn src_over_channel(src: u8, dst: u8, src_alpha: f32) -> u8 {
165 let result = u8_to_norm(src) + u8_to_norm(dst) * (1.0 - src_alpha);
166 norm_to_u8(result)
167}
168
169#[inline]
171fn u8_to_norm(value: u8) -> f32 {
172 value as f32 / 255.0
173}
174
175#[inline]
177fn norm_to_u8(value: f32) -> u8 {
178 (value * 255.0).round() as u8
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184 use vello_common::color::Srgb;
185
186 #[test]
188 fn test_u8_to_norm() {
189 assert_eq!(u8_to_norm(0), 0.0);
190 assert!((u8_to_norm(255) - 1.0).abs() < 1e-6);
191 }
192
193 #[test]
195 fn test_norm_to_u8() {
196 assert_eq!(norm_to_u8(0.0), 0);
197 assert_eq!(norm_to_u8(1.0), 255);
198 assert_eq!(norm_to_u8(0.5), 128); }
200
201 #[test]
203 fn test_conversion_roundtrip() {
204 for value in [0, 1, 50, 127, 128, 200, 254, 255] {
205 let normalized = u8_to_norm(value);
206 let back = norm_to_u8(normalized);
207 assert_eq!(back, value);
208 }
209 }
210
211 #[test]
213 fn test_compose_src_over_opaque_source() {
214 let src = PremulRgba8 {
215 r: 255,
216 g: 0,
217 b: 0,
218 a: 255,
219 }; let dst = PremulRgba8 {
221 r: 0,
222 g: 255,
223 b: 0,
224 a: 255,
225 }; let result = compose_src_over(src, dst);
228 assert_eq!(result.r, 255);
230 assert_eq!(result.g, 0);
231 assert_eq!(result.b, 0);
232 assert_eq!(result.a, 255);
233 }
234
235 #[test]
237 fn test_compose_src_over_transparent_source() {
238 let src = PremulRgba8 {
239 r: 0,
240 g: 0,
241 b: 0,
242 a: 0,
243 };
244 let dst = PremulRgba8 {
245 r: 0,
246 g: 255,
247 b: 0,
248 a: 255,
249 };
250
251 let result = compose_src_over(src, dst);
252 assert_eq!(result.r, 0);
254 assert_eq!(result.g, 255);
255 assert_eq!(result.b, 0);
256 assert_eq!(result.a, 255);
257 }
258
259 #[test]
261 fn test_compose_src_over_semi_transparent() {
262 let src = PremulRgba8 {
263 r: 128,
264 g: 0,
265 b: 0,
266 a: 128,
267 }; let dst = PremulRgba8 {
269 r: 0,
270 g: 128,
271 b: 0,
272 a: 128,
273 }; let result = compose_src_over(src, dst);
276 assert_eq!(
281 result,
282 PremulRgba8 {
283 r: 128,
284 g: 64,
285 b: 0,
286 a: 192,
287 }
288 );
289 }
290
291 #[test]
293 fn test_compose_shadow_color() {
294 let mut shadow_pixmap = Pixmap::new(2, 2);
295 let mut dst_pixmap = Pixmap::new(2, 2);
296
297 shadow_pixmap.set_pixel(
299 0,
300 0,
301 PremulRgba8 {
302 r: 0,
303 g: 0,
304 b: 0,
305 a: 255,
306 },
307 );
308
309 let shadow_color = AlphaColor {
310 components: [1.0, 0.0, 0.0, 1.0], cs: std::marker::PhantomData::<Srgb>,
312 };
313
314 write_colored_shadow(&shadow_pixmap, &mut dst_pixmap, shadow_color, true);
315
316 let result = dst_pixmap.sample(0, 0);
318 assert_eq!(result.r, 255);
319 assert_eq!(result.g, 0);
320 assert_eq!(result.b, 0);
321 assert_eq!(result.a, 255);
322 }
323}