🐛 Fix stroke artifacts

This commit is contained in:
Alejandro Alonso 2026-02-23 07:23:12 +01:00
parent c58054d19c
commit bdcf448f3f
5 changed files with 335 additions and 1 deletions

View File

@ -642,6 +642,10 @@ impl RenderState {
apply_to_current_surface: bool, apply_to_current_surface: bool,
offset: Option<(f32, f32)>, offset: Option<(f32, f32)>,
parent_shadows: Option<Vec<skia_safe::Paint>>, parent_shadows: Option<Vec<skia_safe::Paint>>,
<<<<<<< Updated upstream
=======
outset: Option<f32>,
>>>>>>> Stashed changes
) { ) {
let surface_ids = fills_surface_id as u32 let surface_ids = fills_surface_id as u32
| strokes_surface_id as u32 | strokes_surface_id as u32
@ -710,6 +714,10 @@ impl RenderState {
&visible_strokes, &visible_strokes,
Some(SurfaceId::Current), Some(SurfaceId::Current),
antialias, antialias,
<<<<<<< Updated upstream
=======
outset,
>>>>>>> Stashed changes
); );
self.surfaces.apply_mut(SurfaceId::Current as u32, |s| { self.surfaces.apply_mut(SurfaceId::Current as u32, |s| {
@ -1055,10 +1063,31 @@ impl RenderState {
{ {
if let Some(fills_to_render) = self.nested_fills.last() { if let Some(fills_to_render) = self.nested_fills.last() {
let fills_to_render = fills_to_render.clone(); let fills_to_render = fills_to_render.clone();
<<<<<<< Updated upstream
fills::render(self, shape, &fills_to_render, antialias, fills_surface_id); fills::render(self, shape, &fills_to_render, antialias, fills_surface_id);
} }
} else { } else {
fills::render(self, shape, &shape.fills, antialias, fills_surface_id); fills::render(self, shape, &shape.fills, antialias, fills_surface_id);
=======
fills::render(
self,
shape,
&fills_to_render,
antialias,
fills_surface_id,
outset,
);
}
} else {
fills::render(
self,
shape,
&shape.fills,
antialias,
fills_surface_id,
outset,
);
>>>>>>> Stashed changes
} }
// Skip stroke rendering for clipped frames - they are drawn in render_shape_exit // Skip stroke rendering for clipped frames - they are drawn in render_shape_exit
@ -1073,6 +1102,10 @@ impl RenderState {
&visible_strokes, &visible_strokes,
Some(strokes_surface_id), Some(strokes_surface_id),
antialias, antialias,
<<<<<<< Updated upstream
=======
outset,
>>>>>>> Stashed changes
); );
if !fast_mode { if !fast_mode {
for stroke in &visible_strokes { for stroke in &visible_strokes {

View File

@ -2,7 +2,15 @@ use skia_safe::{self as skia, Paint, RRect};
use super::{filters, RenderState, SurfaceId}; use super::{filters, RenderState, SurfaceId};
use crate::render::get_source_rect; use crate::render::get_source_rect;
use crate::shapes::{merge_fills, Fill, Frame, ImageFill, Rect, Shape, Type}; use crate::shapes::{merge_fills, Fill, Frame, ImageFill, Rect, Shape, StrokeKind, Type};
/// True when the shape has at least one visible inner stroke.
fn has_inner_stroke(shape: &Shape) -> bool {
let is_open = shape.is_open();
shape
.visible_strokes()
.any(|s| s.render_kind(is_open) == StrokeKind::Inner)
}
fn draw_image_fill( fn draw_image_fill(
render_state: &mut RenderState, render_state: &mut RenderState,
@ -97,6 +105,10 @@ pub fn render(
fills: &[Fill], fills: &[Fill],
antialias: bool, antialias: bool,
surface_id: SurfaceId, surface_id: SurfaceId,
<<<<<<< Updated upstream
=======
outset: Option<f32>,
>>>>>>> Stashed changes
) { ) {
if fills.is_empty() { if fills.is_empty() {
return; return;
@ -106,8 +118,18 @@ pub fn render(
// and sampling options that get_fill_shader (used by merge_fills) lacks. // and sampling options that get_fill_shader (used by merge_fills) lacks.
let has_image_fills = fills.iter().any(|f| matches!(f, Fill::Image(_))); let has_image_fills = fills.iter().any(|f| matches!(f, Fill::Image(_)));
if has_image_fills { if has_image_fills {
let scale = render_state.get_scale().max(1e-6);
let inset = if has_inner_stroke(shape) {
Some(1.0 / scale)
} else {
None
};
for fill in fills.iter().rev() { for fill in fills.iter().rev() {
<<<<<<< Updated upstream
render_single_fill(render_state, shape, fill, antialias, surface_id); render_single_fill(render_state, shape, fill, antialias, surface_id);
=======
render_single_fill(render_state, shape, fill, antialias, surface_id, outset, inset);
>>>>>>> Stashed changes
} }
return; return;
} }
@ -115,6 +137,13 @@ pub fn render(
let mut paint = merge_fills(fills, shape.selrect); let mut paint = merge_fills(fills, shape.selrect);
paint.set_anti_alias(antialias); paint.set_anti_alias(antialias);
let scale = render_state.get_scale().max(1e-6);
let inset = if has_inner_stroke(shape) {
Some(1.0 / scale)
} else {
None
};
if let Some(image_filter) = shape.image_filter(1.) { if let Some(image_filter) = shape.image_filter(1.) {
let bounds = image_filter.compute_fast_bounds(shape.selrect); let bounds = image_filter.compute_fast_bounds(shape.selrect);
if filters::render_with_filter_surface( if filters::render_with_filter_surface(
@ -124,7 +153,11 @@ pub fn render(
|state, temp_surface| { |state, temp_surface| {
let mut filtered_paint = paint.clone(); let mut filtered_paint = paint.clone();
filtered_paint.set_image_filter(image_filter.clone()); filtered_paint.set_image_filter(image_filter.clone());
<<<<<<< Updated upstream
draw_fill_to_surface(state, shape, temp_surface, &filtered_paint); draw_fill_to_surface(state, shape, temp_surface, &filtered_paint);
=======
draw_fill_to_surface(state, shape, temp_surface, &filtered_paint, outset, inset);
>>>>>>> Stashed changes
}, },
) { ) {
return; return;
@ -133,28 +166,53 @@ pub fn render(
} }
} }
<<<<<<< Updated upstream
draw_fill_to_surface(render_state, shape, surface_id, &paint); draw_fill_to_surface(render_state, shape, surface_id, &paint);
=======
draw_fill_to_surface(render_state, shape, surface_id, &paint, outset, inset);
>>>>>>> Stashed changes
} }
/// Draws a single paint (with a merged shader) to the appropriate surface /// Draws a single paint (with a merged shader) to the appropriate surface
/// based on the shape type. /// based on the shape type.
/// When `inset` is Some(eps), the fill is inset by eps (e.g. to avoid seam with inner strokes).
fn draw_fill_to_surface( fn draw_fill_to_surface(
render_state: &mut RenderState, render_state: &mut RenderState,
shape: &Shape, shape: &Shape,
surface_id: SurfaceId, surface_id: SurfaceId,
paint: &Paint, paint: &Paint,
<<<<<<< Updated upstream
) { ) {
match &shape.shape_type { match &shape.shape_type {
Type::Rect(_) | Type::Frame(_) => { Type::Rect(_) | Type::Frame(_) => {
render_state.surfaces.draw_rect_to(surface_id, shape, paint); render_state.surfaces.draw_rect_to(surface_id, shape, paint);
=======
outset: Option<f32>,
inset: Option<f32>,
) {
match &shape.shape_type {
Type::Rect(_) | Type::Frame(_) => {
render_state
.surfaces
.draw_rect_to(surface_id, shape, paint, outset, inset);
>>>>>>> Stashed changes
} }
Type::Circle => { Type::Circle => {
render_state render_state
.surfaces .surfaces
<<<<<<< Updated upstream
.draw_circle_to(surface_id, shape, paint); .draw_circle_to(surface_id, shape, paint);
} }
Type::Path(_) | Type::Bool(_) => { Type::Path(_) | Type::Bool(_) => {
render_state.surfaces.draw_path_to(surface_id, shape, paint); render_state.surfaces.draw_path_to(surface_id, shape, paint);
=======
.draw_circle_to(surface_id, shape, paint, outset, inset);
}
Type::Path(_) | Type::Bool(_) => {
render_state
.surfaces
.draw_path_to(surface_id, shape, paint, outset);
>>>>>>> Stashed changes
} }
Type::Group(_) => {} Type::Group(_) => {}
_ => unreachable!("This shape should not have fills"), _ => unreachable!("This shape should not have fills"),
@ -167,6 +225,11 @@ fn render_single_fill(
fill: &Fill, fill: &Fill,
antialias: bool, antialias: bool,
surface_id: SurfaceId, surface_id: SurfaceId,
<<<<<<< Updated upstream
=======
outset: Option<f32>,
inset: Option<f32>,
>>>>>>> Stashed changes
) { ) {
let mut paint = fill.to_paint(&shape.selrect, antialias); let mut paint = fill.to_paint(&shape.selrect, antialias);
if let Some(image_filter) = shape.image_filter(1.) { if let Some(image_filter) = shape.image_filter(1.) {
@ -185,6 +248,11 @@ fn render_single_fill(
antialias, antialias,
temp_surface, temp_surface,
&filtered_paint, &filtered_paint,
<<<<<<< Updated upstream
=======
outset,
inset,
>>>>>>> Stashed changes
); );
}, },
) { ) {
@ -194,7 +262,20 @@ fn render_single_fill(
} }
} }
<<<<<<< Updated upstream
draw_single_fill_to_surface(render_state, shape, fill, antialias, surface_id, &paint); draw_single_fill_to_surface(render_state, shape, fill, antialias, surface_id, &paint);
=======
draw_single_fill_to_surface(
render_state,
shape,
fill,
antialias,
surface_id,
&paint,
outset,
inset,
);
>>>>>>> Stashed changes
} }
fn draw_single_fill_to_surface( fn draw_single_fill_to_surface(
@ -204,6 +285,11 @@ fn draw_single_fill_to_surface(
antialias: bool, antialias: bool,
surface_id: SurfaceId, surface_id: SurfaceId,
paint: &Paint, paint: &Paint,
<<<<<<< Updated upstream
=======
outset: Option<f32>,
inset: Option<f32>,
>>>>>>> Stashed changes
) { ) {
match (fill, &shape.shape_type) { match (fill, &shape.shape_type) {
(Fill::Image(image_fill), _) => { (Fill::Image(image_fill), _) => {
@ -217,15 +303,30 @@ fn draw_single_fill_to_surface(
); );
} }
(_, Type::Rect(_) | Type::Frame(_)) => { (_, Type::Rect(_) | Type::Frame(_)) => {
<<<<<<< Updated upstream
render_state.surfaces.draw_rect_to(surface_id, shape, paint); render_state.surfaces.draw_rect_to(surface_id, shape, paint);
=======
render_state
.surfaces
.draw_rect_to(surface_id, shape, paint, outset, inset);
>>>>>>> Stashed changes
} }
(_, Type::Circle) => { (_, Type::Circle) => {
render_state render_state
.surfaces .surfaces
<<<<<<< Updated upstream
.draw_circle_to(surface_id, shape, paint); .draw_circle_to(surface_id, shape, paint);
} }
(_, Type::Path(_)) | (_, Type::Bool(_)) => { (_, Type::Path(_)) | (_, Type::Bool(_)) => {
render_state.surfaces.draw_path_to(surface_id, shape, paint); render_state.surfaces.draw_path_to(surface_id, shape, paint);
=======
.draw_circle_to(surface_id, shape, paint, outset, inset);
}
(_, Type::Path(_)) | (_, Type::Bool(_)) => {
render_state
.surfaces
.draw_path_to(surface_id, shape, paint, outset);
>>>>>>> Stashed changes
} }
(_, Type::Group(_)) => { (_, Type::Group(_)) => {
// Groups can have fills but they propagate them to their children // Groups can have fills but they propagate them to their children

View File

@ -106,12 +106,22 @@ fn render_shadow_paint(
) { ) {
match &shape.shape_type { match &shape.shape_type {
Type::Rect(_) | Type::Frame(_) => { Type::Rect(_) | Type::Frame(_) => {
<<<<<<< Updated upstream
render_state.surfaces.draw_rect_to(surface_id, shape, paint); render_state.surfaces.draw_rect_to(surface_id, shape, paint);
=======
render_state
.surfaces
.draw_rect_to(surface_id, shape, paint, None, None);
>>>>>>> Stashed changes
} }
Type::Circle => { Type::Circle => {
render_state render_state
.surfaces .surfaces
<<<<<<< Updated upstream
.draw_circle_to(surface_id, shape, paint); .draw_circle_to(surface_id, shape, paint);
=======
.draw_circle_to(surface_id, shape, paint, None, None);
>>>>>>> Stashed changes
} }
Type::Path(_) | Type::Bool(_) => { Type::Path(_) | Type::Bool(_) => {
render_state.surfaces.draw_path_to(surface_id, shape, paint); render_state.surfaces.draw_path_to(surface_id, shape, paint);

View File

@ -526,6 +526,10 @@ pub fn render(
strokes: &[&Stroke], strokes: &[&Stroke],
surface_id: Option<SurfaceId>, surface_id: Option<SurfaceId>,
antialias: bool, antialias: bool,
<<<<<<< Updated upstream
=======
outset: Option<f32>,
>>>>>>> Stashed changes
) { ) {
if strokes.is_empty() { if strokes.is_empty() {
return; return;
@ -540,6 +544,13 @@ pub fn render(
// edges semi-transparent and revealing strokes underneath. // edges semi-transparent and revealing strokes underneath.
if let Some(image_filter) = shape.image_filter(1.) { if let Some(image_filter) = shape.image_filter(1.) {
let mut content_bounds = shape.selrect; let mut content_bounds = shape.selrect;
<<<<<<< Updated upstream
=======
// Expand for outset if provided
if let Some(s) = outset.filter(|&s| s > 0.0) {
content_bounds.outset((s, s));
}
>>>>>>> Stashed changes
let max_margin = strokes let max_margin = strokes
.iter() .iter()
.map(|s| s.bounds_width(shape.is_open())) .map(|s| s.bounds_width(shape.is_open()))
@ -583,6 +594,10 @@ pub fn render(
antialias, antialias,
true, true,
true, true,
<<<<<<< Updated upstream
=======
outset,
>>>>>>> Stashed changes
); );
} }
@ -595,12 +610,36 @@ pub fn render(
// No blur or filter surface unavailable — draw strokes individually. // No blur or filter surface unavailable — draw strokes individually.
for stroke in strokes.iter().rev() { for stroke in strokes.iter().rev() {
<<<<<<< Updated upstream
render_single(render_state, shape, stroke, surface_id, None, antialias); render_single(render_state, shape, stroke, surface_id, None, antialias);
=======
render_single(
render_state,
shape,
stroke,
surface_id,
None,
antialias,
outset,
);
>>>>>>> Stashed changes
} }
return; return;
} }
<<<<<<< Updated upstream
render_merged(render_state, shape, strokes, surface_id, antialias, false); render_merged(render_state, shape, strokes, surface_id, antialias, false);
=======
render_merged(
render_state,
shape,
strokes,
surface_id,
antialias,
false,
outset,
);
>>>>>>> Stashed changes
} }
fn strokes_share_geometry(strokes: &[&Stroke]) -> bool { fn strokes_share_geometry(strokes: &[&Stroke]) -> bool {
@ -620,6 +659,10 @@ fn render_merged(
surface_id: Option<SurfaceId>, surface_id: Option<SurfaceId>,
antialias: bool, antialias: bool,
bypass_filter: bool, bypass_filter: bool,
<<<<<<< Updated upstream
=======
outset: Option<f32>,
>>>>>>> Stashed changes
) { ) {
let representative = *strokes let representative = *strokes
.last() .last()
@ -635,6 +678,13 @@ fn render_merged(
if !bypass_filter { if !bypass_filter {
if let Some(image_filter) = blur_filter.clone() { if let Some(image_filter) = blur_filter.clone() {
let mut content_bounds = shape.selrect; let mut content_bounds = shape.selrect;
<<<<<<< Updated upstream
=======
// Expand for outset if provided
if let Some(s) = outset.filter(|&s| s > 0.0) {
content_bounds.outset((s, s));
}
>>>>>>> Stashed changes
let stroke_margin = representative.bounds_width(shape.is_open()); let stroke_margin = representative.bounds_width(shape.is_open());
if stroke_margin > 0.0 { if stroke_margin > 0.0 {
content_bounds.inset((-stroke_margin, -stroke_margin)); content_bounds.inset((-stroke_margin, -stroke_margin));
@ -660,7 +710,19 @@ fn render_merged(
canvas.save_layer(&layer_rec); canvas.save_layer(&layer_rec);
}); });
<<<<<<< Updated upstream
render_merged(state, shape, strokes, Some(temp_surface), antialias, true); render_merged(state, shape, strokes, Some(temp_surface), antialias, true);
=======
render_merged(
state,
shape,
strokes,
Some(temp_surface),
antialias,
true,
outset,
);
>>>>>>> Stashed changes
state.surfaces.apply_mut(temp_surface as u32, |surface| { state.surfaces.apply_mut(temp_surface as u32, |surface| {
surface.canvas().restore(); surface.canvas().restore();
@ -676,7 +738,20 @@ fn render_merged(
// via SrcOver), matching the non-merged path where strokes[0] is drawn last (on top). // via SrcOver), matching the non-merged path where strokes[0] is drawn last (on top).
let fills: Vec<Fill> = strokes.iter().map(|s| s.fill.clone()).collect(); let fills: Vec<Fill> = strokes.iter().map(|s| s.fill.clone()).collect();
<<<<<<< Updated upstream
let merged = merge_fills(&fills, shape.selrect); let merged = merge_fills(&fills, shape.selrect);
=======
// Expand selrect if outset is provided
let selrect = if let Some(s) = outset.filter(|&s| s > 0.0) {
let mut r = shape.selrect;
r.outset((s, s));
r
} else {
shape.selrect
};
let merged = merge_fills(&fills, selrect);
>>>>>>> Stashed changes
let scale = render_state.get_scale(); let scale = render_state.get_scale();
let target_surface = surface_id.unwrap_or(SurfaceId::Strokes); let target_surface = surface_id.unwrap_or(SurfaceId::Strokes);
let canvas = render_state.surfaces.canvas_and_mark_dirty(target_surface); let canvas = render_state.surfaces.canvas_and_mark_dirty(target_surface);
@ -747,6 +822,10 @@ pub fn render_single(
surface_id: Option<SurfaceId>, surface_id: Option<SurfaceId>,
shadow: Option<&ImageFilter>, shadow: Option<&ImageFilter>,
antialias: bool, antialias: bool,
<<<<<<< Updated upstream
=======
outset: Option<f32>,
>>>>>>> Stashed changes
) { ) {
render_single_internal( render_single_internal(
render_state, render_state,
@ -757,6 +836,10 @@ pub fn render_single(
antialias, antialias,
false, false,
false, false,
<<<<<<< Updated upstream
=======
outset,
>>>>>>> Stashed changes
); );
} }
@ -770,10 +853,21 @@ fn render_single_internal(
antialias: bool, antialias: bool,
bypass_filter: bool, bypass_filter: bool,
skip_blur: bool, skip_blur: bool,
<<<<<<< Updated upstream
=======
outset: Option<f32>,
>>>>>>> Stashed changes
) { ) {
if !bypass_filter { if !bypass_filter {
if let Some(image_filter) = shape.image_filter(1.) { if let Some(image_filter) = shape.image_filter(1.) {
let mut content_bounds = shape.selrect; let mut content_bounds = shape.selrect;
<<<<<<< Updated upstream
=======
// Expand for outset if provided
if let Some(s) = outset.filter(|&s| s > 0.0) {
content_bounds.outset((s, s));
}
>>>>>>> Stashed changes
let stroke_margin = stroke.bounds_width(shape.is_open()); let stroke_margin = stroke.bounds_width(shape.is_open());
if stroke_margin > 0.0 { if stroke_margin > 0.0 {
content_bounds.inset((-stroke_margin, -stroke_margin)); content_bounds.inset((-stroke_margin, -stroke_margin));
@ -799,6 +893,10 @@ fn render_single_internal(
antialias, antialias,
true, true,
true, true,
<<<<<<< Updated upstream
=======
outset,
>>>>>>> Stashed changes
); );
}, },
) { ) {
@ -867,7 +965,25 @@ fn render_single_internal(
shape_type @ (Type::Path(_) | Type::Bool(_)) => { shape_type @ (Type::Path(_) | Type::Bool(_)) => {
if let Some(path) = shape_type.path() { if let Some(path) = shape_type.path() {
let is_open = path.is_open(); let is_open = path.is_open();
<<<<<<< Updated upstream
let paint = stroke.to_stroked_paint(is_open, &selrect, svg_attrs, antialias); let paint = stroke.to_stroked_paint(is_open, &selrect, svg_attrs, antialias);
=======
let mut paint =
stroke.to_stroked_paint(is_open, &selrect, svg_attrs, antialias);
// Apply outset by increasing stroke width
if let Some(s) = outset.filter(|&s| s > 0.0) {
let current_width = paint.stroke_width();
// Path stroke kinds are built differently:
// - Center uses the stroke width directly.
// - Inner/Outer use a doubled width plus clipping/clearing logic.
// Compensate outset so visual growth is comparable across kinds.
let outset_growth = match stroke.render_kind(is_open) {
StrokeKind::Center => s * 2.0,
StrokeKind::Inner | StrokeKind::Outer => s * 4.0,
};
paint.set_stroke_width(current_width + outset_growth);
}
>>>>>>> Stashed changes
draw_stroke_on_path( draw_stroke_on_path(
canvas, canvas,
stroke, stroke,

View File

@ -355,9 +355,42 @@ impl Surfaces {
)); ));
} }
<<<<<<< Updated upstream
pub fn draw_rect_to(&mut self, id: SurfaceId, shape: &Shape, paint: &Paint) { pub fn draw_rect_to(&mut self, id: SurfaceId, shape: &Shape, paint: &Paint) {
if let Some(corners) = shape.shape_type.corners() { if let Some(corners) = shape.shape_type.corners() {
let rrect = RRect::new_rect_radii(shape.selrect, &corners); let rrect = RRect::new_rect_radii(shape.selrect, &corners);
=======
pub fn draw_rect_to(
&mut self,
id: SurfaceId,
shape: &Shape,
paint: &Paint,
outset: Option<f32>,
inset: Option<f32>,
) {
let mut rect = if let Some(s) = outset.filter(|&s| s > 0.0) {
let mut r = shape.selrect;
r.outset((s, s));
r
} else {
shape.selrect
};
if let Some(eps) = inset.filter(|&e| e > 0.0) {
rect.inset((eps, eps));
}
if let Some(corners) = shape.shape_type.corners() {
let corners = if let Some(eps) = inset.filter(|&e| e > 0.0) {
let mut c = corners;
for r in c.iter_mut() {
r.x = (r.x - eps).max(0.0);
r.y = (r.y - eps).max(0.0);
}
c
} else {
corners
};
let rrect = RRect::new_rect_radii(rect, &corners);
>>>>>>> Stashed changes
self.canvas_and_mark_dirty(id).draw_rrect(rrect, paint); self.canvas_and_mark_dirty(id).draw_rrect(rrect, paint);
} else { } else {
self.canvas_and_mark_dirty(id) self.canvas_and_mark_dirty(id)
@ -365,6 +398,7 @@ impl Surfaces {
} }
} }
<<<<<<< Updated upstream
pub fn draw_circle_to(&mut self, id: SurfaceId, shape: &Shape, paint: &Paint) { pub fn draw_circle_to(&mut self, id: SurfaceId, shape: &Shape, paint: &Paint) {
self.canvas_and_mark_dirty(id) self.canvas_and_mark_dirty(id)
.draw_oval(shape.selrect, paint); .draw_oval(shape.selrect, paint);
@ -373,6 +407,46 @@ impl Surfaces {
pub fn draw_path_to(&mut self, id: SurfaceId, shape: &Shape, paint: &Paint) { pub fn draw_path_to(&mut self, id: SurfaceId, shape: &Shape, paint: &Paint) {
if let Some(path) = shape.get_skia_path() { if let Some(path) = shape.get_skia_path() {
self.canvas_and_mark_dirty(id).draw_path(&path, paint); self.canvas_and_mark_dirty(id).draw_path(&path, paint);
=======
pub fn draw_circle_to(
&mut self,
id: SurfaceId,
shape: &Shape,
paint: &Paint,
outset: Option<f32>,
inset: Option<f32>,
) {
let mut rect = if let Some(s) = outset.filter(|&s| s > 0.0) {
let mut r = shape.selrect;
r.outset((s, s));
r
} else {
shape.selrect
};
if let Some(eps) = inset.filter(|&e| e > 0.0) {
rect.inset((eps, eps));
}
self.canvas_and_mark_dirty(id).draw_oval(rect, paint);
}
pub fn draw_path_to(
&mut self,
id: SurfaceId,
shape: &Shape,
paint: &Paint,
outset: Option<f32>,
) {
if let Some(path) = shape.get_skia_path() {
let canvas = self.canvas_and_mark_dirty(id);
if let Some(s) = outset.filter(|&s| s > 0.0) {
// Draw path as a thick stroke to get outset (expanded) silhouette
let mut stroke_paint = paint.clone();
stroke_paint.set_stroke_width(s * 2.0);
canvas.draw_path(&path, &stroke_paint);
} else {
canvas.draw_path(&path, paint);
}
>>>>>>> Stashed changes
} }
} }