🎉 Implement inner shadows (wasm) (#5767)

* 🎉 Implement inner shadows (wasm)

* 🐛 Fix reset canvas problem

---------

Co-authored-by: alonso.torres <alonso.torres@kaleidos.net>
This commit is contained in:
Belén Albeza 2025-02-14 13:46:30 +01:00 committed by GitHub
parent 2ffb77cb4d
commit 6cbaacf1e0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 127 additions and 20 deletions

View file

@ -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)

View file

@ -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))))

View file

@ -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<CachedSurfaceImage>,
@ -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,12 +320,24 @@ 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());
shadows::render_drop_shadow(
self,
shadow,
self.viewbox.zoom * self.options.dpr(),
);
}
}
};
self.apply_drawing_to_render_canvas();
}

View file

@ -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()

View file

@ -399,6 +399,12 @@ impl Shape {
.filter(|shadow| shadow.style() == ShadowStyle::Drop)
}
pub fn inner_shadows(&self) -> impl DoubleEndedIterator<Item = &Shadow> {
self.shadows
.iter()
.filter(|shadow| shadow.style() == ShadowStyle::Inner)
}
pub fn to_path_transform(&self) -> Option<skia::Matrix> {
match self.kind {
Kind::Path(_) | Kind::Bool(_, _) => {

View file

@ -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<ImageFilter> {
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<ImageFilter> {
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
}
}