0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-04-17 01:12:16 -05:00

🎉 Render drop shadows (wasm) (#5693)

Co-authored-by: Belén Albeza <belen@hey.com>
This commit is contained in:
Alejandro 2025-01-28 15:10:06 +01:00 committed by GitHub
parent 27dce6fcfa
commit 09131f7533
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 217 additions and 3 deletions

View file

@ -469,6 +469,32 @@
r4 (or (get corners 3) 0)]
(h/call internal-module "_set_shape_corners" r1 r2 r3 r4)))
(defn- translate-shadow-style
[style]
(case style
:drop-shadow 0
:inner-shadow 1
0))
(defn set-shape-shadows
[shadows]
(h/call internal-module "_clear_shape_shadows")
(let [total-shadows (count shadows)]
(loop [index 0]
(when (< index total-shadows)
(let [shadow (nth shadows index)
color (dm/get-prop shadow :color)
blur (dm/get-prop shadow :blur)
rgba (rgba-from-hex (dm/get-prop color :color) (dm/get-prop color :opacity))
hidden (dm/get-prop shadow :hidden)
x (dm/get-prop shadow :offset-x)
y (dm/get-prop shadow :offset-y)
spread (dm/get-prop shadow :spread)
style (dm/get-prop shadow :style)]
(h/call internal-module "_add_shape_shadow" rgba blur spread x y (translate-shadow-style style) hidden)
(recur (inc index)))))))
(def debounce-render-without-cache (fns/debounce render-without-cache 100))
(defn set-view-box
@ -514,7 +540,8 @@
(dm/get-prop shape :r3)
(dm/get-prop shape :r4)])
bool-content (dm/get-prop shape :bool-content)
svg-attrs (dm/get-prop shape :svg-attrs)]
svg-attrs (dm/get-prop shape :svg-attrs)
shadows (dm/get-prop shape :shadow)]
(use-shape id)
(set-shape-type type)
@ -535,6 +562,7 @@
(set-shape-svg-raw-content (get-static-markup shape)))
(when (some? bool-content) (set-shape-bool-content bool-content))
(when (some? corners) (set-shape-corners corners))
(when (some? shadows) (set-shape-shadows shadows))
(let [pending' (concat (set-shape-fills fills) (set-shape-strokes strokes))]
(recur (inc index) (into pending pending'))))
pending))]

View file

@ -82,7 +82,17 @@ Bool operations (`bool-type`) are serialized as `u8`:
Blur types are serialized as `u8`:
| Value | Field |
| ----- | ----- |
| 1 | Layer |
| \_ | None |
## Shadow Styles
Shadow styles are serialized as `u8`:
| Value | Field |
| ----- | ------------ |
| 1 | Layer |
| \_ | None |
| 0 | Drop Shadow |
| 1 | Inner Shadow |
| \_ | Drop Shadow |

View file

@ -596,6 +596,33 @@ pub extern "C" fn set_shape_path_attrs(num_attrs: u32) {
}
}
#[no_mangle]
pub extern "C" fn add_shape_shadow(
raw_color: u32,
blur: f32,
spread: f32,
x: f32,
y: f32,
raw_style: u8,
hidden: bool,
) {
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
if let Some(shape) = state.current_shape() {
let color = skia::Color::new(raw_color);
let style = shapes::ShadowStyle::from(raw_style);
let shadow = shapes::Shadow::new(color, blur, spread, (x, y), style, hidden);
shape.add_shadow(shadow);
}
}
#[no_mangle]
pub extern "C" fn clear_shape_shadows() {
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
if let Some(shape) = state.current_shape() {
shape.clear_shadows();
}
}
fn main() {
init_gl();
}

View file

