diff --git a/graphics/src/damage.rs b/graphics/src/damage.rs index 2f29956e8d..60bcaa85f3 100644 --- a/graphics/src/damage.rs +++ b/graphics/src/damage.rs @@ -40,9 +40,26 @@ impl Damage for Primitive { bounds.expand(1.5) } - Self::Quad { bounds, .. } - | Self::Image { bounds, .. } - | Self::Svg { bounds, .. } => bounds.expand(1.0), + Self::Quad { + bounds, + shadow_offset, + shadow_softness, + .. + } => { + let shadow_bounds = Rectangle { + x: bounds.x + shadow_offset.x.min(0.0) - shadow_softness, + y: bounds.y + shadow_offset.y.min(0.0) - shadow_softness, + width: bounds.width + + (shadow_offset.x.max(0.0) + shadow_softness) * 2.0, + height: bounds.height + + (shadow_offset.y.max(0.0) + shadow_softness) * 2.0, + }; + + shadow_bounds.expand(1.0) + } + Self::Image { bounds, .. } | Self::Svg { bounds, .. } => { + bounds.expand(1.0) + } Self::Clip { bounds, .. } => bounds.expand(1.0), Self::Group { primitives } => primitives .iter() diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index a9ac458239..41c9f8cee6 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -159,7 +159,9 @@ impl Backend { border_radius, border_width, border_color, - .. + shadow_color, + shadow_offset, + shadow_softness, } => { let physical_bounds = (*bounds + translation) * scale_factor; @@ -189,6 +191,128 @@ impl Backend { } let path = rounded_rectangle(*bounds, fill_border_radius); + if shadow_color.a > 0.0 { + fn smoothstep(a: f32, b: f32, x: f32) -> f32 { + let x = x.clamp(a, b); + let x = x.clamp(0.0, (x - a) / (b - a)); + + x * x * (3.0 - 2.0 * x) + } + + fn rounded_box_sdf( + center_distance: Vector, + size: Size, + radius: [f32; 4], + ) -> f32 { + let radius = match ( + center_distance.x > 0.0, + center_distance.y > 0.0, + ) { + (false, false) => radius[0], + (true, false) => radius[1], + (true, true) => radius[2], + (false, true) => radius[3], + }; + + let x = (center_distance.x.abs() - size.width + radius) + .max(0.0); + let y = (center_distance.y.abs() - size.height + + radius) + .max(0.0); + + (x * x + y * y).sqrt() - radius + } + + let shadow_bounds = Rectangle { + x: bounds.x + shadow_offset.x.min(0.0) + - shadow_softness, + y: bounds.y + shadow_offset.y.min(0.0) + - shadow_softness, + width: bounds.width + + (shadow_offset.x.max(0.0) + shadow_softness) + * 2.0, + height: bounds.height + + (shadow_offset.y.max(0.0) + shadow_softness) + * 2.0, + } * scale_factor; + + if let Some(shadow_color) = tiny_skia::Color::from_rgba( + shadow_color.r, + shadow_color.g, + shadow_color.b, + shadow_color.a, + ) { + let mut bytes = vec![]; + let radius = [ + fill_border_radius[0] * scale_factor, + fill_border_radius[1] * scale_factor, + fill_border_radius[2] * scale_factor, + fill_border_radius[3] * scale_factor, + ]; + for y in shadow_bounds.y as u32 + ..(shadow_bounds.y as u32 + + shadow_bounds.height as u32) + { + for x in shadow_bounds.x as u32 + ..(shadow_bounds.x as u32 + + shadow_bounds.width as u32) + { + let shadow_distance = rounded_box_sdf( + Vector { + x: x as f32 + - physical_bounds.position().x + - shadow_offset.x * scale_factor + - (physical_bounds.width / 2.0), + y: y as f32 + - physical_bounds.position().y + - shadow_offset.y * scale_factor + - (physical_bounds.height / 2.0), + }, + Size::new( + physical_bounds.width / 2.0, + physical_bounds.height / 2.0, + ), + radius, + ); + let shadow_alpha = 1.0 + - smoothstep( + -shadow_softness * scale_factor, + *shadow_softness * scale_factor, + shadow_distance, + ); + + let mut color = shadow_color; + color.apply_opacity(shadow_alpha); + + bytes.extend(bytemuck::bytes_of( + &color.to_color_u8().premultiply(), + )); + } + } + + if let Some(p) = tiny_skia::Pixmap::from_vec( + bytes, + tiny_skia::IntSize::from_wh( + shadow_bounds.width as u32, + shadow_bounds.height as u32, + ) + .unwrap(), + ) { + pixels.draw_pixmap( + shadow_bounds.x as i32, + shadow_bounds.y as i32, + p.as_ref(), + &tiny_skia::PixmapPaint { + blend_mode: tiny_skia::BlendMode::SourceOver, + ..Default::default() + }, + Default::default(), + None, + ) + } + } + } + pixels.fill_path( &path, &tiny_skia::Paint {