Merge pull request #7175 from penpot/superalex-fix-clipping

🐛 Fix clipping
This commit is contained in:
Elena Torró
2025-08-29 11:03:07 +02:00
committed by GitHub

View File

@@ -13,7 +13,7 @@ mod surfaces;
mod text; mod text;
mod ui; mod ui;
use skia_safe::{self as skia, Matrix, Rect}; use skia_safe::{self as skia, Matrix, RRect, Rect};
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
@@ -22,7 +22,7 @@ use options::RenderOptions;
pub use surfaces::{SurfaceId, Surfaces}; pub use surfaces::{SurfaceId, Surfaces};
use crate::performance; use crate::performance;
use crate::shapes::{Blur, BlurType, Corners, Fill, Shape, SolidColor, StructureEntry, Type}; use crate::shapes::{Blur, BlurType, Corners, Fill, Shape, StructureEntry, Type};
use crate::state::ShapesPool; use crate::state::ShapesPool;
use crate::tiles::{self, PendingTiles, TileRect}; use crate::tiles::{self, PendingTiles, TileRect};
use crate::uuid::Uuid; use crate::uuid::Uuid;
@@ -367,17 +367,13 @@ impl RenderState {
pub fn apply_drawing_to_render_canvas(&mut self, shape: Option<&Shape>) { pub fn apply_drawing_to_render_canvas(&mut self, shape: Option<&Shape>) {
performance::begin_measure!("apply_drawing_to_render_canvas"); performance::begin_measure!("apply_drawing_to_render_canvas");
self.surfaces.draw_into( let paint = skia::Paint::default();
SurfaceId::DropShadows,
SurfaceId::Current,
Some(&skia::Paint::default()),
);
self.surfaces.draw_into( self.surfaces
SurfaceId::Fills, .draw_into(SurfaceId::DropShadows, SurfaceId::Current, Some(&paint));
SurfaceId::Current,
Some(&skia::Paint::default()), self.surfaces
); .draw_into(SurfaceId::Fills, SurfaceId::Current, Some(&paint));
let mut render_overlay_below_strokes = false; let mut render_overlay_below_strokes = false;
if let Some(shape) = shape { if let Some(shape) = shape {
@@ -385,26 +381,18 @@ impl RenderState {
} }
if render_overlay_below_strokes { if render_overlay_below_strokes {
self.surfaces.draw_into( self.surfaces
SurfaceId::InnerShadows, .draw_into(SurfaceId::InnerShadows, SurfaceId::Current, Some(&paint));
SurfaceId::Current,
Some(&skia::Paint::default()),
);
} }
self.surfaces.draw_into( self.surfaces
SurfaceId::Strokes, .draw_into(SurfaceId::Strokes, SurfaceId::Current, Some(&paint));
SurfaceId::Current,
Some(&skia::Paint::default()),
);
if !render_overlay_below_strokes { if !render_overlay_below_strokes {
self.surfaces.draw_into( self.surfaces
SurfaceId::InnerShadows, .draw_into(SurfaceId::InnerShadows, SurfaceId::Current, Some(&paint));
SurfaceId::Current,
Some(&skia::Paint::default()),
);
} }
let surface_ids = SurfaceId::Strokes as u32 let surface_ids = SurfaceId::Strokes as u32
| SurfaceId::Fills as u32 | SurfaceId::Fills as u32
| SurfaceId::DropShadows as u32 | SurfaceId::DropShadows as u32
@@ -430,6 +418,7 @@ impl RenderState {
structure: &HashMap<Uuid, Vec<StructureEntry>>, structure: &HashMap<Uuid, Vec<StructureEntry>>,
shape: &Shape, shape: &Shape,
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)
@@ -447,6 +436,43 @@ 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);
@@ -602,7 +628,10 @@ impl RenderState {
for stroke in shape.visible_strokes().rev() { for stroke in shape.visible_strokes().rev() {
shadows::render_stroke_drop_shadows(self, shape, stroke, antialias); shadows::render_stroke_drop_shadows(self, shape, stroke, antialias);
strokes::render(self, shape, stroke, None, None, None, antialias, None); //In clipped content strokes are drawn over the contained elements in a subsequent step
if !shape.clip() {
strokes::render(self, shape, stroke, None, None, None, antialias, None);
}
shadows::render_stroke_inner_shadows(self, shape, stroke, antialias); shadows::render_stroke_inner_shadows(self, shape, stroke, antialias);
} }
@@ -869,37 +898,22 @@ impl RenderState {
_ => {} _ => {}
} }
// Detect clipping and apply it properly //In clipped content strokes are drawn over the contained elements
if let Type::Frame(_) = &element.shape_type { if element.clip() {
if element.clip() { let mut element_strokes: Cow<Shape> = Cow::Borrowed(element);
let mut layer_paint = skia::Paint::default(); element_strokes.to_mut().clear_fills();
layer_paint.set_blend_mode(skia::BlendMode::DstIn); element_strokes.to_mut().clear_shadows();
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&layer_paint); element_strokes.to_mut().clip_content = false;
self.surfaces self.render_shape(
.canvas(SurfaceId::Current) tree,
.save_layer(&layer_rec); modifiers,
structure,
// We clip only the fills content &element_strokes,
let mut element_fills: Cow<Shape> = Cow::Borrowed(element); scale_content,
element_fills.to_mut().clear_strokes(); None,
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(tree, modifiers, structure, &element_fills, 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(tree, modifiers, structure, &element_strokes, 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.surfaces.canvas(SurfaceId::Current).restore();
self.focus_mode.exit(&element.id); self.focus_mode.exit(&element.id);
} }
@@ -957,7 +971,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;
@@ -1026,6 +1040,7 @@ impl RenderState {
structure, structure,
element, element,
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));
@@ -1035,7 +1050,7 @@ impl RenderState {
self.pending_nodes.push(NodeRenderState { self.pending_nodes.push(NodeRenderState {
id: node_id, id: node_id,
visited_children: true, visited_children: true,
clip_bounds: None, clip_bounds,
visited_mask: false, visited_mask: false,
mask, mask,
}); });