1use super::FilterEffect;
17use super::gaussian_blur::{MAX_KERNEL_SIZE, apply_blur, plan_decimated_blur};
18use super::shift::offset_pixels;
19use crate::layer_manager::LayerManager;
20use vello_common::color::{AlphaColor, Srgb};
21use vello_common::filter_effects::EdgeMode;
22use vello_common::peniko::color::PremulRgba8;
23#[cfg(not(feature = "std"))]
24use vello_common::peniko::kurbo::common::FloatFuncs as _;
25use vello_common::pixmap::Pixmap;
26
27pub(crate) struct DropShadow {
28 pub dx: f32,
29 pub dy: f32,
30 pub color: AlphaColor<Srgb>,
31 std_deviation: f32,
33 edge_mode: EdgeMode,
35 n_decimations: usize,
37 kernel: [f32; MAX_KERNEL_SIZE],
40 kernel_size: usize,
42}
43
44impl DropShadow {
45 pub(crate) fn new(
49 dx: f32,
50 dy: f32,
51 std_deviation: f32,
52 edge_mode: EdgeMode,
53 color: AlphaColor<Srgb>,
54 ) -> Self {
55 let (n_decimations, kernel, kernel_size) = plan_decimated_blur(std_deviation);
57
58 Self {
59 dx,
60 dy,
61 color,
62 std_deviation,
63 edge_mode,
64 n_decimations,
65 kernel,
66 kernel_size,
67 }
68 }
69}
70
71impl FilterEffect for DropShadow {
72 fn execute_lowp(&self, pixmap: &mut Pixmap, layer_manager: &mut LayerManager) {
73 apply_drop_shadow(
74 pixmap,
75 self.dx,
76 self.dy,
77 self.std_deviation,
78 self.n_decimations,
79 &self.kernel[..self.kernel_size],
80 self.color,
81 self.edge_mode,
82 layer_manager,
83 );
84 }
85
86 fn execute_highp(&self, pixmap: &mut Pixmap, layer_manager: &mut LayerManager) {
87 Self::execute_lowp(self, pixmap, layer_manager);
90 }
91}
92
93fn apply_drop_shadow(
100 pixmap: &mut Pixmap,
101 dx: f32,
102 dy: f32,
103 std_deviation: f32,
104 n_decimations: usize,
105 kernel: &[f32],
106 color: AlphaColor<Srgb>,
107 edge_mode: EdgeMode,
108 layer_manager: &mut LayerManager,
109) {
110 let mut shadow_pixmap = pixmap.clone();
112
113 offset_pixels(&mut shadow_pixmap, dx, dy);
115
116 if std_deviation > 0.0 {
118 let scratch =
119 layer_manager.get_scratch_buffer(shadow_pixmap.width(), shadow_pixmap.height());
120 apply_blur(
121 &mut shadow_pixmap,
122 scratch,
123 n_decimations,
124 kernel,
125 edge_mode,
126 );
127 }
128
129 compose_shadow_direct(&shadow_pixmap, pixmap, color);
131}
132
133fn compose_shadow_direct(shadow: &Pixmap, dst: &mut Pixmap, color: AlphaColor<Srgb>) {
138 let width = dst.width();
139 let height = dst.height();
140
141 let shadow_r = (color.components[0] * 255.0).round() as u8;
143 let shadow_g = (color.components[1] * 255.0).round() as u8;
144 let shadow_b = (color.components[2] * 255.0).round() as u8;
145
146 for y in 0..height {
147 for x in 0..width {
148 let alpha = shadow.sample(x, y).a;
150
151 let shadow_alpha = (u8_to_norm(alpha) * color.components[3]).min(1.0);
153 let final_alpha = norm_to_u8(shadow_alpha);
154
155 let alpha_u16 = u16::from(final_alpha);
157 let premultiply = |channel: u8| ((u16::from(channel) * alpha_u16) / 255) as u8;
158
159 let colored_shadow = PremulRgba8 {
160 r: premultiply(shadow_r),
161 g: premultiply(shadow_g),
162 b: premultiply(shadow_b),
163 a: final_alpha,
164 };
165
166 let original_pixel = dst.sample(x, y);
168 let result = compose_src_over(original_pixel, colored_shadow);
169
170 dst.set_pixel(x, y, result);
171 }
172 }
173}
174
175fn compose_src_over(src: PremulRgba8, dst: PremulRgba8) -> PremulRgba8 {
182 let src_a = u8_to_norm(src.a);
183
184 PremulRgba8 {
185 r: src_over_channel(src.r, dst.r, src_a),
186 g: src_over_channel(src.g, dst.g, src_a),
187 b: src_over_channel(src.b, dst.b, src_a),
188 a: src_over_channel(src.a, dst.a, src_a),
189 }
190}
191
192#[inline]
196fn src_over_channel(src: u8, dst: u8, src_alpha: f32) -> u8 {
197 let result = u8_to_norm(src) + u8_to_norm(dst) * (1.0 - src_alpha);
198 norm_to_u8(result)
199}
200
201#[inline]
203fn u8_to_norm(value: u8) -> f32 {
204 value as f32 / 255.0
205}
206
207#[inline]
209fn norm_to_u8(value: f32) -> u8 {
210 (value * 255.0).round() as u8
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216 use vello_common::color::Srgb;
217
218 #[test]
220 fn test_u8_to_norm() {
221 assert_eq!(u8_to_norm(0), 0.0);
222 assert!((u8_to_norm(255) - 1.0).abs() < 1e-6);
223 }
224
225 #[test]
227 fn test_norm_to_u8() {
228 assert_eq!(norm_to_u8(0.0), 0);
229 assert_eq!(norm_to_u8(1.0), 255);
230 assert_eq!(norm_to_u8(0.5), 128); }
232
233 #[test]
235 fn test_conversion_roundtrip() {
236 for value in [0, 1, 50, 127, 128, 200, 254, 255] {
237 let normalized = u8_to_norm(value);
238 let back = norm_to_u8(normalized);
239 assert_eq!(back, value);
240 }
241 }
242
243 #[test]
245 fn test_compose_src_over_opaque_source() {
246 let src = PremulRgba8 {
247 r: 255,
248 g: 0,
249 b: 0,
250 a: 255,
251 }; let dst = PremulRgba8 {
253 r: 0,
254 g: 255,
255 b: 0,
256 a: 255,
257 }; let result = compose_src_over(src, dst);
260 assert_eq!(result.r, 255);
262 assert_eq!(result.g, 0);
263 assert_eq!(result.b, 0);
264 assert_eq!(result.a, 255);
265 }
266
267 #[test]
269 fn test_compose_src_over_transparent_source() {
270 let src = PremulRgba8 {
271 r: 0,
272 g: 0,
273 b: 0,
274 a: 0,
275 };
276 let dst = PremulRgba8 {
277 r: 0,
278 g: 255,
279 b: 0,
280 a: 255,
281 };
282
283 let result = compose_src_over(src, dst);
284 assert_eq!(result.r, 0);
286 assert_eq!(result.g, 255);
287 assert_eq!(result.b, 0);
288 assert_eq!(result.a, 255);
289 }
290
291 #[test]
293 fn test_compose_src_over_semi_transparent() {
294 let src = PremulRgba8 {
295 r: 128,
296 g: 0,
297 b: 0,
298 a: 128,
299 }; let dst = PremulRgba8 {
301 r: 0,
302 g: 128,
303 b: 0,
304 a: 128,
305 }; let result = compose_src_over(src, dst);
308 assert_eq!(
313 result,
314 PremulRgba8 {
315 r: 128,
316 g: 64,
317 b: 0,
318 a: 192,
319 }
320 );
321 }
322
323 #[test]
325 fn test_compose_shadow_color() {
326 let mut shadow_pixmap = Pixmap::new(2, 2);
327 let mut dst_pixmap = Pixmap::new(2, 2);
328
329 shadow_pixmap.set_pixel(
331 0,
332 0,
333 PremulRgba8 {
334 r: 0,
335 g: 0,
336 b: 0,
337 a: 255,
338 },
339 );
340
341 let shadow_color = AlphaColor {
342 components: [1.0, 0.0, 0.0, 1.0], cs: std::marker::PhantomData::<Srgb>,
344 };
345
346 compose_shadow_direct(&shadow_pixmap, &mut dst_pixmap, shadow_color);
347
348 let result = dst_pixmap.sample(0, 0);
350 assert_eq!(result.r, 255);
351 assert_eq!(result.g, 0);
352 assert_eq!(result.b, 0);
353 assert_eq!(result.a, 255);
354 }
355}