diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index f1611ec2b2..271a7bc38d 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -23,7 +23,8 @@ pub use surfaces::{SurfaceId, Surfaces}; use crate::performance; use crate::shapes::{ - all_with_ancestors, Blur, BlurType, Corners, Fill, Shadow, Shape, SolidColor, Stroke, Type, + all_with_ancestors, radius_to_sigma, Blur, BlurType, Corners, Fill, Shadow, Shape, SolidColor, + Stroke, Type, }; use crate::state::{ShapesPoolMutRef, ShapesPoolRef}; use crate::tiles::{self, PendingTiles, TileRect}; @@ -816,7 +817,7 @@ impl RenderState { { if let Some(blur) = shape.blur.filter(|b| !b.hidden) { shape.to_mut().set_blur(None); - Some(blur.value) + Some(blur.sigma()) } else { None } @@ -1437,7 +1438,7 @@ impl RenderState { if !self.options.is_fast_mode() { if let Some(frame_blur) = Self::frame_clip_layer_blur(element) { let scale = self.get_scale(); - let sigma = frame_blur.value * scale; + let sigma = radius_to_sigma(frame_blur.value * scale); if let Some(filter) = skia::image_filters::blur((sigma, sigma), None, None, None) { @@ -1630,8 +1631,10 @@ impl RenderState { let mut plain_shape = Cow::Borrowed(shape); let combined_blur = Self::combine_blur_values(self.combined_layer_blur(shape.blur), extra_layer_blur); - let blur_filter = combined_blur - .and_then(|blur| skia::image_filters::blur((blur.value, blur.value), None, None, None)); + let blur_filter = combined_blur.and_then(|blur| { + let sigma = blur.sigma(); + skia::image_filters::blur((sigma, sigma), None, None, None) + }); let use_low_zoom_path = scale <= 1.0 && combined_blur.is_none(); @@ -1714,12 +1717,8 @@ impl RenderState { // Create filter with blur only (no offset, no spread - handled geometrically) let blur_only_filter = if transformed_shadow.blur > 0.0 { - Some(skia::image_filters::blur( - (transformed_shadow.blur, transformed_shadow.blur), - None, - None, - None, - )) + let sigma = radius_to_sigma(transformed_shadow.blur); + Some(skia::image_filters::blur((sigma, sigma), None, None, None)) } else { None }; diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index ef12164896..390391e11b 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -30,7 +30,7 @@ pub mod text_paths; mod transform; pub use blend::*; -pub use blurs::*; +pub use blurs::{radius_to_sigma, Blur, BlurType}; pub use bools::*; pub use corners::*; pub use fills::*; @@ -1004,7 +1004,8 @@ impl Shape { } } - let blur = skia::image_filters::blur((children_blur, children_blur), None, None, None); + let sigma = radius_to_sigma(children_blur); + let blur = skia::image_filters::blur((sigma, sigma), None, None, None); if let Some(image_filter) = blur { let blur_bounds = image_filter.compute_fast_bounds(rect); rect.join(blur_bounds); @@ -1236,12 +1237,10 @@ impl Shape { self.blur .filter(|blur| !blur.hidden) .and_then(|blur| match blur.blur_type { - BlurType::LayerBlur => skia::image_filters::blur( - (blur.value * scale, blur.value * scale), - None, - None, - None, - ), + BlurType::LayerBlur => { + let sigma = radius_to_sigma(blur.value * scale); + skia::image_filters::blur((sigma, sigma), None, None, None) + } }) } @@ -1251,7 +1250,8 @@ impl Shape { .filter(|blur| !blur.hidden) .and_then(|blur| match blur.blur_type { BlurType::LayerBlur => { - skia::MaskFilter::blur(skia::BlurStyle::Normal, blur.value * scale, Some(true)) + let sigma = radius_to_sigma(blur.value * scale); + skia::MaskFilter::blur(skia::BlurStyle::Normal, sigma, Some(true)) } }) } diff --git a/render-wasm/src/shapes/blurs.rs b/render-wasm/src/shapes/blurs.rs index 4232f0ee1c..543e11efa8 100644 --- a/render-wasm/src/shapes/blurs.rs +++ b/render-wasm/src/shapes/blurs.rs @@ -1,3 +1,17 @@ +/// Skia's kBLUR_SIGMA_SCALE (1/√3 ≈ 0.57735). Used to convert blur radius to sigma +const BLUR_SIGMA_SCALE: f32 = 0.577_350_27; + +/// Converts a blur radius to sigma (standard deviation) for Skia's blur APIs. +/// Matches Skia's SkBlurMask::ConvertRadiusToSigma: +#[inline] +pub fn radius_to_sigma(radius: f32) -> f32 { + if radius > 0.0 { + BLUR_SIGMA_SCALE * radius + 0.5 + } else { + 0.0 + } +} + #[derive(Debug, Clone, Copy, PartialEq)] pub enum BlurType { LayerBlur, @@ -22,4 +36,11 @@ impl Blur { pub fn scale_content(&mut self, value: f32) { self.value *= value; } + + /// Returns the sigma (standard deviation) for Skia blur APIs. + /// The stored `value` is a blur radius; this converts it to sigma. + #[inline] + pub fn sigma(&self) -> f32 { + radius_to_sigma(self.value) + } } diff --git a/render-wasm/src/shapes/shadows.rs b/render-wasm/src/shapes/shadows.rs index 71e09a493c..6cfa912659 100644 --- a/render-wasm/src/shapes/shadows.rs +++ b/render-wasm/src/shapes/shadows.rs @@ -1,5 +1,6 @@ use skia_safe::{self as skia, image_filters, ImageFilter, Paint}; +use super::blurs::radius_to_sigma; use super::Color; use crate::render::filters::compose_filters; @@ -48,9 +49,10 @@ impl Shadow { } pub fn get_drop_shadow_filter(&self) -> Option { + let sigma = radius_to_sigma(self.blur); let mut filter = image_filters::drop_shadow_only( (self.offset.0, self.offset.1), - (self.blur, self.blur), + (sigma, sigma), self.color, None, None, @@ -78,7 +80,7 @@ impl Shadow { } pub fn get_inner_shadow_filter(&self) -> Option { - let sigma = self.blur * 0.5; + let sigma = radius_to_sigma(self.blur); let mut filter = skia::image_filters::drop_shadow_only( (self.offset.0, self.offset.1), // DPR? (sigma, sigma),