From a80f114d66f962cd8686936600f390d88dde2023 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Mon, 24 Mar 2025 11:49:38 +0100 Subject: [PATCH 1/2] :tada: Avoid rendering too small shapes --- render-wasm/src/render.rs | 3 ++- render-wasm/src/shapes.rs | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 4846879d3..5b6a409f1 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -586,7 +586,7 @@ impl RenderState { if !self.render_in_progress { return Ok(()); } - + let scale = self.get_scale(); let mut should_stop = false; while !should_stop { if let Some(current_tile) = self.current_tile { @@ -663,6 +663,7 @@ impl RenderState { } if !transformed_element.extrect().intersects(self.render_area) || transformed_element.hidden() + || transformed_element.visually_insignificant(scale) { debug::render_debug_shape(self, &transformed_element, false); continue; diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index 55ddbd77f..4973c000f 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -42,6 +42,8 @@ pub use transform::*; use crate::math; use crate::math::{Bounds, Matrix, Point}; +const MIN_VISIBLE_SIZE: f32 = 2.0; + #[derive(Debug, Clone, PartialEq)] pub enum Type { Frame(Frame), @@ -505,6 +507,11 @@ impl Shape { self.hidden } + pub fn visually_insignificant(&self, scale: f32) -> bool { + self.selrect.width() * scale < MIN_VISIBLE_SIZE + || self.selrect.height() * scale < MIN_VISIBLE_SIZE + } + // TODO: Maybe store this inside the shape pub fn bounds(&self) -> Bounds { let mut bounds = Bounds::new( From 85b24e1e8d857064a9f9158d2fba9a8b58ebe5b1 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Mon, 24 Mar 2025 12:54:41 +0100 Subject: [PATCH 2/2] :tada: Disable antialias for small shapes --- render-wasm/src/render.rs | 16 +++++--- render-wasm/src/render/fills.rs | 17 ++++---- render-wasm/src/render/shadows.rs | 48 +++++++++++++++------- render-wasm/src/render/strokes.rs | 66 ++++++++++++++++++++----------- render-wasm/src/shapes.rs | 6 +++ render-wasm/src/shapes/fills.rs | 10 ++--- render-wasm/src/shapes/shadows.rs | 12 +++--- render-wasm/src/shapes/strokes.rs | 8 ++-- 8 files changed, 119 insertions(+), 64 deletions(-) diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 5b6a409f1..4f046820f 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -306,6 +306,8 @@ impl RenderState { s.canvas().save(); }); + 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| { @@ -315,11 +317,13 @@ impl RenderState { 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, true); + 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, true); + s.canvas() + .clip_rect(bounds, skia::ClipOp::Intersect, antialias); }); } @@ -392,15 +396,15 @@ impl RenderState { ); for fill in shape.fills().rev() { - fills::render(self, &shape, fill); + fills::render(self, &shape, fill, antialias); } for stroke in shape.strokes().rev() { - strokes::render(self, &shape, stroke); + strokes::render(self, &shape, stroke, antialias); } - shadows::render_inner_shadows(self, &shape); - shadows::render_drop_shadows(self, &shape); + shadows::render_inner_shadows(self, &shape, antialias); + shadows::render_drop_shadows(self, &shape, antialias); } }; diff --git a/render-wasm/src/render/fills.rs b/render-wasm/src/render/fills.rs index 1efdc8fd8..3bdf61d33 100644 --- a/render-wasm/src/render/fills.rs +++ b/render-wasm/src/render/fills.rs @@ -9,6 +9,7 @@ fn draw_image_fill( shape: &Shape, image_fill: &ImageFill, paint: &Paint, + antialias: bool, ) { let image = render_state.images.get(&image_fill.id()); if image.is_none() { @@ -62,15 +63,15 @@ fn draw_image_fill( .. }) => { let rrect: RRect = RRect::new_rect_radii(container, &corners); - canvas.clip_rrect(rrect, skia::ClipOp::Intersect, true); + canvas.clip_rrect(rrect, skia::ClipOp::Intersect, antialias); } Type::Rect(_) | Type::Frame(_) => { - canvas.clip_rect(container, skia::ClipOp::Intersect, true); + canvas.clip_rect(container, skia::ClipOp::Intersect, antialias); } Type::Circle => { let mut oval_path = skia::Path::new(); oval_path.add_oval(container, None); - canvas.clip_path(&oval_path, skia::ClipOp::Intersect, true); + canvas.clip_path(&oval_path, skia::ClipOp::Intersect, antialias); } shape_type @ (Type::Path(_) | Type::Bool(_)) => { if let Some(path) = shape_type.path() { @@ -78,13 +79,13 @@ fn draw_image_fill( canvas.clip_path( &path.to_skia_path().transform(&path_transform), skia::ClipOp::Intersect, - true, + antialias, ); } } } Type::SVGRaw(_) => { - canvas.clip_rect(container, skia::ClipOp::Intersect, true); + canvas.clip_rect(container, skia::ClipOp::Intersect, antialias); } Type::Group(_) => unreachable!("A group should not have fills"), Type::Text(_) => unimplemented!("TODO"), @@ -108,12 +109,12 @@ fn draw_image_fill( /** * This SHOULD be the only public function in this module. */ -pub fn render(render_state: &mut RenderState, shape: &Shape, fill: &Fill) { - let paint = &fill.to_paint(&shape.selrect); +pub fn render(render_state: &mut RenderState, shape: &Shape, fill: &Fill, antialias: bool) { + let paint = &fill.to_paint(&shape.selrect, antialias); match (fill, &shape.shape_type) { (Fill::Image(image_fill), _) => { - draw_image_fill(render_state, shape, image_fill, paint); + draw_image_fill(render_state, shape, image_fill, paint, antialias); } (_, Type::Rect(_) | Type::Frame(_)) => { render_state diff --git a/render-wasm/src/render/shadows.rs b/render-wasm/src/render/shadows.rs index d64a9779a..4b9ab2ed4 100644 --- a/render-wasm/src/render/shadows.rs +++ b/render-wasm/src/render/shadows.rs @@ -3,27 +3,37 @@ use crate::shapes::{Shadow, Shape, Type}; use skia_safe::{self as skia, Paint}; // Drop Shadows -pub fn render_drop_shadows(render_state: &mut RenderState, shape: &Shape) { +pub fn render_drop_shadows(render_state: &mut RenderState, shape: &Shape, antialias: bool) { if shape.has_fills() { for shadow in shape.drop_shadows().rev().filter(|s| !s.hidden()) { - render_fill_drop_shadow(render_state, &shape, &shadow); + render_fill_drop_shadow(render_state, &shape, &shadow, antialias); } } else { let scale = render_state.get_scale(); for shadow in shape.drop_shadows().rev().filter(|s| !s.hidden()) { - render_stroke_drop_shadow(render_state, &shadow, scale); + render_stroke_drop_shadow(render_state, &shadow, scale, antialias); } } } -fn render_fill_drop_shadow(render_state: &mut RenderState, shape: &Shape, shadow: &Shadow) { - let paint = &shadow.get_drop_shadow_paint(); +fn render_fill_drop_shadow( + render_state: &mut RenderState, + shape: &Shape, + shadow: &Shadow, + antialias: bool, +) { + let paint = &shadow.get_drop_shadow_paint(antialias); render_shadow_paint(render_state, shape, paint, SurfaceId::DropShadows); } // TODO: Stroke shadows -fn render_stroke_drop_shadow(render_state: &mut RenderState, shadow: &Shadow, scale: f32) { - let shadow_paint = &shadow.to_paint(scale); +fn render_stroke_drop_shadow( + render_state: &mut RenderState, + shadow: &Shadow, + scale: f32, + antialias: bool, +) { + let shadow_paint = &shadow.to_paint(scale, antialias); render_state .surfaces @@ -42,27 +52,37 @@ fn render_stroke_drop_shadow(render_state: &mut RenderState, shadow: &Shadow, sc } // Inner Shadows -pub fn render_inner_shadows(render_state: &mut RenderState, shape: &Shape) { +pub fn render_inner_shadows(render_state: &mut RenderState, shape: &Shape, antialias: bool) { if shape.has_fills() { for shadow in shape.inner_shadows().rev().filter(|s| !s.hidden()) { - render_fill_inner_shadow(render_state, &shape, &shadow); + render_fill_inner_shadow(render_state, &shape, &shadow, antialias); } } else { let scale = render_state.get_scale(); for shadow in shape.inner_shadows().rev().filter(|s| !s.hidden()) { - render_stroke_inner_shadow(render_state, &shadow, scale); + render_stroke_inner_shadow(render_state, &shadow, scale, antialias); } } } -fn render_fill_inner_shadow(render_state: &mut RenderState, shape: &Shape, shadow: &Shadow) { - let paint = &shadow.get_inner_shadow_paint(); +fn render_fill_inner_shadow( + render_state: &mut RenderState, + shape: &Shape, + shadow: &Shadow, + antialias: bool, +) { + let paint = &shadow.get_inner_shadow_paint(antialias); render_shadow_paint(render_state, shape, paint, SurfaceId::InnerShadows); } // TODO: Stroke shadows -fn render_stroke_inner_shadow(render_state: &mut RenderState, shadow: &Shadow, scale: f32) { - let shadow_paint = &shadow.to_paint(scale); +fn render_stroke_inner_shadow( + render_state: &mut RenderState, + shadow: &Shadow, + scale: f32, + antialias: bool, +) { + let shadow_paint = &shadow.to_paint(scale, antialias); render_state .surfaces diff --git a/render-wasm/src/render/strokes.rs b/render-wasm/src/render/strokes.rs index 5084e2506..08c47b2f7 100644 --- a/render-wasm/src/render/strokes.rs +++ b/render-wasm/src/render/strokes.rs @@ -15,13 +15,14 @@ fn draw_stroke_on_rect( corners: &Option, svg_attrs: &HashMap, scale: f32, + antialias: bool, ) { // Draw the different kind of strokes for a rect is straightforward, we just need apply a stroke to: // - The same rect if it's a center stroke // - A bigger rect if it's an outer stroke // - A smaller rect if it's an outer stroke let stroke_rect = stroke.outer_rect(rect); - let paint = stroke.to_paint(selrect, svg_attrs, scale); + let paint = stroke.to_paint(selrect, svg_attrs, scale, antialias); match corners { Some(radii) => { @@ -42,13 +43,17 @@ fn draw_stroke_on_circle( selrect: &Rect, svg_attrs: &HashMap, scale: f32, + antialias: bool, ) { // Draw the different kind of strokes for an oval is straightforward, we just need apply a stroke to: // - The same oval if it's a center stroke // - A bigger oval if it's an outer stroke // - A smaller oval if it's an outer stroke let stroke_rect = stroke.outer_rect(rect); - canvas.draw_oval(&stroke_rect, &stroke.to_paint(selrect, svg_attrs, scale)); + canvas.draw_oval( + &stroke_rect, + &stroke.to_paint(selrect, svg_attrs, scale, antialias), + ); } fn draw_stroke_on_path( @@ -59,18 +64,20 @@ fn draw_stroke_on_path( path_transform: Option<&Matrix>, svg_attrs: &HashMap, scale: f32, + antialias: bool, ) { let mut skia_path = path.to_skia_path(); skia_path.transform(path_transform.unwrap()); let is_open = path.is_open(); - let paint_stroke = stroke.to_stroked_paint(is_open, selrect, svg_attrs, scale); + let paint_stroke = stroke.to_stroked_paint(is_open, selrect, svg_attrs, scale, antialias); + // Draw the different kind of strokes for a path requires different strategies: match stroke.render_kind(is_open) { // For inner stroke we draw a center stroke (with double width) and clip to the original path (that way the extra outer stroke is removed) StrokeKind::InnerStroke => { canvas.save(); // As we are using clear for surfaces we use save and restore here to still be able to clean the full surface - canvas.clip_path(&skia_path, skia::ClipOp::Intersect, true); + canvas.clip_path(&skia_path, skia::ClipOp::Intersect, antialias); canvas.draw_path(&skia_path, &paint_stroke); canvas.restore(); } @@ -82,7 +89,7 @@ fn draw_stroke_on_path( StrokeKind::OuterStroke => { let mut paint = skia::Paint::default(); paint.set_blend_mode(skia::BlendMode::SrcOver); - paint.set_anti_alias(true); + paint.set_anti_alias(antialias); let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint); canvas.save_layer(&layer_rec); @@ -90,7 +97,7 @@ fn draw_stroke_on_path( let mut clear_paint = skia::Paint::default(); clear_paint.set_blend_mode(skia::BlendMode::Clear); - clear_paint.set_anti_alias(true); + clear_paint.set_anti_alias(antialias); canvas.draw_path(&skia_path, &clear_paint); canvas.restore(); @@ -105,6 +112,7 @@ fn draw_stroke_on_path( is_open, svg_attrs, scale, + antialias, ); } @@ -155,6 +163,7 @@ fn handle_stroke_caps( is_open: bool, svg_attrs: &HashMap, scale: f32, + antialias: bool, ) { let points_count = path.count_points(); let mut points = vec![Point::default(); points_count]; @@ -165,7 +174,8 @@ fn handle_stroke_caps( let first_point = points.first().unwrap(); let last_point = points.last().unwrap(); - let mut paint_stroke = stroke.to_stroked_paint(is_open, selrect, svg_attrs, scale); + let mut paint_stroke = + stroke.to_stroked_paint(is_open, selrect, svg_attrs, scale, antialias); handle_stroke_cap( canvas, @@ -325,6 +335,7 @@ fn draw_image_stroke_in_container( shape: &Shape, stroke: &Stroke, image_fill: &ImageFill, + antialias: bool, ) { let image = render_state.images.get(&image_fill.id()); if image.is_none() { @@ -341,7 +352,7 @@ fn draw_image_stroke_in_container( // Save canvas and layer state let mut pb = skia::Paint::default(); pb.set_blend_mode(skia::BlendMode::SrcOver); - pb.set_anti_alias(true); + pb.set_anti_alias(antialias); let layer_rec = skia::canvas::SaveLayerRec::default().paint(&pb); canvas.save_layer(&layer_rec); @@ -359,11 +370,18 @@ fn draw_image_stroke_in_container( &shape_type.corners(), svg_attrs, scale, + antialias, ); } - Type::Circle => { - draw_stroke_on_circle(canvas, stroke, container, &outer_rect, svg_attrs, scale) - } + Type::Circle => draw_stroke_on_circle( + canvas, + stroke, + container, + &outer_rect, + svg_attrs, + scale, + antialias, + ), shape_type @ (Type::Path(_) | Type::Bool(_)) => { if let Some(p) = shape_type.path() { @@ -373,15 +391,16 @@ fn draw_image_stroke_in_container( let stroke_kind = stroke.render_kind(p.is_open()); match stroke_kind { StrokeKind::InnerStroke => { - canvas.clip_path(&path, skia::ClipOp::Intersect, true); + canvas.clip_path(&path, skia::ClipOp::Intersect, antialias); } StrokeKind::CenterStroke => {} StrokeKind::OuterStroke => { - canvas.clip_path(&path, skia::ClipOp::Difference, true); + canvas.clip_path(&path, skia::ClipOp::Difference, antialias); } } let is_open = p.is_open(); - let mut paint = stroke.to_stroked_paint(is_open, &outer_rect, svg_attrs, scale); + let mut paint = + stroke.to_stroked_paint(is_open, &outer_rect, svg_attrs, scale, antialias); canvas.draw_path(&path, &paint); if stroke.render_kind(is_open) == StrokeKind::OuterStroke { // Small extra inner stroke to overlap with the fill @@ -397,6 +416,7 @@ fn draw_image_stroke_in_container( is_open, svg_attrs, scale, + antialias, ); canvas.restore(); } @@ -410,11 +430,11 @@ fn draw_image_stroke_in_container( // stroke over the image. let mut image_paint = skia::Paint::default(); image_paint.set_blend_mode(skia::BlendMode::SrcIn); - image_paint.set_anti_alias(true); + image_paint.set_anti_alias(antialias); // Compute scaled rect and clip to it let dest_rect = calculate_scaled_rect(size, container, stroke.delta()); - canvas.clip_rect(dest_rect, skia::ClipOp::Intersect, true); + canvas.clip_rect(dest_rect, skia::ClipOp::Intersect, antialias); canvas.draw_image_rect(image.unwrap(), None, dest_rect, &image_paint); // Clear outer stroke for paths if necessary. When adding an outer stroke we need to empty the stroke added too in the inner area. @@ -424,7 +444,7 @@ fn draw_image_stroke_in_container( path.transform(&path_transform.unwrap()); let mut clear_paint = skia::Paint::default(); clear_paint.set_blend_mode(skia::BlendMode::Clear); - clear_paint.set_anti_alias(true); + clear_paint.set_anti_alias(antialias); canvas.draw_path(&path, &clear_paint); } } @@ -436,7 +456,7 @@ fn draw_image_stroke_in_container( /** * This SHOULD be the only public function in this module. */ -pub fn render(render_state: &mut RenderState, shape: &Shape, stroke: &Stroke) { +pub fn render(render_state: &mut RenderState, shape: &Shape, stroke: &Stroke, antialias: bool) { let scale = render_state.get_scale(); let canvas = render_state.surfaces.canvas(SurfaceId::Strokes); let selrect = shape.selrect; @@ -444,7 +464,7 @@ pub fn render(render_state: &mut RenderState, shape: &Shape, stroke: &Stroke) { let svg_attrs = &shape.svg_attrs; if let Fill::Image(image_fill) = &stroke.fill { - draw_image_stroke_in_container(render_state, shape, stroke, image_fill); + draw_image_stroke_in_container(render_state, shape, stroke, image_fill, antialias); } else { match &shape.shape_type { shape_type @ (Type::Rect(_) | Type::Frame(_)) => { @@ -456,11 +476,12 @@ pub fn render(render_state: &mut RenderState, shape: &Shape, stroke: &Stroke) { &shape_type.corners(), svg_attrs, scale, + antialias, ); } - Type::Circle => { - draw_stroke_on_circle(canvas, stroke, &selrect, &selrect, svg_attrs, scale) - } + Type::Circle => draw_stroke_on_circle( + canvas, stroke, &selrect, &selrect, svg_attrs, scale, antialias, + ), shape_type @ (Type::Path(_) | Type::Bool(_)) => { if let Some(path) = shape_type.path() { draw_stroke_on_path( @@ -471,6 +492,7 @@ pub fn render(render_state: &mut RenderState, shape: &Shape, stroke: &Stroke) { path_transform.as_ref(), svg_attrs, scale, + antialias, ); } } diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index 4973c000f..481316878 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -43,6 +43,7 @@ use crate::math; use crate::math::{Bounds, Matrix, Point}; const MIN_VISIBLE_SIZE: f32 = 2.0; +const ANTIALIAS_THRESHOLD: f32 = 15.0; #[derive(Debug, Clone, PartialEq)] pub enum Type { @@ -512,6 +513,11 @@ impl Shape { || self.selrect.height() * scale < MIN_VISIBLE_SIZE } + pub fn should_use_antialias(&self, scale: f32) -> bool { + self.selrect.width() * scale > ANTIALIAS_THRESHOLD + || self.selrect.height() * scale > ANTIALIAS_THRESHOLD + } + // TODO: Maybe store this inside the shape pub fn bounds(&self) -> Bounds { let mut bounds = Bounds::new( diff --git a/render-wasm/src/shapes/fills.rs b/render-wasm/src/shapes/fills.rs index d94c21fc0..1fb21eace 100644 --- a/render-wasm/src/shapes/fills.rs +++ b/render-wasm/src/shapes/fills.rs @@ -158,13 +158,13 @@ impl Fill { }) } - pub fn to_paint(&self, rect: &Rect) -> skia::Paint { + pub fn to_paint(&self, rect: &Rect, anti_alias: bool) -> skia::Paint { match self { Self::Solid(color) => { let mut p = skia::Paint::default(); p.set_color(*color); p.set_style(skia::PaintStyle::Fill); - p.set_anti_alias(true); + p.set_anti_alias(anti_alias); p.set_blend_mode(skia::BlendMode::SrcOver); p } @@ -173,7 +173,7 @@ impl Fill { p.set_shader(gradient.to_linear_shader(&rect)); p.set_alpha((gradient.opacity * 255.) as u8); p.set_style(skia::PaintStyle::Fill); - p.set_anti_alias(true); + p.set_anti_alias(anti_alias); p.set_blend_mode(skia::BlendMode::SrcOver); p } @@ -182,14 +182,14 @@ impl Fill { p.set_shader(gradient.to_radial_shader(&rect)); p.set_alpha((gradient.opacity * 255.) as u8); p.set_style(skia::PaintStyle::Fill); - p.set_anti_alias(true); + p.set_anti_alias(anti_alias); p.set_blend_mode(skia::BlendMode::SrcOver); p } Self::Image(image_fill) => { let mut p = skia::Paint::default(); p.set_style(skia::PaintStyle::Fill); - p.set_anti_alias(true); + p.set_anti_alias(anti_alias); p.set_blend_mode(skia::BlendMode::SrcOver); p.set_alpha(image_fill.opacity); p diff --git a/render-wasm/src/shapes/shadows.rs b/render-wasm/src/shapes/shadows.rs index 56bc6409c..999f1221c 100644 --- a/render-wasm/src/shapes/shadows.rs +++ b/render-wasm/src/shapes/shadows.rs @@ -62,7 +62,7 @@ impl Shadow { self.hidden } - pub fn to_paint(&self, scale: f32) -> skia::Paint { + pub fn to_paint(&self, scale: f32, antialias: bool) -> skia::Paint { let mut paint = skia::Paint::default(); let image_filter = match self.style { @@ -71,7 +71,7 @@ impl Shadow { }; paint.set_image_filter(image_filter); - paint.set_anti_alias(true); + paint.set_anti_alias(antialias); paint } @@ -128,13 +128,13 @@ impl Shadow { // New methods for Drop Shadows - pub fn get_drop_shadow_paint(&self) -> skia::Paint { + pub fn get_drop_shadow_paint(&self, antialias: bool) -> 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.set_anti_alias(antialias); paint } @@ -158,13 +158,13 @@ impl Shadow { // New methods for Inner Shadows - pub fn get_inner_shadow_paint(&self) -> skia::Paint { + pub fn get_inner_shadow_paint(&self, antialias: bool) -> skia::Paint { let mut paint = skia::Paint::default(); let image_filter = self.get_inner_shadow_filter(); paint.set_image_filter(image_filter); - paint.set_anti_alias(true); + paint.set_anti_alias(antialias); paint } diff --git a/render-wasm/src/shapes/strokes.rs b/render-wasm/src/shapes/strokes.rs index d68b95bbd..7f0fa692c 100644 --- a/render-wasm/src/shapes/strokes.rs +++ b/render-wasm/src/shapes/strokes.rs @@ -160,8 +160,9 @@ impl Stroke { rect: &Rect, svg_attrs: &HashMap, scale: f32, + antialias: bool, ) -> skia::Paint { - let mut paint = self.fill.to_paint(rect); + let mut paint = self.fill.to_paint(rect, antialias); paint.set_style(skia::PaintStyle::Stroke); let width = match self.kind { @@ -171,7 +172,7 @@ impl Stroke { }; paint.set_stroke_width(width); - paint.set_anti_alias(true); + paint.set_anti_alias(antialias); if let Some("round") = svg_attrs.get("stroke-linecap").map(String::as_str) { paint.set_stroke_cap(skia::paint::Cap::Round); @@ -225,8 +226,9 @@ impl Stroke { rect: &Rect, svg_attrs: &HashMap, scale: f32, + antialias: bool, ) -> skia::Paint { - let mut paint = self.to_paint(rect, svg_attrs, scale); + let mut paint = self.to_paint(rect, svg_attrs, scale, antialias); match self.render_kind(is_open) { StrokeKind::InnerStroke => { paint.set_stroke_width(2. * paint.stroke_width());