Skip to content

Commit

Permalink
Add isStroked overload for IGeometryContext (#15430)
Browse files Browse the repository at this point in the history
* add isStroked overload for IGeometryContext

* move IGeometryContextEx

* fix Fill Path line

* remove added whitespaces

* close a figure to non-stroke lines with a line

* add geometry tests

* add wpf test files

* Added a test for tracking path closure with missing strokes for various line caps/joins

* add IsStroke overload for other segments

* update tests

* Skip line join test for closed geometry with holes for now

---------

Co-authored-by: Nikita Tsukanov <keks9n@gmail.com>
  • Loading branch information
2 people authored and maxkatz6 committed May 8, 2024
1 parent de72a96 commit 9395904
Show file tree
Hide file tree
Showing 21 changed files with 923 additions and 256 deletions.
2 changes: 1 addition & 1 deletion src/Avalonia.Base/Media/ArcSegment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public SweepDirection SweepDirection

internal override void ApplyTo(StreamGeometryContext ctx)
{
ctx.ArcTo(Point, Size, RotationAngle, IsLargeArc, SweepDirection);
ctx.ArcTo(Point, Size, RotationAngle, IsLargeArc, SweepDirection, IsStroked);
}

public override string ToString()
Expand Down
2 changes: 1 addition & 1 deletion src/Avalonia.Base/Media/BezierSegment .cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public Point Point3

internal override void ApplyTo(StreamGeometryContext ctx)
{
ctx.CubicBezierTo(Point1, Point2, Point3);
ctx.CubicBezierTo(Point1, Point2, Point3, IsStroked);
}

public override string ToString()
Expand Down
2 changes: 1 addition & 1 deletion src/Avalonia.Base/Media/LineSegment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public Point Point

internal override void ApplyTo(StreamGeometryContext ctx)
{
ctx.LineTo(Point);
ctx.LineTo(Point, IsStroked);
}

public override string ToString()
Expand Down
9 changes: 9 additions & 0 deletions src/Avalonia.Base/Media/PathSegment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,14 @@ namespace Avalonia.Media
public abstract class PathSegment : AvaloniaObject
{
internal abstract void ApplyTo(StreamGeometryContext ctx);

public static readonly StyledProperty<bool> IsStrokedProperty =
AvaloniaProperty.Register<PathSegment, bool>(nameof(IsStroked), true);

public bool IsStroked
{
get => GetValue(IsStrokedProperty);
set => SetValue(IsStrokedProperty, value);
}
}
}
2 changes: 1 addition & 1 deletion src/Avalonia.Base/Media/QuadraticBezierSegment .cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public Point Point2

internal override void ApplyTo(StreamGeometryContext ctx)
{
ctx.QuadraticBezierTo(Point1, Point2);
ctx.QuadraticBezierTo(Point1, Point2, IsStroked);
}

public override string ToString()
Expand Down
43 changes: 42 additions & 1 deletion src/Avalonia.Base/Media/StreamGeometryContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Avalonia.Media
/// of <see cref="StreamGeometryContext"/> is obtained by calling
/// <see cref="StreamGeometry.Open"/>.
/// </remarks>
public class StreamGeometryContext : IGeometryContext
public class StreamGeometryContext : IGeometryContext, IGeometryContext2
{
private readonly IStreamGeometryContextImpl _impl;

Expand Down Expand Up @@ -102,5 +102,46 @@ public void Dispose()
{
_impl.Dispose();
}

/// <inheritdoc/>
public void LineTo(Point point, bool isStroked)
{
if (_impl is IGeometryContext2 context2)
context2.LineTo(point, isStroked);
else
_impl.LineTo(point);

_currentPoint = point;
}

public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection, bool isStroked)
{
if (_impl is IGeometryContext2 context2)
context2.ArcTo(point, size, rotationAngle, isLargeArc, sweepDirection, isStroked);
else
_impl.ArcTo(point, size, rotationAngle, isLargeArc, sweepDirection);

_currentPoint = point;
}

