diff --git a/src/Skia/Avalonia.Skia/GeometryImpl.cs b/src/Skia/Avalonia.Skia/GeometryImpl.cs
index cd6fd76b3c..a5797d7fd5 100644
--- a/src/Skia/Avalonia.Skia/GeometryImpl.cs
+++ b/src/Skia/Avalonia.Skia/GeometryImpl.cs
@@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Skia.Helpers;
+using Avalonia.Utilities;
using SkiaSharp;
namespace Avalonia.Skia
@@ -78,10 +79,7 @@ namespace Avalonia.Skia
public IGeometryImpl GetWidenedGeometry(IPen pen)
{
- using var cache = new PathCache();
- cache.UpdateIfNeeded(StrokePath, pen, includeDashStyle: true);
-
- if (cache.ExpandedPath is { } path)
+ if (StrokePath is not null && SKPathHelper.CreateStrokedPath(StrokePath, pen) is { } path)
{
// The path returned to us by skia here does not have closed figures.
// Fix that by calling CreateClosedPath.
@@ -162,60 +160,34 @@ namespace Avalonia.Skia
private struct PathCache : IDisposable
{
- private double _width, _miterLimit;
- private PenLineCap _cap;
- private PenLineJoin _join;
+ private int _penHash;
private SKPath? _path, _cachedFor;
private Rect? _renderBounds;
private static readonly SKPath s_emptyPath = new();
-
public Rect RenderBounds => _renderBounds ??= (_path ?? _cachedFor ?? s_emptyPath).Bounds.ToAvaloniaRect();
public SKPath ExpandedPath => _path ?? s_emptyPath;
- public void UpdateIfNeeded(SKPath? strokePath, IPen? pen, bool includeDashStyle = false)
+ public void UpdateIfNeeded(SKPath? strokePath, IPen? pen)
{
- var strokeWidth = pen?.Thickness ?? 0;
- var miterLimit = pen?.MiterLimit ?? 0;
- var cap = pen?.LineCap ?? default;
- var join = pen?.LineJoin ?? default;
-
- if (_cachedFor == strokePath
- && _path != null
- && cap == _cap
- && join == _join
- && Math.Abs(_width - strokeWidth) < float.Epsilon
- && (join != PenLineJoin.Miter || Math.Abs(_miterLimit - miterLimit) > float.Epsilon))
+ if (PenHelper.GetHashCode(pen, includeBrush: false) is { } penHash &&
+ penHash == _penHash &&
+ strokePath == _cachedFor)
+ {
// We are up to date
return;
+ }
_renderBounds = null;
_cachedFor = strokePath;
- _width = strokeWidth;
- _cap = cap;
- _join = join;
- _miterLimit = miterLimit;
-
- if (strokePath == null || Math.Abs(strokeWidth) < float.Epsilon)
- {
- _path = null;
- return;
- }
-
- var paint = SKPaintCache.Shared.Get();
- paint.IsStroke = true;
- paint.StrokeWidth = (float)_width;
- paint.StrokeCap = cap.ToSKStrokeCap();
- paint.StrokeJoin = join.ToSKStrokeJoin();
- paint.StrokeMiter = (float)miterLimit;
-
- if (includeDashStyle && DrawingContextHelper.TryCreateDashEffect(pen, out var dashEffect))
- paint.PathEffect = dashEffect;
+ _penHash = penHash;
+ _path?.Dispose();
- _path = new SKPath();
- paint.GetFillPath(strokePath, _path);
+ if (strokePath is not null && pen is not null)
+ _path = SKPathHelper.CreateStrokedPath(strokePath, pen);
+ else
+ _path = null;
- SKPaintCache.Shared.ReturnReset(paint);
}
public void Dispose()
@@ -223,7 +195,6 @@ namespace Avalonia.Skia
_path?.Dispose();
_path = null;
}
-
}
}
}
diff --git a/src/Skia/Avalonia.Skia/Helpers/PenHelper.cs b/src/Skia/Avalonia.Skia/Helpers/PenHelper.cs
new file mode 100644
index 0000000000..2f7b6d771d
--- /dev/null
+++ b/src/Skia/Avalonia.Skia/Helpers/PenHelper.cs
@@ -0,0 +1,38 @@
+using System;
+using Avalonia.Media;
+
+namespace Avalonia.Skia.Helpers;
+
+internal static class PenHelper
+{
+ ///
+ /// Gets a hash code for a pen, optionally including the brush.
+ ///
+ /// The pen.
+ /// Whether to include the brush in the hash code.
+ /// The hash code.
+ public static int GetHashCode(IPen? pen, bool includeBrush)
+ {
+ if (pen is null)
+ return 0;
+
+ var hash = new HashCode();
+ hash.Add(pen.LineCap);
+ hash.Add(pen.LineJoin);
+ hash.Add(pen.MiterLimit);
+ hash.Add(pen.Thickness);
+
+ if (pen.DashStyle is { } dashStyle)
+ {
+ hash.Add(dashStyle.Offset);
+
+ for (var i = 0; i < dashStyle.Dashes?.Count; i++)
+ hash.Add(dashStyle.Dashes[i]);
+ }
+
+ if (includeBrush)
+ hash.Add(pen.Brush);
+
+ return hash.ToHashCode();
+ }
+}
diff --git a/src/Skia/Avalonia.Skia/Helpers/SKPathHelper.cs b/src/Skia/Avalonia.Skia/Helpers/SKPathHelper.cs
index 3bbb80e305..228d68a4d8 100644
--- a/src/Skia/Avalonia.Skia/Helpers/SKPathHelper.cs
+++ b/src/Skia/Avalonia.Skia/Helpers/SKPathHelper.cs
@@ -1,9 +1,17 @@
-using SkiaSharp;
+using System;
+using Avalonia.Media;
+using Avalonia.Utilities;
+using SkiaSharp;
namespace Avalonia.Skia.Helpers;
internal static class SKPathHelper
{
+ ///
+ /// Creates a new path that is a closed version of the source path.
+ ///
+ /// The source path.
+ /// A closed path.
public static SKPath CreateClosedPath(SKPath path)
{
using var iter = path.CreateIterator(true);
@@ -29,4 +37,31 @@ internal static class SKPathHelper
return rv;
}
+
+ ///
+ /// Creates a path that is the result of a pen being applied to the stroke of the given path.
+ ///
+ /// The path to stroke.
+ /// The pen to use to stroke the path.
+ /// The resulting path, or null if the pen has 0 thickness.
+ public static SKPath? CreateStrokedPath(SKPath path, IPen pen)
+ {
+ if (MathUtilities.IsZero(pen.Thickness))
+ return null;
+
+ var paint = SKPaintCache.Shared.Get();
+ paint.IsStroke = true;
+ paint.StrokeWidth = (float)pen.Thickness;
+ paint.StrokeCap = pen.LineCap.ToSKStrokeCap();
+ paint.StrokeJoin = pen.LineJoin.ToSKStrokeJoin();
+ paint.StrokeMiter = (float)pen.MiterLimit;
+
+ if (DrawingContextHelper.TryCreateDashEffect(pen, out var dashEffect))
+ paint.PathEffect = dashEffect;
+
+ var result = new SKPath();
+ paint.GetFillPath(path, result);
+ SKPaintCache.Shared.ReturnReset(paint);
+ return result;
+ }
}