From 6cbaacf1e04e47e0a839d0f7179f8692b520e9e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Fri, 14 Feb 2025 13:46:30 +0100 Subject: [PATCH] :tada: Implement inner shadows (wasm) (#5767) * :tada: Implement inner shadows (wasm) * :bug: Fix reset canvas problem --------- Co-authored-by: alonso.torres --- .../app/main/ui/workspace/viewport_wasm.cljs | 4 +- frontend/src/app/render_wasm/helpers.cljc | 1 - render-wasm/src/render.rs | 50 +++++++++++++--- render-wasm/src/render/shadows.rs | 26 +++++++- render-wasm/src/shapes.rs | 6 ++ render-wasm/src/shapes/shadows.rs | 60 +++++++++++++++---- 6 files changed, 127 insertions(+), 20 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs index 46e7d0772..25284edf5 100644 --- a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs +++ b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs @@ -12,6 +12,7 @@ [app.common.data.macros :as dm] [app.common.files.helpers :as cfh] [app.common.geom.shapes :as gsh] + [app.common.types.shape :as cts] [app.common.types.shape-tree :as ctt] [app.common.types.shape.layout :as ctl] [app.main.data.workspace.modifiers :as dwm] @@ -111,7 +112,8 @@ text-modifiers (mf/deref refs/workspace-text-modifier) objects-modified (mf/with-memo [base-objects text-modifiers modifiers] - (apply-modifiers-to-selected selected base-objects text-modifiers modifiers)) + (binding [cts/*wasm-sync* false] + (apply-modifiers-to-selected selected base-objects text-modifiers modifiers))) selected-shapes (keep (d/getf objects-modified) selected) diff --git a/frontend/src/app/render_wasm/helpers.cljc b/frontend/src/app/render_wasm/helpers.cljc index 55bc15c19..671c12cc2 100644 --- a/frontend/src/app/render_wasm/helpers.cljc +++ b/frontend/src/app/render_wasm/helpers.cljc @@ -13,4 +13,3 @@ (let [fn-sym (with-meta (gensym "fn-") {:tag 'function})] `(let [~fn-sym (cljs.core/unchecked-get ~module ~name)] (~fn-sym ~@params)))) - diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 4ce8204f9..55964ea9d 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -1,11 +1,10 @@ -use skia::Contains; use skia_safe as skia; use std::collections::HashMap; use uuid::Uuid; use crate::math; use crate::view::Viewbox; -use skia::Matrix; +use skia::{Contains, Matrix}; mod blend; mod cache; @@ -60,6 +59,7 @@ pub(crate) struct RenderState { pub render_surface: skia::Surface, pub drawing_surface: skia::Surface, pub shadow_surface: skia::Surface, + pub overlay_surface: skia::Surface, pub debug_surface: skia::Surface, pub font_provider: skia::textlayout::TypefaceFontProvider, pub cached_surface_image: Option, @@ -86,6 +86,9 @@ impl RenderState { let shadow_surface = final_surface .new_surface_with_dimensions((width, height)) .unwrap(); + let overlay_surface = final_surface + .new_surface_with_dimensions((width, height)) + .unwrap(); let drawing_surface = final_surface .new_surface_with_dimensions((width, height)) .unwrap(); @@ -102,6 +105,7 @@ impl RenderState { gpu_state, final_surface, render_surface, + overlay_surface, shadow_surface, drawing_surface, debug_surface, @@ -164,6 +168,10 @@ impl RenderState { .final_surface .new_surface_with_dimensions((dpr_width, dpr_height)) .unwrap(); + self.overlay_surface = self + .final_surface + .new_surface_with_dimensions((dpr_width, dpr_height)) + .unwrap(); self.shadow_surface = self .final_surface .new_surface_with_dimensions((dpr_width, dpr_height)) @@ -201,6 +209,10 @@ impl RenderState { .canvas() .clear(self.background_color) .reset_matrix(); + self.overlay_surface + .canvas() + .clear(self.background_color) + .reset_matrix(); self.debug_surface .canvas() .clear(skia::Color::TRANSPARENT) @@ -232,8 +244,20 @@ impl RenderState { .context .flush_and_submit_surface(&mut self.render_surface, None); - self.shadow_surface.canvas().clear(skia::Color::TRANSPARENT); + self.gpu_state + .context + .flush_and_submit_surface(&mut self.overlay_surface, None); + self.overlay_surface.draw( + &mut self.render_surface.canvas(), + (0.0, 0.0), + skia::SamplingOptions::new(skia::FilterMode::Linear, skia::MipmapMode::Nearest), + None, + ); + self.shadow_surface.canvas().clear(skia::Color::TRANSPARENT); + self.overlay_surface + .canvas() + .clear(skia::Color::TRANSPARENT); self.drawing_surface .canvas() .clear(skia::Color::TRANSPARENT); @@ -296,13 +320,25 @@ impl RenderState { for stroke in shape.strokes().rev() { strokes::render(self, shape, stroke); } + + for shadow in shape.inner_shadows().rev().filter(|s| !s.hidden()) { + shadows::render_inner_shadow( + self, + shadow, + self.viewbox.zoom * self.options.dpr(), + ); + } + + for shadow in shape.drop_shadows().rev().filter(|s| !s.hidden()) { + shadows::render_drop_shadow( + self, + shadow, + self.viewbox.zoom * self.options.dpr(), + ); + } } }; - for shadow in shape.drop_shadows().rev().filter(|s| !s.hidden()) { - shadows::render_drop_shadow(self, shadow, self.viewbox.zoom * self.options.dpr()); - } - self.apply_drawing_to_render_canvas(); } diff --git a/render-wasm/src/render/shadows.rs b/render-wasm/src/render/shadows.rs index dd9fa9ea2..bb3a55e04 100644 --- a/render-wasm/src/render/shadows.rs +++ b/render-wasm/src/render/shadows.rs @@ -4,7 +4,7 @@ use super::RenderState; use crate::shapes::Shadow; pub fn render_drop_shadow(render_state: &mut RenderState, shadow: &Shadow, scale: f32) { - let shadow_paint = shadow.to_paint(true, scale); + let shadow_paint = shadow.to_paint(scale); render_state.drawing_surface.draw( &mut render_state.shadow_surface.canvas(), (0.0, 0.0), @@ -18,6 +18,30 @@ pub fn render_drop_shadow(render_state: &mut RenderState, shadow: &Shadow, scale skia::SamplingOptions::new(skia::FilterMode::Linear, skia::MipmapMode::Nearest), Some(&skia::Paint::default()), ); + + render_state + .shadow_surface + .canvas() + .clear(skia::Color::TRANSPARENT); +} + +pub fn render_inner_shadow(render_state: &mut RenderState, shadow: &Shadow, scale: f32) { + let shadow_paint = shadow.to_paint(scale); + + render_state.drawing_surface.draw( + render_state.shadow_surface.canvas(), + (0.0, 0.0), + skia::SamplingOptions::new(skia::FilterMode::Linear, skia::MipmapMode::Nearest), + Some(&shadow_paint), + ); + + render_state.shadow_surface.draw( + &mut render_state.overlay_surface.canvas(), + (0.0, 0.0), + skia::SamplingOptions::new(skia::FilterMode::Linear, skia::MipmapMode::Nearest), + None, + ); + render_state .shadow_surface .canvas() diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index 95755b31c..e776f8b5b 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -399,6 +399,12 @@ impl Shape { .filter(|shadow| shadow.style() == ShadowStyle::Drop) } + pub fn inner_shadows(&self) -> impl DoubleEndedIterator { + self.shadows + .iter() + .filter(|shadow| shadow.style() == ShadowStyle::Inner) + } + pub fn to_path_transform(&self) -> Option { match self.kind { Kind::Path(_) | Kind::Bool(_, _) => { diff --git a/render-wasm/src/shapes/shadows.rs b/render-wasm/src/shapes/shadows.rs index 94f020979..acf51fcd4 100644 --- a/render-wasm/src/shapes/shadows.rs +++ b/render-wasm/src/shapes/shadows.rs @@ -1,4 +1,4 @@ -use skia_safe::{self as skia, image_filters}; +use skia_safe::{self as skia, image_filters, ImageFilter}; use super::Color; @@ -26,10 +26,10 @@ impl Default for ShadowStyle { #[derive(Debug, Clone, Copy, PartialEq)] pub struct Shadow { - color: Color, - blur: f32, - spread: f32, - offset: (f32, f32), + pub color: Color, + pub blur: f32, + pub spread: f32, + pub offset: (f32, f32), style: ShadowStyle, hidden: bool, } @@ -62,8 +62,21 @@ impl Shadow { self.hidden } - pub fn to_paint(&self, dilate: bool, scale: f32) -> skia::Paint { + pub fn to_paint(&self, scale: f32) -> skia::Paint { let mut paint = skia::Paint::default(); + + let image_filter = match self.style { + ShadowStyle::Drop => self.drop_shadow_filters(scale), + ShadowStyle::Inner => self.inner_shadow_filters(scale), + }; + + paint.set_image_filter(image_filter); + paint.set_anti_alias(true); + + paint + } + + fn drop_shadow_filters(&self, scale: f32) -> Option { let mut filter = image_filters::drop_shadow_only( (self.offset.0 * scale, self.offset.1 * scale), (self.blur * scale, self.blur * scale), @@ -73,14 +86,41 @@ impl Shadow { None, ); - if dilate { + if self.spread > 0. { filter = image_filters::dilate((self.spread * scale, self.spread * scale), filter, None); } - paint.set_image_filter(filter); - paint.set_anti_alias(true); + filter + } - paint + fn inner_shadow_filters(&self, scale: f32) -> Option { + let sigma = self.blur * 0.5; + let mut filter = skia::image_filters::drop_shadow_only( + (self.offset.0 * scale, self.offset.1 * scale), // DPR? + (sigma * scale, sigma * scale), + skia::Color::BLACK, + None, + None, + None, + ); + + filter = skia::image_filters::color_filter( + skia::color_filters::blend(self.color, skia::BlendMode::SrcOut).unwrap(), + filter, + None, + ); + + if self.spread > 0. { + filter = skia::image_filters::dilate( + (self.spread * scale, self.spread * scale), + filter, + None, + ); + } + + filter = skia::image_filters::blend(skia::BlendMode::SrcIn, None, filter, None); + + filter } }