public void CubicBezierTo(Point controlPoint1, Point controlPoint2, Point endPoint, bool isStroked)
{
if (_impl is IGeometryContext2 context2)
context2.CubicBezierTo(controlPoint1, controlPoint2, endPoint, isStroked);
else
_impl.CubicBezierTo(controlPoint1, controlPoint2, endPoint);

_currentPoint = endPoint;
}

public void QuadraticBezierTo(Point controlPoint, Point endPoint, bool isStroked)
{
if (_impl is IGeometryContext2 context2)
context2.QuadraticBezierTo(controlPoint, endPoint, isStroked);
else
_impl.QuadraticBezierTo(controlPoint, endPoint);

_currentPoint = endPoint;
}
}
}
46 changes: 46 additions & 0 deletions src/Avalonia.Base/Platform/IGeometryContext2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Avalonia.Media;

namespace Avalonia.Platform
{
// TODO12 combine with IGeometryContext
public interface IGeometryContext2 : IGeometryContext
{
/// <summary>
/// Draws a line to the specified point.
/// </summary>
/// <param name="point">The destination point.</param>
/// <param name="isStroked">Whether the segment is stroked</param>
void LineTo(Point point, bool isStroked);

/// <summary>
/// Draws an arc to the specified point.
/// </summary>
/// <param name="point">The destination point.</param>
/// <param name="size">The radii of an oval whose perimeter is used to draw the angle.</param>
/// <param name="rotationAngle">The rotation angle (in radians) of the oval that specifies the curve.</param>
/// <param name="isLargeArc">true to draw the arc greater than 180 degrees; otherwise, false.</param>
/// <param name="sweepDirection">
/// A value that indicates whether the arc is drawn in the Clockwise or Counterclockwise direction.
/// </param>
/// <param name="isStroked">Whether the segment is stroked</param>
void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection, bool isStroked);

/// <summary>
/// Draws a Bezier curve to the specified point.
/// </summary>
/// <param name="controlPoint1">The first control point used to specify the shape of the curve.</param>
/// <param name="controlPoint2">The second control point used to specify the shape of the curve.</param>
/// <param name="endPoint">The destination point for the end of the curve.</param>
/// <param name="isStroked">Whether the segment is stroked</param>
void CubicBezierTo(Point controlPoint1, Point controlPoint2, Point endPoint, bool isStroked);

/// <summary>
/// Draws a quadratic Bezier curve to the specified point
/// </summary>
/// <param name="controlPoint ">Control point</param>
/// <param name="endPoint">DestinationPoint</param>
/// <param name="isStroked">Whether the segment is stroked</param>
void QuadraticBezierTo(Point controlPoint, Point endPoint, bool isStroked);
}

}
128 changes: 119 additions & 9 deletions src/Skia/Avalonia.Skia/StreamGeometryImpl.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using Avalonia.Media;
using Avalonia.Platform;
using SkiaSharp;
Expand Down Expand Up @@ -77,12 +78,14 @@ private static SKPath CreateEmptyPath()
/// <summary>
/// A Skia implementation of a <see cref="IStreamGeometryContextImpl"/>.
/// </summary>
private class StreamContext : IStreamGeometryContextImpl
private class StreamContext : IStreamGeometryContextImpl, IGeometryContext2
{
private readonly StreamGeometryImpl _geometryImpl;
private SKPath Stroke => _geometryImpl._strokePath;
private SKPath Fill => _geometryImpl._fillPath ??= new();
private bool _isFilled;
private Point _startPoint;
private bool _isFigureBroken;
private bool Duplicate => _isFilled && !ReferenceEquals(_geometryImpl._fillPath, Stroke);

/// <summary>
Expand All @@ -93,7 +96,7 @@ public StreamContext(StreamGeometryImpl geometryImpl)
{
_geometryImpl = geometryImpl;
}

/// <inheritdoc />
/// <remarks>Will update bounds of passed geometry.</remarks>
public void Dispose()
Expand All @@ -117,7 +120,7 @@ public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc,
sweep,
(float)point.X,
(float)point.Y);
if(Duplicate)
if (Duplicate)
Fill.ArcTo(
(float)size.Width,
(float)size.Height,
Expand All @@ -136,34 +139,36 @@ public void BeginFigure(Point startPoint, bool isFilled)
if (Stroke == Fill)
_geometryImpl._fillPath = Stroke.Clone();
}

_isFilled = isFilled;
_startPoint = startPoint;
_isFigureBroken = false;
Stroke.MoveTo((float)startPoint.X, (float)startPoint.Y);
if(Duplicate)
if (Duplicate)
Fill.MoveTo((float)startPoint.X, (float)startPoint.Y);
}

/// <inheritdoc />
public void CubicBezierTo(Point point1, Point point2, Point point3)
{
Stroke.CubicTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y, (float)point3.X, (float)point3.Y);
if(Duplicate)
if (Duplicate)
Fill.CubicTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y, (float)point3.X, (float)point3.Y);
}

/// <inheritdoc />
public void QuadraticBezierTo(Point point1, Point point2)
{
Stroke.QuadTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y);
if(Duplicate)
if (Duplicate)
Fill.QuadTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y);
}

/// <inheritdoc />
public void LineTo(Point point)
{
Stroke.LineTo((float)point.X, (float)point.Y);
if(Duplicate)
if (Duplicate)
Fill.LineTo((float)point.X, (float)point.Y);
}

Expand All @@ -172,7 +177,13 @@ public void EndFigure(bool isClosed)
{
if (isClosed)
{
Stroke.Close();
if (_isFigureBroken)
{
LineTo(_startPoint);
_isFigureBroken = false;
}
else
Stroke.Close();
if (Duplicate)
Fill.Close();
}
Expand All @@ -183,6 +194,105 @@ public void SetFillRule(FillRule fillRule)
{
Fill.FillType = fillRule == FillRule.EvenOdd ? SKPathFillType.EvenOdd : SKPathFillType.Winding;
}

/// <inheritdoc />
public void LineTo(Point point, bool isStroked)
{
if (isStroked)
{
Stroke.LineTo((float)point.X, (float)point.Y);
}
else
{
if (Stroke == Fill)
_geometryImpl._fillPath = Stroke.Clone();

_isFigureBroken = true;

Stroke.MoveTo((float)point.X, (float)point.Y);
}
if (Duplicate)
Fill.LineTo((float)point.X, (float)point.Y);
}

/// <inheritdoc />
public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection, bool isStroked)
{
var arc = isLargeArc ? SKPathArcSize.Large : SKPathArcSize.Small;
var sweep = sweepDirection == SweepDirection.Clockwise
? SKPathDirection.Clockwise
: SKPathDirection.CounterClockwise;

if (isStroked)
{
Stroke.ArcTo(
(float)size.Width,
(float)size.Height,
(float)rotationAngle,
arc,
sweep,
(float)point.X,
(float)point.Y);
}
else
{
if (Stroke == Fill)
_geometryImpl._fillPath = Stroke.Clone();

_isFigureBroken = true;

Stroke.MoveTo((float)point.X, (float)point.Y);
}
if (Duplicate)
Fill.ArcTo(
(float)size.Width,
(float)size.Height,
(float)rotationAngle,
arc,
sweep,
(float)point.X,
(float)point.Y);
}

/// <inheritdoc />
public void CubicBezierTo(Point point1, Point point2, Point point3, bool isStroked)
{
if (isStroked)
{
Stroke.CubicTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y, (float)point3.X, (float)point3.Y);
}
else
{
if (Stroke == Fill)
_geometryImpl._fillPath = Stroke.Clone();

_isFigureBroken = true;

Stroke.MoveTo((float)point3.X, (float)point3.Y);
}
if (Duplicate)
Fill.CubicTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y, (float)point3.X, (float)point3.Y);
}

/// <inheritdoc />
public void QuadraticBezierTo(Point point1, Point point2, bool isStroked)
{
if (isStroked)
{
Stroke.QuadTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y);
}
else
{
if (Stroke == Fill)
_geometryImpl._fillPath = Stroke.Clone();

_isFigureBroken = true;

Stroke.MoveTo((float)point2.X, (float)point2.Y);
}
if (Duplicate)
Fill.QuadTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y);
}
}
}
}

0 comments on commit 9395904

Please sign in to comment.