diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.scss b/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.scss index 606a614f87..d738d59e21 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.scss +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.scss @@ -76,7 +76,7 @@ } .disabled-label-tooltip { - flex-grow:1; + flex-grow: 1; } .label { diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 73a646c5f7..f070f3b4c8 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -944,21 +944,21 @@ (defn set-shape-blur [blur] - (if (some? blur) - (let [type (sr/translate-blur-type :layer-blur) - hidden (:hidden blur) - value (:value blur)] - (h/call wasm/internal-module "_set_shape_blur" type hidden value)) - (h/call wasm/internal-module "_clear_shape_blur"))) + (let [type (sr/translate-blur-type :layer-blur)] + (if (some? blur) + (let [hidden (:hidden blur) + value (:value blur)] + (h/call wasm/internal-module "_set_shape_blur" type hidden value)) + (h/call wasm/internal-module "_clear_shape_blur" type)))) (defn set-shape-background-blur [background-blur] - (if (some? background-blur) - (let [type (sr/translate-blur-type :background-blur) - hidden (:hidden background-blur) - value (:value background-blur)] - (h/call wasm/internal-module "_set_shape_blur" type hidden value)) - (h/call wasm/internal-module "_clear_shape_blur"))) + (let [type (sr/translate-blur-type :background-blur)] + (if (some? background-blur) + (let [hidden (:hidden background-blur) + value (:value background-blur)] + (h/call wasm/internal-module "_set_shape_blur" type hidden value)) + (h/call wasm/internal-module "_clear_shape_blur" type)))) (defn set-shape-corners [corners] diff --git a/frontend/src/app/render_wasm/shape.cljs b/frontend/src/app/render_wasm/shape.cljs index 54c292f59f..feed61d674 100644 --- a/frontend/src/app/render_wasm/shape.cljs +++ b/frontend/src/app/render_wasm/shape.cljs @@ -130,7 +130,6 @@ (defn- set-wasm-attr! [shape k] (when wasm/context-initialized? - ;;TODO_BLUR: ask about this, (let [shape (case k :svg-attrs (svg-filters/apply-svg-derived (assoc shape :svg-attrs (get shape :svg-attrs))) (:fills :blur :shadow) (svg-filters/apply-svg-derived shape) diff --git a/frontend/src/app/render_wasm/wasm.cljs b/frontend/src/app/render_wasm/wasm.cljs index afca7f0ba1..b8c68a85cd 100644 --- a/frontend/src/app/render_wasm/wasm.cljs +++ b/frontend/src/app/render_wasm/wasm.cljs @@ -44,7 +44,6 @@ (set! context-initialized? false) (reset! context-lost? false)) -;; TODO_BLUR: ask for blur-type?? (defonce serializers #js {:blur-type shared/RawBlurType :blend-mode shared/RawBlendMode diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index d0b015fa46..e3159f9501 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -24,7 +24,6 @@ use std::collections::{HashMap, HashSet}; use options::RenderOptions; pub use surfaces::{SurfaceId, Surfaces}; -// TODO_BLUR: should we add here BackgroundBlur use crate::error::{Error, Result}; use crate::math; use crate::shapes::{ @@ -371,7 +370,7 @@ pub(crate) struct RenderState { // migration to remove group-level fills is completed, this code should be removed. // Frames contained in groups must reset this nested_fills stack pushing a new empty vector. pub nested_fills: Vec>, - pub nested_blurs: Vec>, // FIXME: why is this an option?, sholud be an option now? TODO_BLUR + pub nested_blurs: Vec>, // FIXME: why is this an option? pub nested_shadows: Vec>, pub show_grid: Option, pub rulers: RulerState, @@ -588,7 +587,7 @@ impl RenderState { backbuffer_crop_cache: HashMap::default(), }) } - /// TODO_BLUR: ? + /// Combines every visible layer blur currently active (ancestors + shape) /// into a single equivalent blur. Layer blur radii compound by adding their /// variances (σ² = radius²), so we: @@ -650,10 +649,7 @@ impl RenderState { { return; } - let blur = match shape - .blur - .filter(|b| !b.hidden && b.blur_type == BlurType::BackgroundBlur) - { + let blur = match shape.visible_background_blur() { Some(blur) => blur, None => return, }; @@ -1203,6 +1199,7 @@ impl RenderState { && parent_shadows.is_none() && !shape.needs_layer() && shape.blur.is_none() + && shape.background_blur.is_none() && !has_inherited_blur && shape.shadows.is_empty() && shape.transform.is_identity() @@ -1312,20 +1309,9 @@ impl RenderState { // We don't want to change the value in the global state let mut shape: Cow = Cow::Borrowed(shape); - // Remove background blur from the shape so it doesn't get processed - // as a layer blur. The actual rendering is done before the save_layer - // in render_background_blur() so it's independent of shape opacity. - if !skip_effects - && apply_to_current_surface - && fills_surface_id == SurfaceId::Fills - && !matches!(shape.shape_type, Type::Text(_)) - && !matches!(shape.shape_type, Type::SVGRaw(_)) - && shape - .blur - .is_some_and(|b| !b.hidden && b.blur_type == BlurType::BackgroundBlur) - { - shape.to_mut().set_blur(None); - } + // Background blur is stored separately (shape.background_blur) and is + // rendered before the save_layer in render_background_blur(), so here + // shape.blur only ever holds a layer blur. let frame_has_blur = Self::frame_clip_layer_blur(&shape).is_some(); let shape_has_blur = shape.blur.is_some(); @@ -2842,6 +2828,7 @@ impl RenderState { plain_shape_mut.clear_shadows(); plain_shape_mut.blur = None; + plain_shape_mut.background_blur = None; // Shadow rendering uses a single render_shape call with no render_shape_exit, // so strokes must be drawn here. Disable clip_content to avoid skip_strokes @@ -3645,10 +3632,8 @@ impl RenderState { // assigned to this tile) because the blur snapshots Current // which must contain the shapes behind it. let tile_has_bg_blur = ids.iter().any(|id| { - tree.get(id).is_some_and(|s| { - s.blur - .is_some_and(|b| !b.hidden && b.blur_type == BlurType::BackgroundBlur) - }) + tree.get(id) + .is_some_and(|s| s.visible_background_blur().is_some()) }); // We only need first level shapes, in the same order as the parent node. diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index d651e3f457..95c704ceff 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -188,6 +188,7 @@ pub struct Shape { pub blend_mode: BlendMode, pub vertical_align: VerticalAlign, pub blur: Option, + pub background_blur: Option, pub opacity: f32, pub hidden: bool, pub svg: Option, @@ -291,6 +292,7 @@ impl Shape { opacity: 1., hidden: false, blur: None, + background_blur: None, svg: None, svg_attrs: None, shadows: Vec::with_capacity(1), @@ -314,6 +316,10 @@ impl Shape { blur.scale_content(value); } + if let Some(background_blur) = self.background_blur.as_mut() { + background_blur.scale_content(value); + } + self.layout_item .iter_mut() .for_each(|i| i.scale_content(value)); @@ -631,6 +637,15 @@ impl Shape { self.blur = blur; } + pub fn set_background_blur(&mut self, blur: Option) { + self.invalidate_extrect(); + self.background_blur = blur; + } + + pub fn visible_background_blur(&self) -> Option { + self.background_blur.filter(|blur| !blur.hidden) + } + pub fn add_child(&mut self, id: Uuid) { self.children.push(id); } @@ -1421,6 +1436,7 @@ impl Shape { } self.blur.is_none() + && self.background_blur.is_none() && self.shadows.is_empty() && (self.opacity - 1.0).abs() <= 1e-4 && self.blend_mode().0 == skia::BlendMode::SrcOver @@ -1665,7 +1681,7 @@ impl Shape { return false; } - if self.blur.is_some() { + if self.blur.is_some() || self.background_blur.is_some() { return false; } @@ -1805,6 +1821,38 @@ mod tests { ) } + #[test] + fn layer_blur_and_background_blur_can_coexist() { + let mut shape = any_shape(); + + let layer_blur = Blur::new(BlurType::LayerBlur, false, 4.0); + let background_blur = Blur::new(BlurType::BackgroundBlur, false, 8.0); + + shape.set_blur(Some(layer_blur)); + shape.set_background_blur(Some(background_blur)); + + assert_eq!(shape.blur, Some(layer_blur)); + assert_eq!(shape.background_blur, Some(background_blur)); + assert_eq!(shape.visible_background_blur(), Some(background_blur)); + + // Clearing one type must not affect the other. + shape.set_blur(None); + assert_eq!(shape.blur, None); + assert_eq!(shape.background_blur, Some(background_blur)); + + shape.set_blur(Some(layer_blur)); + shape.set_background_blur(None); + assert_eq!(shape.blur, Some(layer_blur)); + assert_eq!(shape.background_blur, None); + } + + #[test] + fn hidden_background_blur_is_not_visible() { + let mut shape = any_shape(); + shape.set_background_blur(Some(Blur::new(BlurType::BackgroundBlur, true, 8.0))); + assert_eq!(shape.visible_background_blur(), None); + } + #[test] fn test_set_corners() { let mut shape = any_shape(); diff --git a/render-wasm/src/wasm/blurs.rs b/render-wasm/src/wasm/blurs.rs index 022439ab44..1fcd8448bd 100644 --- a/render-wasm/src/wasm/blurs.rs +++ b/render-wasm/src/wasm/blurs.rs @@ -29,14 +29,21 @@ impl From for BlurType { #[no_mangle] pub extern "C" fn set_shape_blur(blur_type: u8, hidden: bool, value: f32) { with_current_shape_mut!(state, |shape: &mut Shape| { - let blur_type = RawBlurType::from(blur_type); - shape.set_blur(Some(Blur::new(blur_type.into(), hidden, value))); + let blur_type: BlurType = RawBlurType::from(blur_type).into(); + let blur = Some(Blur::new(blur_type, hidden, value)); + match blur_type { + BlurType::LayerBlur => shape.set_blur(blur), + BlurType::BackgroundBlur => shape.set_background_blur(blur), + } }); } #[no_mangle] -pub extern "C" fn clear_shape_blur() { +pub extern "C" fn clear_shape_blur(blur_type: u8) { with_current_shape_mut!(state, |shape: &mut Shape| { - shape.set_blur(None); + match RawBlurType::from(blur_type).into() { + BlurType::LayerBlur => shape.set_blur(None), + BlurType::BackgroundBlur => shape.set_background_blur(None), + } }); }