From 7cc33b1a1aa44cfa0dd54f238225f1c8a94a334b Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 9 Jan 2025 13:51:12 +0100 Subject: [PATCH] :tada: Render wasm blur support --- frontend/src/app/render_wasm/api.cljs | 19 +++++++++++-- frontend/src/app/render_wasm/shape.cljs | 10 +++++-- render-wasm/docs/serialization.md | 9 ++++++ render-wasm/src/main.rs | 8 ++++++ render-wasm/src/render.rs | 18 ++++++++---- render-wasm/src/shapes.rs | 8 ++++++ render-wasm/src/shapes/blurs.rs | 38 +++++++++++++++++++++++++ render-wasm/src/shapes/renderable.rs | 18 +++++++++++- 8 files changed, 117 insertions(+), 11 deletions(-) create mode 100644 render-wasm/src/shapes/blurs.rs diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 3555d6b25..27d8b5eda 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -346,7 +346,6 @@ [hidden] (h/call internal-module "_set_shape_hidden" hidden)) - (defn- translate-bool-type [bool-type] (case bool-type @@ -364,6 +363,19 @@ [content] (set-shape-path-content content)) +(defn- translate-blur-type + [blur-type] + (case blur-type + :layer-blur 1 + 0)) + +(defn set-shape-blur + [blur] + (let [type (-> blur :type translate-blur-type) + hidden (:hidden blur) + value (:value blur)] + (h/call internal-module "_set_shape_blur" type hidden value))) + (def debounce-render-without-cache (fns/debounce render-without-cache 100)) (defn set-view @@ -395,7 +407,8 @@ opacity (dm/get-prop shape :opacity) hidden (dm/get-prop shape :hidden) content (dm/get-prop shape :content) - bool-content (dm/get-prop shape :bool-content)] + bool-content (dm/get-prop shape :bool-content) + blur (dm/get-prop shape :blur)] (use-shape id) (set-shape-type type) @@ -407,6 +420,8 @@ (set-shape-children children) (set-shape-opacity opacity) (set-shape-hidden hidden) + (when (some? blur) + (set-shape-blur blur)) (when (and (some? content) (= type :path)) (set-shape-path-content content)) (when (some? bool-content) (set-shape-bool-content bool-content)) (let [pending' (concat (set-shape-fills fills) (set-shape-strokes strokes))] diff --git a/frontend/src/app/render_wasm/shape.cljs b/frontend/src/app/render_wasm/shape.cljs index 5fe234ba6..08bba0a95 100644 --- a/frontend/src/app/render_wasm/shape.cljs +++ b/frontend/src/app/render_wasm/shape.cljs @@ -106,7 +106,7 @@ ;; --- SHAPE IMPL -(defn- impl-assoc +(defn- set-wasm-attrs [self k v] (when ^boolean shape/*wasm-sync* (api/use-shape (:id self)) @@ -125,11 +125,16 @@ :hidden (api/set-shape-hidden v) :shapes (api/set-shape-children v) :content (api/set-shape-path-content v) + :blur (api/set-shape-blur v) nil) ;; when something synced with wasm ;; is modified, we need to request ;; a new render. - (api/request-render)) + (api/request-render))) + +(defn- impl-assoc + [self k v] + (set-wasm-attrs self k v) (case k :id (ShapeProxy. v @@ -150,6 +155,7 @@ (defn- impl-dissoc [self k] + (set-wasm-attrs self k nil) (case k :id (ShapeProxy. nil diff --git a/render-wasm/docs/serialization.md b/render-wasm/docs/serialization.md index 9cf862bd9..fddfa274b 100644 --- a/render-wasm/docs/serialization.md +++ b/render-wasm/docs/serialization.md @@ -77,3 +77,12 @@ Bool operations (`bool-type`) are serialized as `u8`: | 2 | Intersection | | 3 | Exclusion | | \_ | Union | + +## BlurType + +Blur types are serialized as `u8`: + +| Value | Field | +| ----- | ------------ | +| 1 | Layer | +| \_ | None | diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 044175666..1578cf16f 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -347,6 +347,14 @@ pub extern "C" fn set_shape_hidden(hidden: bool) { } } +#[no_mangle] +pub extern "C" fn set_shape_blur(blur_type: u8, hidden: bool, value: f32) { + let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); + if let Some(shape) = state.current_shape() { + shape.set_blur(blur_type, hidden, value); + } +} + #[no_mangle] pub extern "C" fn set_shape_path_content() { let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index b0894b331..0f9b1e90c 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -26,6 +26,7 @@ pub trait Renderable { fn hidden(&self) -> bool; fn clip(&self) -> bool; fn children_ids(&self) -> Vec; + fn image_filter(&self, scale: f32) -> Option; } pub(crate) struct CachedSurfaceImage { @@ -156,15 +157,11 @@ impl RenderState { .render(&mut self.drawing_surface, &self.images) .unwrap(); - let mut paint = skia::Paint::default(); - paint.set_blend_mode(element.blend_mode().into()); - paint.set_alpha_f(element.opacity()); - self.drawing_surface.draw( &mut self.final_surface.canvas(), (0.0, 0.0), skia::SamplingOptions::new(skia::FilterMode::Linear, skia::MipmapMode::Nearest), - Some(&paint), + Some(&skia::Paint::default()), ); self.drawing_surface .canvas() @@ -310,8 +307,17 @@ impl RenderState { } } + let mut paint = skia::Paint::default(); + paint.set_blend_mode(element.blend_mode().into()); + paint.set_alpha_f(element.opacity()); + let filter = element.image_filter(self.viewbox.zoom * self.options.dpr()); + if let Some(image_filter) = filter { + paint.set_image_filter(image_filter); + } + + let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint); // This is needed so the next non-children shape does not carry this shape's transform - self.final_surface.canvas().save(); + self.final_surface.canvas().save_layer(&layer_rec); self.drawing_surface.canvas().save(); if !root_id.is_nil() { diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index df390da35..e9463d3e7 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -4,6 +4,7 @@ use uuid::Uuid; use crate::render::{BlendMode, Renderable}; +mod blurs; mod bools; mod fills; mod images; @@ -12,6 +13,7 @@ mod paths; mod renderable; mod strokes; +pub use blurs::*; pub use bools::*; pub use fills::*; pub use images::*; @@ -42,6 +44,7 @@ pub struct Shape { fills: Vec, strokes: Vec, blend_mode: BlendMode, + blur: Blur, opacity: f32, hidden: bool, } @@ -61,6 +64,7 @@ impl Shape { blend_mode: BlendMode::default(), opacity: 1., hidden: false, + blur: Blur::default(), } } @@ -105,6 +109,10 @@ impl Shape { self.hidden = value; } + pub fn set_blur(&mut self, blur_type: u8, hidden: bool, value: f32) { + self.blur = Blur::new(blur_type, hidden, value); + } + pub fn add_child(&mut self, id: Uuid) { self.children.push(id); } diff --git a/render-wasm/src/shapes/blurs.rs b/render-wasm/src/shapes/blurs.rs new file mode 100644 index 000000000..7bc857cf0 --- /dev/null +++ b/render-wasm/src/shapes/blurs.rs @@ -0,0 +1,38 @@ +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum BlurType { + None, + Layer, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Blur { + pub hidden: bool, + pub blur_type: BlurType, + pub value: f32, +} + +impl From for BlurType { + fn from(value: u8) -> Self { + match value { + 1 => BlurType::Layer, + _ => BlurType::None, + } + } +} + +impl Blur { + pub fn default() -> Self { + Blur { + blur_type: BlurType::None, + hidden: true, + value: 0., + } + } + pub fn new(blur_type: u8, hidden: bool, value: f32) -> Self { + Blur { + blur_type: BlurType::from(blur_type), + hidden, + value, + } + } +} diff --git a/render-wasm/src/shapes/renderable.rs b/render-wasm/src/shapes/renderable.rs index aa6558690..05346bf28 100644 --- a/render-wasm/src/shapes/renderable.rs +++ b/render-wasm/src/shapes/renderable.rs @@ -1,7 +1,7 @@ use skia_safe as skia; use uuid::Uuid; -use super::{Fill, Image, Kind, Path, Shape, Stroke, StrokeCap, StrokeKind}; +use super::{BlurType, Fill, Image, Kind, Path, Shape, Stroke, StrokeCap, StrokeKind}; use crate::math::Rect; use crate::render::{ImageStore, Renderable}; @@ -70,6 +70,22 @@ impl Renderable for Shape { self.children.clone() } } + + fn image_filter(&self, scale: f32) -> Option { + if !self.blur.hidden { + match self.blur.blur_type { + BlurType::None => None, + BlurType::Layer => skia::image_filters::blur( + (self.blur.value * scale, self.blur.value * scale), + None, + None, + None, + ), + } + } else { + None + } + } } fn render_fill(