@ -12,6 +12,7 @@ mod fills;
mod gpu_state;
mod images;
mod options;
mod shadows;
mod strokes;
use crate::shapes::{Kind, Shape};
@ -31,6 +32,7 @@ pub(crate) struct RenderState {
// by SVG: https://www.w3.org/TR/SVG2/render.html
pub final_surface: skia::Surface,
pub drawing_surface: skia::Surface,
pub shadow_surface: skia::Surface,
pub debug_surface: skia::Surface,
pub font_provider: skia::textlayout::TypefaceFontProvider,
pub cached_surface_image: Option<CachedSurfaceImage>,
@ -44,6 +46,9 @@ impl RenderState {
// This needs to be done once per WebGL context.
let mut gpu_state = GpuState::new();
let mut final_surface = gpu_state.create_target_surface(width, height);
let shadow_surface = final_surface
.new_surface_with_dimensions((width, height))
.unwrap();
let drawing_surface = final_surface
.new_surface_with_dimensions((width, height))
.unwrap();
@ -60,6 +65,7 @@ impl RenderState {
RenderState {
gpu_state,
final_surface,
shadow_surface,
drawing_surface,
debug_surface,
cached_surface_image: None,
@ -113,6 +119,10 @@ impl RenderState {
let surface = self.gpu_state.create_target_surface(dpr_width, dpr_height);
self.final_surface = surface;
self.shadow_surface = self
.final_surface
.new_surface_with_dimensions((dpr_width, dpr_height))
.unwrap();
self.drawing_surface = self
.final_surface
.new_surface_with_dimensions((dpr_width, dpr_height))
@ -144,6 +154,10 @@ impl RenderState {
.canvas()
.clear(self.background_color)
.reset_matrix();
self.shadow_surface
.canvas()
.clear(self.background_color)
.reset_matrix();
self.final_surface
.canvas()
.clear(self.background_color)
@ -162,6 +176,8 @@ impl RenderState {
Some(&skia::Paint::default()),
);
self.shadow_surface.canvas().clear(skia::Color::TRANSPARENT);
self.drawing_surface
.canvas()
.clear(skia::Color::TRANSPARENT);
@ -214,6 +230,10 @@ impl RenderState {
.clip_rect(shape.bounds(), skia::ClipOp::Intersect, true);
}
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_final_canvas();
}

View file

@ -0,0 +1,25 @@
use skia_safe::{self as skia};
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);
render_state.drawing_surface.draw(
&mut 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.final_surface.canvas(),
(0.0, 0.0),
skia::SamplingOptions::new(skia::FilterMode::Linear, skia::MipmapMode::Nearest),
Some(&skia::Paint::default()),
);
render_state
.shadow_surface
.canvas()
.clear(skia::Color::TRANSPARENT);
}

View file

@ -10,6 +10,7 @@ mod bools;
mod fills;
mod matrix;
mod paths;
mod shadows;
mod strokes;
mod svgraw;
@ -18,6 +19,7 @@ pub use bools::*;
pub use fills::*;
use matrix::*;
pub use paths::*;
pub use shadows::*;
pub use strokes::*;
pub use svgraw::*;
@ -53,6 +55,7 @@ pub struct Shape {
pub hidden: bool,
pub svg: Option<skia::svg::Dom>,
pub svg_attrs: HashMap<String, String>,
shadows: Vec<Shadow>,
}
impl Shape {
@ -73,6 +76,7 @@ impl Shape {
blur: Blur::default(),
svg: None,
svg_attrs: HashMap::new(),
shadows: vec![],
}
}
@ -305,6 +309,20 @@ impl Shape {
!matches!(self.kind, Kind::SVGRaw(_))
}
pub fn add_shadow(&mut self, shadow: Shadow) {
self.shadows.push(shadow);
}
pub fn clear_shadows(&mut self) {
self.shadows.clear();
}
pub fn drop_shadows(&self) -> impl DoubleEndedIterator<Item = &Shadow> {
self.shadows
.iter()
.filter(|shadow| shadow.style() == ShadowStyle::Drop)
}
pub fn to_path_transform(&self) -> Option<skia::Matrix> {
match self.kind {
Kind::Path(_) | Kind::Bool(_, _) => {

View file

@ -0,0 +1,86 @@
use skia_safe::{self as skia, image_filters};
use super::Color;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ShadowStyle {
Drop,
Inner,
}
impl From<u8> for ShadowStyle {
fn from(value: u8) -> Self {
match value {
0 => Self::Drop,
1 => Self::Inner,
_ => Self::default(),
}
}
}
impl Default for ShadowStyle {
fn default() -> Self {
Self::Drop
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Shadow {
color: Color,
blur: f32,
spread: f32,
offset: (f32, f32),
style: ShadowStyle,
hidden: bool,
}
// TODO: create shadows out of a chunk of bytes
impl Shadow {
pub fn new(
color: Color,
blur: f32,
spread: f32,
offset: (f32, f32),
style: ShadowStyle,
hidden: bool,
) -> Self {
Self {
color,
blur,
spread,
offset,
style,
hidden,
}
}
pub fn style(&self) -> ShadowStyle {
self.style
}
pub fn hidden(&self) -> bool {
self.hidden
}
pub fn to_paint(&self, dilate: bool, scale: f32) -> skia::Paint {
let mut paint = skia::Paint::default();
let mut filter = image_filters::drop_shadow_only(
(self.offset.0 * scale, self.offset.1 * scale),
(self.blur * scale, self.blur * scale),
self.color,
None,
None,
None,
);
if dilate {
filter =
image_filters::dilate((self.spread * scale, self.spread * scale), filter, None);
}
paint.set_image_filter(filter);
paint.set_anti_alias(true);
paint
}
}