mirror of
https://github.com/penpot/penpot.git
synced 2025-04-13 07:21:40 -05:00
Merge pull request #6077 from penpot/elenatorro-10516-fix-shadow-rendering
🐛 Fix drop shadows viewport clipping
This commit is contained in:
commit
b727f2fe1f
6 changed files with 174 additions and 63 deletions
|
@ -183,6 +183,9 @@ impl RenderState {
|
|||
|
||||
pub fn reset_canvas(&mut self) {
|
||||
self.surfaces.canvas(SurfaceId::Fills).restore_to_count(1);
|
||||
self.surfaces
|
||||
.canvas(SurfaceId::DropShadows)
|
||||
.restore_to_count(1);
|
||||
self.surfaces.canvas(SurfaceId::Strokes).restore_to_count(1);
|
||||
self.surfaces.canvas(SurfaceId::Current).restore_to_count(1);
|
||||
|
||||
|
@ -191,6 +194,7 @@ impl RenderState {
|
|||
SurfaceId::Fills,
|
||||
SurfaceId::Strokes,
|
||||
SurfaceId::Current,
|
||||
SurfaceId::DropShadows,
|
||||
SurfaceId::Shadow,
|
||||
SurfaceId::Overlay,
|
||||
],
|
||||
|
@ -216,6 +220,16 @@ impl RenderState {
|
|||
pub fn apply_drawing_to_render_canvas(&mut self, shape: &Shape) {
|
||||
self.surfaces
|
||||
.flush_and_submit(&mut self.gpu_state, SurfaceId::Fills);
|
||||
|
||||
self.surfaces
|
||||
.flush_and_submit(&mut self.gpu_state, SurfaceId::DropShadows);
|
||||
|
||||
self.surfaces.draw_into(
|
||||
SurfaceId::DropShadows,
|
||||
SurfaceId::Current,
|
||||
Some(&skia::Paint::default()),
|
||||
);
|
||||
|
||||
self.surfaces.draw_into(
|
||||
SurfaceId::Fills,
|
||||
SurfaceId::Current,
|
||||
|
@ -259,6 +273,7 @@ impl RenderState {
|
|||
self.surfaces.apply_mut(
|
||||
&[
|
||||
SurfaceId::Shadow,
|
||||
SurfaceId::DropShadows,
|
||||
SurfaceId::Overlay,
|
||||
SurfaceId::Fills,
|
||||
SurfaceId::Strokes,
|
||||
|
@ -281,17 +296,19 @@ impl RenderState {
|
|||
modifiers: Option<&Matrix>,
|
||||
clip_bounds: Option<(Rect, Option<Corners>, Matrix)>,
|
||||
) {
|
||||
let surface_ids = &[SurfaceId::Fills, SurfaceId::Strokes];
|
||||
let surface_ids = &[SurfaceId::Fills, SurfaceId::Strokes, SurfaceId::DropShadows];
|
||||
self.surfaces.apply_mut(surface_ids, |s| {
|
||||
s.canvas().save();
|
||||
});
|
||||
|
||||
// set clipping
|
||||
if let Some((bounds, corners, transform)) = clip_bounds {
|
||||
self.surfaces
|
||||
.apply_mut(&[SurfaceId::Fills, SurfaceId::Strokes], |s| {
|
||||
self.surfaces.apply_mut(
|
||||
&[SurfaceId::Fills, SurfaceId::Strokes, SurfaceId::DropShadows],
|
||||
|s| {
|
||||
s.canvas().concat(&transform);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
if let Some(corners) = corners {
|
||||
let rrect = RRect::new_rect_radii(bounds, &corners);
|
||||
|
@ -358,10 +375,12 @@ impl RenderState {
|
|||
text::render(self, text_content);
|
||||
}
|
||||
_ => {
|
||||
self.surfaces
|
||||
.apply_mut(&[SurfaceId::Fills, SurfaceId::Strokes], |s| {
|
||||
self.surfaces.apply_mut(
|
||||
&[SurfaceId::Fills, SurfaceId::Strokes, SurfaceId::DropShadows],
|
||||
|s| {
|
||||
s.canvas().concat(&matrix);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
for fill in shape.fills().rev() {
|
||||
fills::render(self, &shape, fill);
|
||||
|
@ -380,21 +399,17 @@ impl RenderState {
|
|||
);
|
||||
}
|
||||
|
||||
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_shadows(self, &shape);
|
||||
}
|
||||
};
|
||||
|
||||
self.apply_drawing_to_render_canvas(&shape);
|
||||
self.surfaces
|
||||
.apply_mut(&[SurfaceId::Fills, SurfaceId::Strokes], |s| {
|
||||
self.surfaces.apply_mut(
|
||||
&[SurfaceId::Fills, SurfaceId::Strokes, SurfaceId::DropShadows],
|
||||
|s| {
|
||||
s.canvas().restore();
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn start_render_loop(
|
||||
|
@ -403,22 +418,23 @@ impl RenderState {
|
|||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
timestamp: i32,
|
||||
) -> Result<(), String> {
|
||||
let surface_ids = &[SurfaceId::Fills, SurfaceId::Strokes];
|
||||
|
||||
if self.render_in_progress {
|
||||
if let Some(frame_id) = self.render_request_id {
|
||||
self.cancel_animation_frame(frame_id);
|
||||
}
|
||||
}
|
||||
self.reset_canvas();
|
||||
self.surfaces.apply_mut(surface_ids, |s| {
|
||||
s.canvas().scale((
|
||||
self.viewbox.zoom * self.options.dpr(),
|
||||
self.viewbox.zoom * self.options.dpr(),
|
||||
));
|
||||
s.canvas()
|
||||
.translate((self.viewbox.pan_x, self.viewbox.pan_y));
|
||||
});
|
||||
self.surfaces.apply_mut(
|
||||
&[SurfaceId::Fills, SurfaceId::Strokes, SurfaceId::DropShadows],
|
||||
|s| {
|
||||
s.canvas().scale((
|
||||
self.viewbox.zoom * self.options.dpr(),
|
||||
self.viewbox.zoom * self.options.dpr(),
|
||||
));
|
||||
s.canvas()
|
||||
.translate((self.viewbox.pan_x, self.viewbox.pan_y));
|
||||
},
|
||||
);
|
||||
|
||||
self.pending_nodes = vec![NodeRenderState {
|
||||
id: Uuid::nil(),
|
||||
|
@ -510,6 +526,7 @@ impl RenderState {
|
|||
self.surfaces.canvas(SurfaceId::Target).save();
|
||||
self.surfaces.canvas(SurfaceId::Fills).save();
|
||||
self.surfaces.canvas(SurfaceId::Strokes).save();
|
||||
self.surfaces.canvas(SurfaceId::DropShadows).save();
|
||||
|
||||
let navigate_zoom = self.viewbox.zoom / cached.viewbox.zoom;
|
||||
let navigate_x = cached.viewbox.zoom * (self.viewbox.pan_x - cached.viewbox.pan_x);
|
||||
|
@ -532,6 +549,7 @@ impl RenderState {
|
|||
self.surfaces.canvas(SurfaceId::Target).restore();
|
||||
self.surfaces.canvas(SurfaceId::Fills).restore();
|
||||
self.surfaces.canvas(SurfaceId::Strokes).restore();
|
||||
self.surfaces.canvas(SurfaceId::DropShadows).restore();
|
||||
|
||||
self.flush();
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
use skia_safe::{self as skia, RRect};
|
||||
use skia_safe::{self as skia, Paint, RRect};
|
||||
|
||||
use super::{RenderState, SurfaceId};
|
||||
use crate::math::Rect as MathRect;
|
||||
use crate::shapes::{Fill, Frame, ImageFill, Rect, Shape, Type};
|
||||
|
||||
fn draw_image_fill_in_container(
|
||||
fn draw_image_fill(
|
||||
render_state: &mut RenderState,
|
||||
shape: &Shape,
|
||||
fill: &Fill,
|
||||
image_fill: &ImageFill,
|
||||
paint: &Paint,
|
||||
) {
|
||||
let image = render_state.images.get(&image_fill.id());
|
||||
if image.is_none() {
|
||||
|
@ -19,7 +19,6 @@ fn draw_image_fill_in_container(
|
|||
let canvas = render_state.surfaces.canvas(SurfaceId::Fills);
|
||||
let container = &shape.selrect;
|
||||
let path_transform = shape.to_path_transform();
|
||||
let paint = fill.to_paint(container);
|
||||
|
||||
let width = size.0 as f32;
|
||||
let height = size.1 as f32;
|
||||
|
@ -110,39 +109,29 @@ fn draw_image_fill_in_container(
|
|||
* This SHOULD be the only public function in this module.
|
||||
*/
|
||||
pub fn render(render_state: &mut RenderState, shape: &Shape, fill: &Fill) {
|
||||
let canvas = render_state.surfaces.canvas(SurfaceId::Fills);
|
||||
let selrect = shape.selrect;
|
||||
let path_transform = shape.to_path_transform();
|
||||
let paint = &fill.to_paint(&shape.selrect);
|
||||
|
||||
match (fill, &shape.shape_type) {
|
||||
(Fill::Image(image_fill), _) => {
|
||||
draw_image_fill_in_container(render_state, shape, fill, image_fill);
|
||||
draw_image_fill(render_state, shape, image_fill, paint);
|
||||
}
|
||||
(_, Type::Rect(_) | Type::Frame(_)) => {
|
||||
if let Some(corners) = shape.shape_type.corners() {
|
||||
let rrect = RRect::new_rect_radii(selrect, &corners);
|
||||
canvas.draw_rrect(rrect, &fill.to_paint(&selrect));
|
||||
} else {
|
||||
canvas.draw_rect(selrect, &fill.to_paint(&selrect));
|
||||
}
|
||||
render_state
|
||||
.surfaces
|
||||
.draw_rect_to(SurfaceId::Fills, shape, paint);
|
||||
}
|
||||
(_, Type::Circle) => {
|
||||
canvas.draw_oval(selrect, &fill.to_paint(&selrect));
|
||||
render_state
|
||||
.surfaces
|
||||
.draw_circle_to(SurfaceId::Fills, shape, paint);
|
||||
}
|
||||
(_, Type::Path(_)) | (_, Type::Bool(_)) => {
|
||||
if let Some(path) = &shape.shape_type.path() {
|
||||
let svg_attrs = &shape.svg_attrs;
|
||||
let mut skia_path = &mut path.to_skia_path();
|
||||
|
||||
if let Some(path_transform) = path_transform {
|
||||
skia_path = skia_path.transform(&path_transform);
|
||||
if let Some("evenodd") = svg_attrs.get("fill-rule").map(String::as_str) {
|
||||
skia_path.set_fill_type(skia::PathFillType::EvenOdd);
|
||||
}
|
||||
canvas.draw_path(&skia_path, &fill.to_paint(&selrect));
|
||||
}
|
||||
}
|
||||
render_state
|
||||
.surfaces
|
||||
.draw_path_to(SurfaceId::Fills, shape, paint);
|
||||
}
|
||||
(_, _) => {
|
||||
unreachable!("This shape should not have fills")
|
||||
}
|
||||
(_, _) => unreachable!("This shape should not have fills"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,50 @@
|
|||
use skia_safe::{self as skia};
|
||||
|
||||
use super::{RenderState, SurfaceId};
|
||||
use crate::shapes::Shadow;
|
||||
use crate::shapes::{Shadow, Shape, Type};
|
||||
|
||||
pub fn render_drop_shadow(render_state: &mut RenderState, shadow: &Shadow, scale: f32) {
|
||||
let shadow_paint = shadow.to_paint(scale);
|
||||
render_state
|
||||
.surfaces
|
||||
.draw_into(SurfaceId::Fills, SurfaceId::Shadow, Some(&shadow_paint));
|
||||
pub fn render_drop_shadows(render_state: &mut RenderState, shape: &Shape) {
|
||||
if shape.fills().len() > 0 {
|
||||
for shadow in shape.drop_shadows().rev().filter(|s| !s.hidden()) {
|
||||
render_fill_drop_shadow(render_state, &shape, &shadow);
|
||||
}
|
||||
} else {
|
||||
let scale = render_state.viewbox.zoom * render_state.options.dpr();
|
||||
for shadow in shape.drop_shadows().rev().filter(|s| !s.hidden()) {
|
||||
render_stroke_drop_shadow(render_state, &shadow, scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render_fill_drop_shadow(render_state: &mut RenderState, shape: &Shape, shadow: &Shadow) {
|
||||
let paint = &shadow.get_drop_shadow_paint();
|
||||
|
||||
match &shape.shape_type {
|
||||
Type::Rect(_) | Type::Frame(_) => {
|
||||
render_state
|
||||
.surfaces
|
||||
.draw_rect_to(SurfaceId::DropShadows, shape, paint);
|
||||
}
|
||||
Type::Circle => {
|
||||
render_state
|
||||
.surfaces
|
||||
.draw_circle_to(SurfaceId::DropShadows, shape, paint);
|
||||
}
|
||||
Type::Path(_) | Type::Bool(_) => {
|
||||
render_state
|
||||
.surfaces
|
||||
.draw_path_to(SurfaceId::DropShadows, shape, paint);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn render_stroke_drop_shadow(render_state: &mut RenderState, shadow: &Shadow, scale: f32) {
|
||||
let shadow_paint = &shadow.to_paint(scale);
|
||||
|
||||
render_state
|
||||
.surfaces
|
||||
.draw_into(SurfaceId::Strokes, SurfaceId::Shadow, Some(&shadow_paint));
|
||||
.draw_into(SurfaceId::Strokes, SurfaceId::Shadow, Some(shadow_paint));
|
||||
|
||||
render_state.surfaces.draw_into(
|
||||
SurfaceId::Shadow,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use super::gpu_state::GpuState;
|
||||
use skia_safe as skia;
|
||||
use crate::shapes::Shape;
|
||||
use skia_safe::{self as skia, Paint, RRect};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum SurfaceId {
|
||||
|
@ -8,6 +9,7 @@ pub enum SurfaceId {
|
|||
Fills,
|
||||
Strokes,
|
||||
Shadow,
|
||||
DropShadows,
|
||||
Overlay,
|
||||
Debug,
|
||||
}
|
||||
|
@ -23,6 +25,8 @@ pub struct Surfaces {
|
|||
shape_strokes: skia::Surface,
|
||||
// used for rendering shadows
|
||||
shadow: skia::Surface,
|
||||
// used for new shadow rendering
|
||||
drop_shadows: skia::Surface,
|
||||
// for drawing the things that are over shadows.
|
||||
overlay: skia::Surface,
|
||||
// for drawing debug info.
|
||||
|
@ -40,6 +44,7 @@ impl Surfaces {
|
|||
let mut target = gpu_state.create_target_surface(width, height);
|
||||
let current = target.new_surface_with_dimensions((width, height)).unwrap();
|
||||
let shadow = target.new_surface_with_dimensions((width, height)).unwrap();
|
||||
let drop_shadows = target.new_surface_with_dimensions((width, height)).unwrap();
|
||||
let overlay = target.new_surface_with_dimensions((width, height)).unwrap();
|
||||
let shape_fills = target.new_surface_with_dimensions((width, height)).unwrap();
|
||||
let shape_strokes = target.new_surface_with_dimensions((width, height)).unwrap();
|
||||
|
@ -49,6 +54,7 @@ impl Surfaces {
|
|||
target,
|
||||
current,
|
||||
shadow,
|
||||
drop_shadows,
|
||||
overlay,
|
||||
shape_fills,
|
||||
shape_strokes,
|
||||
|
@ -94,6 +100,7 @@ impl Surfaces {
|
|||
SurfaceId::Target => &mut self.target,
|
||||
SurfaceId::Current => &mut self.current,
|
||||
SurfaceId::Shadow => &mut self.shadow,
|
||||
SurfaceId::DropShadows => &mut self.drop_shadows,
|
||||
SurfaceId::Overlay => &mut self.overlay,
|
||||
SurfaceId::Fills => &mut self.shape_fills,
|
||||
SurfaceId::Strokes => &mut self.shape_strokes,
|
||||
|
@ -107,7 +114,27 @@ impl Surfaces {
|
|||
self.current = self.target.new_surface_with_dimensions(dim).unwrap();
|
||||
self.overlay = self.target.new_surface_with_dimensions(dim).unwrap();
|
||||
self.shadow = self.target.new_surface_with_dimensions(dim).unwrap();
|
||||
self.drop_shadows = self.target.new_surface_with_dimensions(dim).unwrap();
|
||||
self.shape_fills = self.target.new_surface_with_dimensions(dim).unwrap();
|
||||
self.debug = self.target.new_surface_with_dimensions(dim).unwrap();
|
||||
}
|
||||
|
||||
pub fn draw_rect_to(&mut self, id: SurfaceId, shape: &Shape, paint: &Paint) {
|
||||
if let Some(corners) = shape.shape_type.corners() {
|
||||
let rrect = RRect::new_rect_radii(shape.selrect, &corners);
|
||||
self.canvas(id).draw_rrect(rrect, paint);
|
||||
} else {
|
||||
self.canvas(id).draw_rect(shape.selrect, paint);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_circle_to(&mut self, id: SurfaceId, shape: &Shape, paint: &Paint) {
|
||||
self.canvas(id).draw_oval(shape.selrect, paint);
|
||||
}
|
||||
|
||||
pub fn draw_path_to(&mut self, id: SurfaceId, shape: &Shape, paint: &Paint) {
|
||||
if let Some(path) = shape.get_skia_path() {
|
||||
self.canvas(id).draw_path(&path, paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -646,6 +646,21 @@ impl Shape {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_skia_path(&self) -> Option<skia::Path> {
|
||||
if let Some(path) = self.shape_type.path() {
|
||||
let mut skia_path = path.to_skia_path();
|
||||
if let Some(path_transform) = self.to_path_transform() {
|
||||
skia_path.transform(&path_transform);
|
||||
}
|
||||
if let Some("evenodd") = self.svg_attrs.get("fill-rule").map(String::as_str) {
|
||||
skia_path.set_fill_type(skia::PathFillType::EvenOdd);
|
||||
}
|
||||
Some(skia_path)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn transform_selrect(&mut self, transform: &Matrix) {
|
||||
let mut center = self.selrect.center();
|
||||
center = transform.map_point(center);
|
||||
|
|
|
@ -123,4 +123,33 @@ impl Shadow {
|
|||
|
||||
filter
|
||||
}
|
||||
|
||||
// New methods for DropShadow
|
||||
pub fn get_drop_shadow_paint(&self) -> skia::Paint {
|
||||
let mut paint = skia::Paint::default();
|
||||
|
||||
let image_filter = self.get_drop_shadow_filter();
|
||||
|
||||
paint.set_image_filter(image_filter);
|
||||
paint.set_anti_alias(true);
|
||||
|
||||
paint
|
||||
}
|
||||
|
||||
fn get_drop_shadow_filter(&self) -> Option<ImageFilter> {
|
||||
let mut filter = image_filters::drop_shadow_only(
|
||||
(self.offset.0, self.offset.1),
|
||||
(self.blur, self.blur),
|
||||
self.color,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
if self.spread > 0. {
|
||||
filter = image_filters::dilate((self.spread, self.spread), filter, None);
|
||||
}
|
||||
|
||||
filter
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue