mirror of
https://github.com/penpot/penpot.git
synced 2025-07-13 14:17:16 +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 ui;
|
||||
|
||||
use skia_safe::{self as skia, Matrix, RRect, Rect};
|
||||
use skia_safe::{self as skia, Matrix, Rect};
|
||||
use std::borrow::Cow;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
|
@ -21,7 +21,7 @@ use options::RenderOptions;
|
|||
use surfaces::{SurfaceId, Surfaces};
|
||||
|
||||
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::uuid::Uuid;
|
||||
use crate::view::Viewbox;
|
||||
|
@ -317,8 +317,11 @@ impl RenderState {
|
|||
&tile_rect,
|
||||
);
|
||||
|
||||
self.surfaces
|
||||
.draw_cached_tile_surface(self.current_tile.unwrap(), rect);
|
||||
self.surfaces.draw_cached_tile_surface(
|
||||
self.current_tile.unwrap(),
|
||||
rect,
|
||||
self.background_color,
|
||||
);
|
||||
|
||||
if self.options.is_debug_visible() {
|
||||
debug::render_workspace_current_tile(
|
||||
|
@ -394,7 +397,6 @@ impl RenderState {
|
|||
shape: &Shape,
|
||||
modifiers: Option<&Matrix>,
|
||||
scale_content: Option<&f32>,
|
||||
clip_bounds: Option<(Rect, Option<Corners>, Matrix)>,
|
||||
) {
|
||||
let shape = if let Some(scale_content) = scale_content {
|
||||
&shape.scale_content(*scale_content)
|
||||
|
@ -412,43 +414,6 @@ impl RenderState {
|
|||
|
||||
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
|
||||
let mut shape: Cow<Shape> = Cow::Borrowed(shape);
|
||||
|
||||
|
@ -566,7 +531,6 @@ impl RenderState {
|
|||
shadows::render_fill_drop_shadows(self, &shape, antialias);
|
||||
}
|
||||
};
|
||||
|
||||
self.apply_drawing_to_render_canvas(Some(&shape));
|
||||
let surface_ids = SurfaceId::Strokes as u32
|
||||
| SurfaceId::Fills as u32
|
||||
|
@ -765,7 +729,13 @@ impl RenderState {
|
|||
}
|
||||
|
||||
#[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 {
|
||||
// Because masked groups needs two rendering passes (first drawing
|
||||
// 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 {
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -861,7 +862,7 @@ impl RenderState {
|
|||
let NodeRenderState {
|
||||
id: node_id,
|
||||
visited_children,
|
||||
clip_bounds,
|
||||
clip_bounds: _,
|
||||
visited_mask,
|
||||
mask,
|
||||
} = node_render_state;
|
||||
|
@ -879,7 +880,12 @@ impl RenderState {
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -909,7 +915,6 @@ impl RenderState {
|
|||
element,
|
||||
modifiers.get(&element.id),
|
||||
scale_content.get(&element.id),
|
||||
clip_bounds,
|
||||
);
|
||||
} else if visited_children {
|
||||
self.apply_drawing_to_render_canvas(Some(element));
|
||||
|
@ -974,8 +979,11 @@ impl RenderState {
|
|||
if self.surfaces.has_cached_tile_surface(current_tile) {
|
||||
performance::begin_measure!("render_shape_tree::cached");
|
||||
let tile_rect = self.get_current_tile_bounds();
|
||||
self.surfaces
|
||||
.draw_cached_tile_surface(current_tile, tile_rect);
|
||||
self.surfaces.draw_cached_tile_surface(
|
||||
current_tile,
|
||||
tile_rect,
|
||||
self.background_color,
|
||||
);
|
||||
performance::end_measure!("render_shape_tree::cached");
|
||||
|
||||
if self.options.is_debug_visible() {
|
||||
|
@ -1012,6 +1020,7 @@ impl RenderState {
|
|||
}
|
||||
}
|
||||
|
||||
// println!("clear current {:?}", self.current_tile);
|
||||
self.surfaces
|
||||
.canvas(SurfaceId::Current)
|
||||
.clear(self.background_color);
|
||||
|
|
|
@ -307,8 +307,14 @@ impl Surfaces {
|
|||
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 mut paint = skia::Paint::default();
|
||||
paint.set_color(color);
|
||||
|
||||
self.target.canvas().draw_rect(rect, &paint);
|
||||
|
||||
self.target
|
||||
.canvas()
|
||||
.draw_image_rect(&image, None, rect, &skia::Paint::default());
|
||||
|
|
|
@ -4,7 +4,7 @@ use indexmap::IndexSet;
|
|||
use skia_safe as skia;
|
||||
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);
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue