Browse Source

feat(Geometries): PolyBezierSegment (#16664)

* feat(Geometries): PolyBezierSegment

* fix: warning

* add PolyBezierSegment to CrossUI Test

* test: AddPolyBezierSegment CrossUI test
pull/16693/head
workgroupengineering 2 years ago
committed by GitHub
parent
commit
6e35fd2bbe
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 90
      src/Avalonia.Base/Media/PolyBezierSegment.cs
  2. 22
      src/Avalonia.Base/Points.cs
  3. 1
      tests/Avalonia.RenderTests.WpfCompare/CrossUI.Wpf.cs
  4. 30
      tests/Avalonia.RenderTests/CrossTests/CrossGeometryTests.cs
  5. 115
      tests/Avalonia.RenderTests/CrossUI/CrossUI.Avalonia.cs
  6. 1
      tests/Avalonia.RenderTests/CrossUI/CrossUI.cs
  7. BIN
      tests/TestFiles/CrossTests/Media/Geometry/Should_Render_PolyBezierSegment_With_Strokeless_Lines.wpf.png

90
src/Avalonia.Base/Media/PolyBezierSegment.cs

@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using Avalonia.Utilities;
namespace Avalonia.Media;
/// <summary>
/// PolyBezierSegment
/// </summary>
public sealed class PolyBezierSegment : PathSegment
{
/// <summary>
/// Points DirectProperty definition
/// </summary>
public static readonly DirectProperty<PolyBezierSegment, Points?> PointsProperty =
AvaloniaProperty.RegisterDirect<PolyBezierSegment, Points?>(nameof(Points),
o => o.Points,
(o, v) => o.Points = v);
private Points? _points = [];
public PolyBezierSegment()
{
}
public PolyBezierSegment(IEnumerable<Point> points, bool isStroked)
{
if (points is null)
{
throw new ArgumentNullException(nameof(points));
}
Points = new Points(points);
IsStroked = isStroked;
}
/// <summary>
/// Gets or sets the Point collection that defines this <see cref="PolyBezierSegment"/> object.
/// </summary>
/// <value>
/// The points.
/// </value>
[Metadata.Content]
public Points? Points
{
get => _points;
set => SetAndRaise(PointsProperty, ref _points, value);
}
internal override void ApplyTo(StreamGeometryContext ctx)
{
var isStroken = this.IsStroked;
if (_points is { Count: > 0 } points)
{
var i = 0;
for (; i < points.Count; i += 3)
{
ctx.CubicBezierTo(points[i],
points[i + 1],
points[i + 2],
isStroken);
}
var delta = i - points.Count;
if (delta != 0)
{
Logging.Logger.TryGet(Logging.LogEventLevel.Warning,
Logging.LogArea.Visual)
?.Log(nameof(PolyBezierSegment),
$"{nameof(PolyBezierSegment)} has ivalid number of points. Last {Math.Abs(delta)} points will be ignored.");
}
}
}
public override string ToString()
{
var builder = StringBuilderCache.Acquire();
if (_points is { Count: > 0 } points)
{
builder.Append('C').Append(' ');
foreach (var point in _points)
{
builder.Append(FormattableString.Invariant($"{point}"));
builder.Append(' ');
}
builder.Length = builder.Length - 1;
}
return StringBuilderCache.GetStringAndRelease(builder);
}
}

22
src/Avalonia.Base/Points.cs

@ -1,18 +1,20 @@
using System.Collections.Generic;
using Avalonia.Collections;
namespace Avalonia
namespace Avalonia;
/// <summary>
/// Represents a collection of <see cref="Point"/> values that can be individually accessed by index.
/// </summary>
public sealed class Points : AvaloniaList<Point>
{
public sealed class Points : AvaloniaList<Point>
public Points()
{
public Points()
{
}
}
public Points(IEnumerable<Point> points) : base(points)
{
}
public Points(IEnumerable<Point> points) : base(points)
{
}
}

1
tests/Avalonia.RenderTests.WpfCompare/CrossUI.Wpf.cs

@ -191,6 +191,7 @@ namespace Avalonia.RenderTests.WpfCompare
CrossPathSegment.CubicBezier cubicBezier => new BezierSegment(cubicBezier.Point1.ToWpf(), cubicBezier.Point2.ToWpf(), cubicBezier.Point3.ToWpf(), cubicBezier.IsStroked),
CrossPathSegment.QuadraticBezier quadraticBezier => new QuadraticBezierSegment(quadraticBezier.Point1.ToWpf(), quadraticBezier.Point2.ToWpf(), quadraticBezier.IsStroked),
CrossPathSegment.PolyLine polyLine => new PolyLineSegment(polyLine.Points.Select(p => p.ToWpf()).ToList(), polyLine.IsStroked),
CrossPathSegment.PolyBezierSegment pb => new PolyBezierSegment(pb.Points.Select(p => p.ToWpf()), pb.IsStroked),
_ => throw new NotImplementedException(),
}), f.Closed)))
};

30
tests/Avalonia.RenderTests/CrossTests/CrossGeometryTests.cs

@ -141,6 +141,36 @@ public class CrossGeometryTests : CrossTestBase
$"{nameof(Should_Render_PolyLineSegment_With_Strokeless_Lines)}");
}
[CrossFact]
public void Should_Render_PolyBezierSegment_With_Strokeless_Lines()
{
var brush = new CrossSolidColorBrush(Colors.Blue);
var pen = new CrossPen()
{
Brush = new CrossSolidColorBrush(Colors.Red),
Thickness = 8
};
var figure = new CrossPathFigure()
{
Start = new Point(10, 100),
Closed = false,
Segments =
{
new CrossPathSegment.PolyBezierSegment([new(0, 0), new(200, 0), new(300, 100), new(300, 0), new(500, 0), new(600,100)], false)
}
};
var geometry = new CrossPathGeometry { Figures = { figure } };
var control = new CrossFuncControl(ctx => ctx.DrawGeometry(brush, pen, geometry))
{
Width = 700,
Height = 400,
};
RenderAndCompare(control,
$"{nameof(Should_Render_PolyBezierSegment_With_Strokeless_Lines)}");
}
// Skip the test for now
#if !AVALONIA_SKIA
[CrossTheory,

115
tests/Avalonia.RenderTests/CrossUI/CrossUI.Avalonia.cs

@ -156,61 +156,68 @@ namespace Avalonia.Direct2D1.RenderTests.CrossUI
static Geometry ConvertGeometry(CrossGeometry g)
{
if (g is CrossRectangleGeometry rg)
return new RectangleGeometry(rg.Rect);
else if (g is CrossSvgGeometry svg)
return PathGeometry.Parse(svg.Path);
else if (g is CrossEllipseGeometry ellipse)
return new EllipseGeometry(ellipse.Rect);
else if(g is CrossStreamGeometry streamGeometry)
return (StreamGeometry)streamGeometry.GetContext().GetGeometry();
else if (g is CrossPathGeometry path)
return new PathGeometry()
{
Figures = RetAddRange(new PathFigures(), path.Figures.Select(f =>
new PathFigure()
{
StartPoint = f.Start,
IsClosed = f.Closed,
Segments = RetAddRange<PathSegments, PathSegment>(new PathSegments(), f.Segments.Select<CrossPathSegment, PathSegment>(s =>
s switch
{
CrossPathSegment.Line l => new LineSegment()
{
Point = l.To, IsStroked = l.IsStroked
},
CrossPathSegment.Arc a => new ArcSegment()
{
Point = a.Point,
RotationAngle = a.RotationAngle,
Size = a.Size,
IsLargeArc = a.IsLargeArc,
SweepDirection = a.SweepDirection,
IsStroked = a.IsStroked
},
CrossPathSegment.CubicBezier c => new BezierSegment()
{
Point1 = c.Point1,
Point2 = c.Point2,
Point3 = c.Point3,
IsStroked = c.IsStroked
},
CrossPathSegment.QuadraticBezier q => new QuadraticBezierSegment()
{
Point1 = q.Point1,
Point2 = q.Point2,
IsStroked = q.IsStroked
},
CrossPathSegment.PolyLine p => new PolyLineSegment()
switch (g)
{
case CrossRectangleGeometry rg:
return new RectangleGeometry(rg.Rect);
case CrossSvgGeometry svg:
return PathGeometry.Parse(svg.Path);
case CrossEllipseGeometry ellipse:
return new EllipseGeometry(ellipse.Rect);
case CrossStreamGeometry streamGeometry:
return (StreamGeometry)streamGeometry.GetContext().GetGeometry();
case CrossPathGeometry path:
return new PathGeometry()
{
Figures = RetAddRange(new PathFigures(), path.Figures.Select(f =>
new PathFigure()
{
StartPoint = f.Start,
IsClosed = f.Closed,
Segments = RetAddRange<PathSegments, PathSegment>(new PathSegments(),
f.Segments.Select<CrossPathSegment, PathSegment>(s =>
s switch
{
Points = p.Points.ToList(),
IsStroked = p.IsStroked
},
_ => throw new InvalidOperationException()
}))
}))
};
throw new NotSupportedException();
CrossPathSegment.Line l => new LineSegment()
{
Point = l.To,
IsStroked = l.IsStroked
},
CrossPathSegment.Arc a => new ArcSegment()
{
Point = a.Point,
RotationAngle = a.RotationAngle,
Size = a.Size,
IsLargeArc = a.IsLargeArc,
SweepDirection = a.SweepDirection,
IsStroked = a.IsStroked
},
CrossPathSegment.CubicBezier c => new BezierSegment()
{
Point1 = c.Point1,
Point2 = c.Point2,
Point3 = c.Point3,
IsStroked = c.IsStroked
},
CrossPathSegment.QuadraticBezier q => new QuadraticBezierSegment()
{
Point1 = q.Point1,
Point2 = q.Point2,
IsStroked = q.IsStroked
},
CrossPathSegment.PolyLine p => new PolyLineSegment()
{
Points = p.Points.ToList(),
IsStroked = p.IsStroked
},
CrossPathSegment.PolyBezierSegment p => new PolyBezierSegment(p.Points,p.IsStroked),
_ => throw new InvalidOperationException()
}))
}))
};
default:
throw new NotSupportedException();
}
}
static TList RetAddRange<TList, T>(TList l, IEnumerable<T> en) where TList : IList<T>

1
tests/Avalonia.RenderTests/CrossUI/CrossUI.cs

@ -159,6 +159,7 @@ public abstract record class CrossPathSegment(bool IsStroked)
public record CubicBezier(Point Point1, Point Point2, Point Point3, bool IsStroked) : CrossPathSegment(IsStroked);
public record QuadraticBezier(Point Point1, Point Point2, bool IsStroked) : CrossPathSegment(IsStroked);
public record PolyLine(IEnumerable<Point> Points, bool IsStroked) : CrossPathSegment(IsStroked);
public record PolyBezierSegment(IEnumerable<Point> Points, bool IsStroked) : CrossPathSegment(IsStroked);
}
public class CrossDrawingBrush : CrossTileBrush

BIN
tests/TestFiles/CrossTests/Media/Geometry/Should_Render_PolyBezierSegment_With_Strokeless_Lines.wpf.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Loading…
Cancel
Save