Skip to content

Commit

Permalink
Prevent visual glitch when shadow blur width is very high (#4245)
Browse files Browse the repository at this point in the history
* Closes #4238

The comment in the code explains it well, but the short of it is this:
we can't handle a shadow blur width larger than the shadow rectangle, so
we need to clamp the blur. This means smaller things will cast shadows
with a smaller blur width, but that's better than having visual
glitches.
emilk authored Mar 27, 2024
1 parent 947b581 commit a15e6c2
Showing 1 changed file with 20 additions and 4 deletions.
24 changes: 20 additions & 4 deletions crates/epaint/src/shadow.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use emath::NumExt as _;

use super::*;

/// The color and fuzziness of a fuzzy shape.
@@ -16,7 +18,7 @@ pub struct Shadow {

/// The width of the blur, i.e. the width of the fuzzy penumbra.
///
/// A value of 0.0 means no blur.
/// A value of 0.0 means a sharp shadow.
pub blur: f32,

/// Expand the shadow in all directions by this much.
@@ -47,12 +49,26 @@ impl Shadow {
color,
} = *self;

let rect = rect.translate(offset);
let rect = rect.translate(offset).expand(spread);

// We simulate a blurry shadow by tessellating a solid rectangle using a very large feathering.
// Feathering is usually used to make the edges of a shape softer for anti-aliasing.
// The tessellator can't handle blurring/feathering larger than the smallest side of the rect.
// Thats because the tessellator approximate very thin rectangles as line segments,
// and these line segments don't have rounded corners.
// When the feathering is small (the size of a pixel), this is usually fine,
// but here we have a huge feathering to simulate blur,
// so we need to avoid this optimization in the tessellator,
// which is also why we add this rather big epsilon:
let eps = 0.1;
let blur = blur.at_most(rect.size().min_elem() - eps).at_least(0.0);

// TODO(emilk): if blur <= 0, return a simple `Shape::Rect` instead of using the tessellator

let rounding_expansion = spread.abs() + 0.5 * blur;
let rounding = rounding.into() + Rounding::from(rounding_expansion);
let rounding = rounding.into() + Rounding::same(rounding_expansion);

let rect = RectShape::filled(rect.expand(spread), rounding, color);
let rect = RectShape::filled(rect, rounding, color);
let pixels_per_point = 1.0; // doesn't matter here
let font_tex_size = [1; 2]; // unused since we are not tessellating text.
let mut tessellator = Tessellator::new(

0 comments on commit a15e6c2

Please sign in to comment.