mirror of
https://github.com/penpot/penpot.git
synced 2025-07-13 04:47:19 +02:00
🐛 Fix frame clipping
This commit is contained in:
parent
a44c70ef69
commit
51bb6583d2
4 changed files with 157 additions and 52 deletions
90
frontend/resources/wasm-playground/clips.html
Normal file
90
frontend/resources/wasm-playground/clips.html
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>WASM + WebGL2 Canvas</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background: #111;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
canvas {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<canvas id="canvas"></canvas>
|
||||||
|
<script type="module">
|
||||||
|
import initWasmModule from '/js/render_wasm.js';
|
||||||
|
import {
|
||||||
|
init, addShapeSolidFill, assignCanvas, hexToU32ARGB, getRandomInt, getRandomColor,
|
||||||
|
getRandomFloat, useShape, setShapeChildren, setupInteraction, addShapeSolidStrokeFill,
|
||||||
|
set_parent
|
||||||
|
} from './js/lib.js';
|
||||||
|
|
||||||
|
const canvas = document.getElementById("canvas");
|
||||||
|
canvas.width = window.innerWidth;
|
||||||
|
canvas.height = window.innerHeight;
|
||||||
|
|
||||||
|
initWasmModule().then(Module => {
|
||||||
|
init(Module);
|
||||||
|
assignCanvas(canvas);
|
||||||
|
Module._set_canvas_background(hexToU32ARGB("#FABADA", 1));
|
||||||
|
Module._set_view(1, 0, 0);
|
||||||
|
Module._init_shapes_pool(10);
|
||||||
|
setupInteraction(canvas);
|
||||||
|
|
||||||
|
const rectUuid1 = crypto.randomUUID();
|
||||||
|
const rectUuid2 = crypto.randomUUID();
|
||||||
|
const frameUuid = crypto.randomUUID();
|
||||||
|
|
||||||
|
// Create Rect 1
|
||||||
|
useShape(rectUuid1);
|
||||||
|
set_parent(frameUuid);
|
||||||
|
Module._set_shape_type(3);
|
||||||
|
Module._set_shape_selrect(100, 100, 300, 300);
|
||||||
|
Module._set_shape_blur(1, 100, 40);
|
||||||
|
addShapeSolidFill(hexToU32ARGB("#003DF7", 1));
|
||||||
|
|
||||||
|
// Create Rect 2
|
||||||
|
useShape(rectUuid2);
|
||||||
|
set_parent(frameUuid);
|
||||||
|
Module._set_shape_type(3);
|
||||||
|
Module._set_shape_selrect(200, 200, 400, 400);
|
||||||
|
addShapeSolidFill(1870806498)
|
||||||
|
|
||||||
|
// Create Frame
|
||||||
|
useShape(frameUuid);
|
||||||
|
Module._set_parent(0, 0, 0, 0);
|
||||||
|
Module._set_shape_type(0);
|
||||||
|
Module._set_shape_selrect(200, 200, 450, 450);
|
||||||
|
Module._set_shape_corners(50, 50, 50, 50);
|
||||||
|
addShapeSolidFill(hexToU32ARGB("#ee0d32", 1))
|
||||||
|
Module._add_shape_center_stroke(25, 0, 0, 0);
|
||||||
|
addShapeSolidStrokeFill(hexToU32ARGB("#000000", 1));
|
||||||
|
Module._set_shape_blur(1, 100, 4);
|
||||||
|
Module._add_shape_shadow(hexToU32ARGB("#000000", .2), 4, 40, 80, 80, 2, false);
|
||||||
|
|
||||||
|
setShapeChildren([rectUuid1]);
|
||||||
|
|
||||||
|
useShape("00000000-0000-0000-0000-000000000000");
|
||||||
|
setShapeChildren([frameUuid]);
|
||||||
|
|
||||||
|
performance.mark('render:begin');
|
||||||
|
Module._render(Date.now());
|
||||||
|
performance.mark('render:end');
|
||||||
|
const { duration } = performance.measure('render', 'render:begin', 'render:end');
|
||||||
|
// alert(`render time: ${duration.toFixed(2)}ms`);
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -12,7 +12,7 @@ mod surfaces;
|
||||||
mod text;
|
mod text;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
use skia_safe::{self as skia, Matrix, RRect, Rect};
|
use skia_safe::{self as skia, Matrix, Rect};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ use options::RenderOptions;
|
||||||
use surfaces::{SurfaceId, Surfaces};
|
use surfaces::{SurfaceId, Surfaces};
|
||||||
|
|
||||||
use crate::performance;
|
use crate::performance;
|
||||||
use crate::shapes::{Corners, Fill, Shape, StructureEntry, Type};
|
use crate::shapes::{Corners, Fill, Shape, SolidColor, StructureEntry, Type};
|
||||||
use crate::tiles::{self, PendingTiles, TileRect};
|
use crate::tiles::{self, PendingTiles, TileRect};
|
||||||
use crate::uuid::Uuid;
|
use crate::uuid::Uuid;
|
||||||
use crate::view::Viewbox;
|
use crate::view::Viewbox;
|
||||||
|
@ -317,8 +317,11 @@ impl RenderState {
|
||||||
&tile_rect,
|
&tile_rect,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.surfaces
|
self.surfaces.draw_cached_tile_surface(
|
||||||
.draw_cached_tile_surface(self.current_tile.unwrap(), rect);
|
self.current_tile.unwrap(),
|
||||||
|
rect,
|
||||||
|
self.background_color,
|
||||||
|
);
|
||||||
|
|
||||||
if self.options.is_debug_visible() {
|
if self.options.is_debug_visible() {
|
||||||
debug::render_workspace_current_tile(
|
debug::render_workspace_current_tile(
|
||||||
|
@ -394,7 +397,6 @@ impl RenderState {
|
||||||
shape: &Shape,
|
shape: &Shape,
|
||||||
modifiers: Option<&Matrix>,
|
modifiers: Option<&Matrix>,
|
||||||
scale_content: Option<&f32>,
|
scale_content: Option<&f32>,
|
||||||
clip_bounds: Option<(Rect, Option<Corners>, Matrix)>,
|
|
||||||
) {
|
) {
|
||||||
let shape = if let Some(scale_content) = scale_content {
|
let shape = if let Some(scale_content) = scale_content {
|
||||||
&shape.scale_content(*scale_content)
|
&shape.scale_content(*scale_content)
|
||||||
|
@ -412,43 +414,6 @@ impl RenderState {
|
||||||
|
|
||||||
let antialias = shape.should_use_antialias(self.get_scale());
|
let antialias = shape.should_use_antialias(self.get_scale());
|
||||||
|
|
||||||
// set clipping
|
|
||||||
if let Some((bounds, corners, transform)) = clip_bounds {
|
|
||||||
self.surfaces.apply_mut(surface_ids, |s| {
|
|
||||||
s.canvas().concat(&transform);
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(corners) = corners {
|
|
||||||
let rrect = RRect::new_rect_radii(bounds, &corners);
|
|
||||||
self.surfaces.apply_mut(surface_ids, |s| {
|
|
||||||
s.canvas()
|
|
||||||
.clip_rrect(rrect, skia::ClipOp::Intersect, antialias);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
self.surfaces.apply_mut(surface_ids, |s| {
|
|
||||||
s.canvas()
|
|
||||||
.clip_rect(bounds, skia::ClipOp::Intersect, antialias);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// This renders a red line around clipped
|
|
||||||
// shapes (frames).
|
|
||||||
if self.options.is_debug_visible() {
|
|
||||||
let mut paint = skia::Paint::default();
|
|
||||||
paint.set_style(skia::PaintStyle::Stroke);
|
|
||||||
paint.set_color(skia::Color::from_argb(255, 255, 0, 0));
|
|
||||||
paint.set_stroke_width(4.);
|
|
||||||
self.surfaces
|
|
||||||
.canvas(SurfaceId::Fills)
|
|
||||||
.draw_rect(bounds, &paint);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.surfaces.apply_mut(surface_ids, |s| {
|
|
||||||
s.canvas()
|
|
||||||
.concat(&transform.invert().unwrap_or(Matrix::default()));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't want to change the value in the global state
|
// We don't want to change the value in the global state
|
||||||
let mut shape: Cow<Shape> = Cow::Borrowed(shape);
|
let mut shape: Cow<Shape> = Cow::Borrowed(shape);
|
||||||
|
|
||||||
|
@ -566,7 +531,6 @@ impl RenderState {
|
||||||
shadows::render_fill_drop_shadows(self, &shape, antialias);
|
shadows::render_fill_drop_shadows(self, &shape, antialias);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.apply_drawing_to_render_canvas(Some(&shape));
|
self.apply_drawing_to_render_canvas(Some(&shape));
|
||||||
let surface_ids = SurfaceId::Strokes as u32
|
let surface_ids = SurfaceId::Strokes as u32
|
||||||
| SurfaceId::Fills as u32
|
| SurfaceId::Fills as u32
|
||||||
|
@ -765,7 +729,13 @@ impl RenderState {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn render_shape_exit(&mut self, element: &Shape, visited_mask: bool) {
|
pub fn render_shape_exit(
|
||||||
|
&mut self,
|
||||||
|
element: &Shape,
|
||||||
|
visited_mask: bool,
|
||||||
|
modifiers: Option<&Matrix>,
|
||||||
|
scale_content: Option<&f32>,
|
||||||
|
) {
|
||||||
if visited_mask {
|
if visited_mask {
|
||||||
// Because masked groups needs two rendering passes (first drawing
|
// Because masked groups needs two rendering passes (first drawing
|
||||||
// the content and then drawing the mask), we need to do an
|
// the content and then drawing the mask), we need to do an
|
||||||
|
@ -806,8 +776,39 @@ impl RenderState {
|
||||||
if let Type::Group(_) = element.shape_type {
|
if let Type::Group(_) = element.shape_type {
|
||||||
self.nested_fills.pop();
|
self.nested_fills.pop();
|
||||||
}
|
}
|
||||||
self.surfaces.canvas(SurfaceId::Current).restore();
|
|
||||||
|
|
||||||
|
// Detect clipping and apply it properly
|
||||||
|
if let Type::Frame(_) = &element.shape_type {
|
||||||
|
if element.clip() {
|
||||||
|
let mut layer_paint = skia::Paint::default();
|
||||||
|
layer_paint.set_blend_mode(skia::BlendMode::DstIn);
|
||||||
|
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&layer_paint);
|
||||||
|
self.surfaces
|
||||||
|
.canvas(SurfaceId::Current)
|
||||||
|
.save_layer(&layer_rec);
|
||||||
|
|
||||||
|
// We clip only the fills content
|
||||||
|
let mut element_fills: Cow<Shape> = Cow::Borrowed(element);
|
||||||
|
element_fills.to_mut().clear_strokes();
|
||||||
|
element_fills.to_mut().clear_shadows();
|
||||||
|
// We use the white color as the mask selection one
|
||||||
|
element_fills
|
||||||
|
.to_mut()
|
||||||
|
.set_fills([Fill::Solid(SolidColor(skia::Color::WHITE))].to_vec());
|
||||||
|
self.render_shape(&element_fills, modifiers, scale_content);
|
||||||
|
|
||||||
|
self.surfaces.canvas(SurfaceId::Current).restore();
|
||||||
|
|
||||||
|
// Now we paint the strokes - in clipped content strokes are drawn over the contained elements
|
||||||
|
let mut element_strokes: Cow<Shape> = Cow::Borrowed(element);
|
||||||
|
element_strokes.to_mut().clear_fills();
|
||||||
|
element_strokes.to_mut().clear_shadows();
|
||||||
|
self.render_shape(&element_strokes, modifiers, scale_content);
|
||||||
|
|
||||||
|
// TODO: drop shadows. With thos approach actually drop shadows for frames with clipped content are lost.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.surfaces.canvas(SurfaceId::Current).restore();
|
||||||
self.focus_mode.exit(&element.id);
|
self.focus_mode.exit(&element.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -861,7 +862,7 @@ impl RenderState {
|
||||||
let NodeRenderState {
|
let NodeRenderState {
|
||||||
id: node_id,
|
id: node_id,
|
||||||
visited_children,
|
visited_children,
|
||||||
clip_bounds,
|
clip_bounds: _,
|
||||||
visited_mask,
|
visited_mask,
|
||||||
mask,
|
mask,
|
||||||
} = node_render_state;
|
} = node_render_state;
|
||||||
|
@ -879,7 +880,12 @@ impl RenderState {
|
||||||
}
|
}
|
||||||
|
|
||||||
if visited_children {
|
if visited_children {
|
||||||
self.render_shape_exit(element, visited_mask);
|
self.render_shape_exit(
|
||||||
|
element,
|
||||||
|
visited_mask,
|
||||||
|
modifiers.get(&node_id),
|
||||||
|
scale_content.get(&element.id),
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -909,7 +915,6 @@ impl RenderState {
|
||||||
element,
|
element,
|
||||||
modifiers.get(&element.id),
|
modifiers.get(&element.id),
|
||||||
scale_content.get(&element.id),
|
scale_content.get(&element.id),
|
||||||
clip_bounds,
|
|
||||||
);
|
);
|
||||||
} else if visited_children {
|
} else if visited_children {
|
||||||
self.apply_drawing_to_render_canvas(Some(element));
|
self.apply_drawing_to_render_canvas(Some(element));
|
||||||
|
@ -974,8 +979,11 @@ impl RenderState {
|
||||||
if self.surfaces.has_cached_tile_surface(current_tile) {
|
if self.surfaces.has_cached_tile_surface(current_tile) {
|
||||||
performance::begin_measure!("render_shape_tree::cached");
|
performance::begin_measure!("render_shape_tree::cached");
|
||||||
let tile_rect = self.get_current_tile_bounds();
|
let tile_rect = self.get_current_tile_bounds();
|
||||||
self.surfaces
|
self.surfaces.draw_cached_tile_surface(
|
||||||
.draw_cached_tile_surface(current_tile, tile_rect);
|
current_tile,
|
||||||
|
tile_rect,
|
||||||
|
self.background_color,
|
||||||
|
);
|
||||||
performance::end_measure!("render_shape_tree::cached");
|
performance::end_measure!("render_shape_tree::cached");
|
||||||
|
|
||||||
if self.options.is_debug_visible() {
|
if self.options.is_debug_visible() {
|
||||||
|
@ -1012,6 +1020,7 @@ impl RenderState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// println!("clear current {:?}", self.current_tile);
|
||||||
self.surfaces
|
self.surfaces
|
||||||
.canvas(SurfaceId::Current)
|
.canvas(SurfaceId::Current)
|
||||||
.clear(self.background_color);
|
.clear(self.background_color);
|
||||||
|
|
|
@ -307,8 +307,14 @@ impl Surfaces {
|
||||||
self.tiles.remove(tile)
|
self.tiles.remove(tile)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_cached_tile_surface(&mut self, tile: Tile, rect: skia::Rect) {
|
pub fn draw_cached_tile_surface(&mut self, tile: Tile, rect: skia::Rect, color: skia::Color) {
|
||||||
let image = self.tiles.get(tile).unwrap();
|
let image = self.tiles.get(tile).unwrap();
|
||||||
|
|
||||||
|
let mut paint = skia::Paint::default();
|
||||||
|
paint.set_color(color);
|
||||||
|
|
||||||
|
self.target.canvas().draw_rect(rect, &paint);
|
||||||
|
|
||||||
self.target
|
self.target
|
||||||
.canvas()
|
.canvas()
|
||||||
.draw_image_rect(&image, None, rect, &skia::Paint::default());
|
.draw_image_rect(&image, None, rect, &skia::Paint::default());
|
||||||
|
|
|
@ -4,7 +4,7 @@ use indexmap::IndexSet;
|
||||||
use skia_safe as skia;
|
use skia_safe as skia;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
|
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
|
||||||
pub struct Tile(pub i32, pub i32);
|
pub struct Tile(pub i32, pub i32);
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
|
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue