Browse Source

Refactor creating a stroked path.

- Move the logic into `SKPathHelper.CreateStrokedPath`; called from `PathHelper` and `GetWidenedGeometry`
- Use a pen hash code to check if we're up-to-date in `PathHelper` and include the dash style in that
pull/12724/head
Steven Kirk 3 years ago
parent
commit
4e011d4fed
  1. 59
      src/Skia/Avalonia.Skia/GeometryImpl.cs
  2. 38
      src/Skia/Avalonia.Skia/Helpers/PenHelper.cs
  3. 37
      src/Skia/Avalonia.Skia/Helpers/SKPathHelper.cs

59
src/Skia/Avalonia.Skia/GeometryImpl.cs

@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Skia.Helpers; using Avalonia.Skia.Helpers;
using Avalonia.Utilities;
using SkiaSharp; using SkiaSharp;
namespace Avalonia.Skia namespace Avalonia.Skia
@ -78,10 +79,7 @@ namespace Avalonia.Skia
public IGeometryImpl GetWidenedGeometry(IPen pen) public IGeometryImpl GetWidenedGeometry(IPen pen)
{ {
using var cache = new PathCache(); if (StrokePath is not null && SKPathHelper.CreateStrokedPath(StrokePath, pen) is { } path)
cache.UpdateIfNeeded(StrokePath, pen, includeDashStyle: true);
if (cache.ExpandedPath is { } path)
{ {
// The path returned to us by skia here does not have closed figures. // The path returned to us by skia here does not have closed figures.
// Fix that by calling CreateClosedPath. // Fix that by calling CreateClosedPath.
@ -162,60 +160,34 @@ namespace Avalonia.Skia
private struct PathCache : IDisposable private struct PathCache : IDisposable
{ {
private double _width, _miterLimit; private int _penHash;
private PenLineCap _cap;
private PenLineJoin _join;
private SKPath? _path, _cachedFor; private SKPath? _path, _cachedFor;
private Rect? _renderBounds; private Rect? _renderBounds;
private static readonly SKPath s_emptyPath = new(); private static readonly SKPath s_emptyPath = new();
public Rect RenderBounds => _renderBounds ??= (_path ?? _cachedFor ?? s_emptyPath).Bounds.ToAvaloniaRect(); public Rect RenderBounds => _renderBounds ??= (_path ?? _cachedFor ?? s_emptyPath).Bounds.ToAvaloniaRect();
public SKPath ExpandedPath => _path ?? s_emptyPath; 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; if (PenHelper.GetHashCode(pen, includeBrush: false) is { } penHash &&
var miterLimit = pen?.MiterLimit ?? 0; penHash == _penHash &&
var cap = pen?.LineCap ?? default; strokePath == _cachedFor)
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))
// We are up to date // We are up to date
return; return;
}
_renderBounds = null; _renderBounds = null;
_cachedFor = strokePath; _cachedFor = strokePath;
_width = strokeWidth; _penHash = penHash;
_cap = cap; _path?.Dispose();
_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;
_path = new SKPath(); if (strokePath is not null && pen is not null)
paint.GetFillPath(strokePath, _path); _path = SKPathHelper.CreateStrokedPath(strokePath, pen);
else
_path = null;
SKPaintCache.Shared.ReturnReset(paint);
} }
public void Dispose() public void Dispose()
@ -223,7 +195,6 @@ namespace Avalonia.Skia
_path?.Dispose(); _path?.Dispose();
_path = null; _path = null;
} }
} }
} }
} }

38
src/Skia/Avalonia.Skia/Helpers/PenHelper.cs

@ -0,0 +1,38 @@
using System;
using Avalonia.Media;
namespace Avalonia.Skia.Helpers;
internal static class PenHelper
{
/// <summary>
/// Gets a hash code for a pen, optionally including the brush.
/// </summary>
/// <param name="pen">The pen.</param>
/// <param name="includeBrush">Whether to include the brush in the hash code.</param>
/// <returns>The hash code.</returns>
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();
}
}

37
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; namespace Avalonia.Skia.Helpers;
internal static class SKPathHelper internal static class SKPathHelper
{ {
/// <summary>
/// Creates a new path that is a closed version of the source path.
/// </summary>
/// <param name="path">The source path.</param>
/// <returns>A closed path.</returns>
public static SKPath CreateClosedPath(SKPath path) public static SKPath CreateClosedPath(SKPath path)
{ {
using var iter = path.CreateIterator(true); using var iter = path.CreateIterator(true);
@ -29,4 +37,31 @@ internal static class SKPathHelper
return rv; return rv;
} }
/// <summary>
/// Creates a path that is the result of a pen being applied to the stroke of the given path.
/// </summary>
/// <param name="path">The path to stroke.</param>
/// <param name="pen">The pen to use to stroke the path.</param>
/// <returns>The resulting path, or null if the pen has 0 thickness.</returns>
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;
}
} }

Loading…
Cancel
Save