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:
parent
27dce6fcfa
commit
09131f7533
7 changed files with 217 additions and 3 deletions
|
@ -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))]
|
||||
|
|
|
@ -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 |
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
25
render-wasm/src/render/shadows.rs
Normal file
25
render-wasm/src/render/shadows.rs
Normal 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);
|
||||
}
|
|
@ -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(_, _) => {
|
||||
|
|
86
render-wasm/src/shapes/shadows.rs
Normal file
86
render-wasm/src/shapes/shadows.rs
Normal 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
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue