From 9395904b93a55fedc26d1b72f3d379ba2985a9fd Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Tue, 30 Apr 2024 00:41:45 +0000 Subject: [PATCH] Add isStroked overload for IGeometryContext (#15430) * 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 --- src/Avalonia.Base/Media/ArcSegment.cs | 2 +- src/Avalonia.Base/Media/BezierSegment .cs | 2 +- src/Avalonia.Base/Media/LineSegment.cs | 2 +- src/Avalonia.Base/Media/PathSegment.cs | 9 + .../Media/QuadraticBezierSegment .cs | 2 +- .../Media/StreamGeometryContext.cs | 43 +- .../Platform/IGeometryContext2.cs | 46 +++ src/Skia/Avalonia.Skia/StreamGeometryImpl.cs | 128 +++++- .../CrossUI.Wpf.cs | 346 ++++++++++------ .../CrossTests/CrossGeometryTests.cs | 159 ++++++++ .../CrossUI/CrossUI.Avalonia.cs | 378 ++++++++++++------ tests/Avalonia.RenderTests/CrossUI/CrossUI.cs | 60 +++ .../CrossTestBase.cs | 2 +- ...ld_Properly_CloseFigure_Flat_Bevel.wpf.png | Bin 0 -> 6815 bytes ...ld_Properly_CloseFigure_Flat_Miter.wpf.png | Bin 0 -> 7625 bytes ...ld_Properly_CloseFigure_Flat_Round.wpf.png | Bin 0 -> 6910 bytes ...d_Properly_CloseFigure_Round_Bevel.wpf.png | Bin 0 -> 6852 bytes ...d_Properly_CloseFigure_Round_Miter.wpf.png | Bin 0 -> 7665 bytes ...d_Properly_CloseFigure_Round_Round.wpf.png | Bin 0 -> 6947 bytes ...der_Geometry_With_Strokeless_Lines.wpf.png | Bin 0 -> 3537 bytes .../Should_Render_Stream_Geometry.wpf.png | Bin 0 -> 3500 bytes 21 files changed, 923 insertions(+), 256 deletions(-) create mode 100644 src/Avalonia.Base/Platform/IGeometryContext2.cs create mode 100644 tests/Avalonia.RenderTests/CrossTests/CrossGeometryTests.cs create mode 100644 tests/TestFiles/CrossTests/Media/Geometry/Should_Properly_CloseFigure_Flat_Bevel.wpf.png create mode 100644 tests/TestFiles/CrossTests/Media/Geometry/Should_Properly_CloseFigure_Flat_Miter.wpf.png create mode 100644 tests/TestFiles/CrossTests/Media/Geometry/Should_Properly_CloseFigure_Flat_Round.wpf.png create mode 100644 tests/TestFiles/CrossTests/Media/Geometry/Should_Properly_CloseFigure_Round_Bevel.wpf.png create mode 100644 tests/TestFiles/CrossTests/Media/Geometry/Should_Properly_CloseFigure_Round_Miter.wpf.png create mode 100644 tests/TestFiles/CrossTests/Media/Geometry/Should_Properly_CloseFigure_Round_Round.wpf.png create mode 100644 tests/TestFiles/CrossTests/Media/Geometry/Should_Render_Geometry_With_Strokeless_Lines.wpf.png create mode 100644 tests/TestFiles/CrossTests/Media/Geometry/Should_Render_Stream_Geometry.wpf.png diff --git a/src/Avalonia.Base/Media/ArcSegment.cs b/src/Avalonia.Base/Media/ArcSegment.cs index ee353b0a893..f5807ab9d7b 100644 --- a/src/Avalonia.Base/Media/ArcSegment.cs +++ b/src/Avalonia.Base/Media/ArcSegment.cs @@ -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() diff --git a/src/Avalonia.Base/Media/BezierSegment .cs b/src/Avalonia.Base/Media/BezierSegment .cs index 31efe1ec232..1c09081e3b2 100644 --- a/src/Avalonia.Base/Media/BezierSegment .cs +++ b/src/Avalonia.Base/Media/BezierSegment .cs @@ -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() diff --git a/src/Avalonia.Base/Media/LineSegment.cs b/src/Avalonia.Base/Media/LineSegment.cs index 68193bb770f..8b0014fcc8e 100644 --- a/src/Avalonia.Base/Media/LineSegment.cs +++ b/src/Avalonia.Base/Media/LineSegment.cs @@ -24,7 +24,7 @@ public Point Point internal override void ApplyTo(StreamGeometryContext ctx) { - ctx.LineTo(Point); + ctx.LineTo(Point, IsStroked); } public override string ToString() diff --git a/src/Avalonia.Base/Media/PathSegment.cs b/src/Avalonia.Base/Media/PathSegment.cs index 0b517e56f3a..6b5f5f601df 100644 --- a/src/Avalonia.Base/Media/PathSegment.cs +++ b/src/Avalonia.Base/Media/PathSegment.cs @@ -3,5 +3,14 @@ namespace Avalonia.Media public abstract class PathSegment : AvaloniaObject { internal abstract void ApplyTo(StreamGeometryContext ctx); + + public static readonly StyledProperty IsStrokedProperty = + AvaloniaProperty.Register(nameof(IsStroked), true); + + public bool IsStroked + { + get => GetValue(IsStrokedProperty); + set => SetValue(IsStrokedProperty, value); + } } } diff --git a/src/Avalonia.Base/Media/QuadraticBezierSegment .cs b/src/Avalonia.Base/Media/QuadraticBezierSegment .cs index 01d22f20437..a338080065b 100644 --- a/src/Avalonia.Base/Media/QuadraticBezierSegment .cs +++ b/src/Avalonia.Base/Media/QuadraticBezierSegment .cs @@ -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() diff --git a/src/Avalonia.Base/Media/StreamGeometryContext.cs b/src/Avalonia.Base/Media/StreamGeometryContext.cs index 66fb65a6c29..c8072564b19 100644 --- a/src/Avalonia.Base/Media/StreamGeometryContext.cs +++ b/src/Avalonia.Base/Media/StreamGeometryContext.cs @@ -10,7 +10,7 @@ namespace Avalonia.Media /// of is obtained by calling /// . /// - public class StreamGeometryContext : IGeometryContext + public class StreamGeometryContext : IGeometryContext, IGeometryContext2 { private readonly IStreamGeometryContextImpl _impl; @@ -102,5 +102,46 @@ public void Dispose() { _impl.Dispose(); } + + /// + 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; + } } } diff --git a/src/Avalonia.Base/Platform/IGeometryContext2.cs b/src/Avalonia.Base/Platform/IGeometryContext2.cs new file mode 100644 index 00000000000..4142430e9d7 --- /dev/null +++ b/src/Avalonia.Base/Platform/IGeometryContext2.cs @@ -0,0 +1,46 @@ +using Avalonia.Media; + +namespace Avalonia.Platform +{ + // TODO12 combine with IGeometryContext + public interface IGeometryContext2 : IGeometryContext + { + /// + /// Draws a line to the specified point. + /// + /// The destination point. + /// Whether the segment is stroked + void LineTo(Point point, bool isStroked); + + /// + /// Draws an arc to the specified point. + /// + /// The destination point. + /// The radii of an oval whose perimeter is used to draw the angle. + /// The rotation angle (in radians) of the oval that specifies the curve. + /// true to draw the arc greater than 180 degrees; otherwise, false. + /// + /// A value that indicates whether the arc is drawn in the Clockwise or Counterclockwise direction. + /// + /// Whether the segment is stroked + void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection, bool isStroked); + + /// + /// Draws a Bezier curve to the specified point. + /// + /// The first control point used to specify the shape of the curve. + /// The second control point used to specify the shape of the curve. + /// The destination point for the end of the curve. + /// Whether the segment is stroked + void CubicBezierTo(Point controlPoint1, Point controlPoint2, Point endPoint, bool isStroked); + + /// + /// Draws a quadratic Bezier curve to the specified point + /// + /// Control point + /// DestinationPoint + /// Whether the segment is stroked + void QuadraticBezierTo(Point controlPoint, Point endPoint, bool isStroked); + } + +} diff --git a/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs b/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs index eb081c4f504..e66c77c9a32 100644 --- a/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs +++ b/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using System.Drawing; using Avalonia.Media; using Avalonia.Platform; using SkiaSharp; @@ -77,12 +78,14 @@ private static SKPath CreateEmptyPath() /// /// A Skia implementation of a . /// - 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); /// @@ -93,7 +96,7 @@ public StreamContext(StreamGeometryImpl geometryImpl) { _geometryImpl = geometryImpl; } - + /// /// Will update bounds of passed geometry. public void Dispose() @@ -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, @@ -136,10 +139,12 @@ 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); } @@ -147,7 +152,7 @@ public void BeginFigure(Point startPoint, bool isFilled) 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); } @@ -155,7 +160,7 @@ public void CubicBezierTo(Point point1, Point point2, Point point3) 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); } @@ -163,7 +168,7 @@ public void QuadraticBezierTo(Point point1, Point point2) 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); } @@ -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(); } @@ -183,6 +194,105 @@ public void SetFillRule(FillRule fillRule) { Fill.FillType = fillRule == FillRule.EvenOdd ? SKPathFillType.EvenOdd : SKPathFillType.Winding; } + + /// + 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); + } + + /// + 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); + } + + /// + 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); + } + + /// + 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); + } } } } diff --git a/tests/Avalonia.RenderTests.WpfCompare/CrossUI.Wpf.cs b/tests/Avalonia.RenderTests.WpfCompare/CrossUI.Wpf.cs index b4506632488..302f7f695c2 100644 --- a/tests/Avalonia.RenderTests.WpfCompare/CrossUI.Wpf.cs +++ b/tests/Avalonia.RenderTests.WpfCompare/CrossUI.Wpf.cs @@ -31,158 +31,272 @@ using WRect = System.Windows.Rect; using WColor = System.Windows.Media.Color; using WMatrix = System.Windows.Media.Matrix; -namespace Avalonia.RenderTests.WpfCompare; +using Avalonia.RenderTests.WpfCompare; +using PenLineCap = Avalonia.Media.PenLineCap; +using WPenLineCap = System.Windows.Media.PenLineCap; +using PenLineJoin = Avalonia.Media.PenLineJoin; +using WPenLineJoin = System.Windows.Media.PenLineJoin; -internal static class WpfConvertExtensions +namespace CrossUI { - public static WPoint ToWpf(this Point pt) => new(pt.X, pt.Y); - public static WSize ToWpf(this Size size) => new(size.Width, size.Height); - public static WRect ToWpf(this Rect rect) => new(rect.Left, rect.Top, rect.Width, rect.Height); - public static WColor ToWpf(this Color color) => WColor.FromArgb(color.A, color.R, color.G, color.B); - public static WMatrix ToWpf(this Matrix m) => new WMatrix(m.M11, m.M12, m.M21, m.M22, m.M31, m.M32); -} - -internal class WpfCrossControl : Panel -{ - private readonly CrossControl _src; - private readonly Dictionary _children; - - public WpfCrossControl(CrossControl src) + public partial class CrossGlobals { - _src = src; - _children = src.Children.ToDictionary(x => x, x => new WpfCrossControl(x)); - Width = src.Bounds.Width; - Height = src.Bounds.Height; - RenderTransform = new MatrixTransform(src.RenderTransform.ToWpf()); - foreach (var ch in src.Children) + public static ICrossStreamGeometryContextImplProvider GetContextImplProvider() { - var c = _children[ch]; - this.Children.Add(c); + return new WpfCrossStreamGeometryContextImplProvider(); } } - - protected override WSize MeasureOverride(WSize availableSize) - { - foreach (var ch in _children) - ch.Value.Measure(ch.Key.Bounds.Size.ToWpf()); - return _src.Bounds.Size.ToWpf(); - } - protected override WSize ArrangeOverride(WSize finalSize) + public class WpfCrossStreamGeometryContextImplProvider : ICrossStreamGeometryContextImplProvider { - foreach (var ch in _children) - ch.Value.Arrange(ch.Key.Bounds.ToWpf()); - return base.ArrangeOverride(finalSize); + public ICrossStreamGeometryContextImpl Create() + { + return new WpfCrossStreamGeometryContextImpl(); + } } - protected override void OnRender(DrawingContext context) + public class WpfCrossStreamGeometryContextImpl : ICrossStreamGeometryContextImpl { - _src.Render(new WpfCrossDrawingContext(context)); + private StreamGeometry _streamGeometry; + private StreamGeometryContext _context; + + public WpfCrossStreamGeometryContextImpl() + { + _streamGeometry = new StreamGeometry(); + _context = _streamGeometry.Open(); + } + + public void ArcTo(Avalonia.Point point, Avalonia.Size size, double rotationAngle, bool isLargeArc, Avalonia.Media.SweepDirection sweepDirection, bool isStroked) + { + _context.ArcTo(point.ToWpf(), size.ToWpf(), rotationAngle, isLargeArc, (SweepDirection)sweepDirection, isStroked, true); + } + + public void BeginFigure(Avalonia.Point point, bool isFilled, bool isClosed) + { + _context.BeginFigure(point.ToWpf(), isFilled, isClosed); + } + + public void CubicBezierTo(Avalonia.Point controlPoint1, Avalonia.Point controlPoint2, Avalonia.Point endPoint, bool isStroked) + { + _context.BezierTo(controlPoint1.ToWpf(), controlPoint2.ToWpf(), endPoint.ToWpf(), isStroked, true); + } + + public void Dispose() + { + _context.Close(); + } + + public void EndFigure() + { + Dispose(); + } + + public object GetGeometry() + { + return _streamGeometry; + } + + public void LineTo(Avalonia.Point point, bool isStroked) + { + _context.LineTo(point.ToWpf(), isStroked, true); + } + + public void QuadraticBezierTo(Avalonia.Point controlPoint, Avalonia.Point endPoint, bool isStroked) + { + _context.QuadraticBezierTo(controlPoint.ToWpf(), endPoint.ToWpf(), isStroked, true); + } } } -internal class WpfCrossDrawingContext : ICrossDrawingContext +namespace Avalonia.RenderTests.WpfCompare { - private readonly DrawingContext _ctx; - - public WpfCrossDrawingContext(DrawingContext ctx) + internal static class WpfConvertExtensions { - _ctx = ctx; + public static WPoint ToWpf(this Point pt) => new(pt.X, pt.Y); + public static WSize ToWpf(this Size size) => new(size.Width, size.Height); + public static WRect ToWpf(this Rect rect) => new(rect.Left, rect.Top, rect.Width, rect.Height); + public static WColor ToWpf(this Color color) => WColor.FromArgb(color.A, color.R, color.G, color.B); + public static WMatrix ToWpf(this Matrix m) => new WMatrix(m.M11, m.M12, m.M21, m.M22, m.M31, m.M32); } - private static Transform? ConvertTransform(Matrix? m) => m == null ? null : new MatrixTransform(m.Value.ToWpf()); - - private static Geometry ConvertGeometry(CrossGeometry g) + internal class WpfCrossControl : Panel { - if (g is CrossRectangleGeometry rg) - return new RectangleGeometry(rg.Rect.ToWpf()); - else if (g is CrossSvgGeometry svg) - return Geometry.Parse(svg.Path); - else if (g is CrossEllipseGeometry ellipse) - return new EllipseGeometry(ellipse.Rect.ToWpf()); - throw new NotSupportedException(); - } + private readonly CrossControl _src; + private readonly Dictionary _children; - private static Drawing ConvertDrawing(CrossDrawing src) - { - if (src is CrossDrawingGroup g) - return new DrawingGroup() { Children = new DrawingCollection(g.Children.Select(ConvertDrawing)) }; - if (src is CrossGeometryDrawing geo) - return new GeometryDrawing() + public WpfCrossControl(CrossControl src) + { + _src = src; + _children = src.Children.ToDictionary(x => x, x => new WpfCrossControl(x)); + Width = src.Bounds.Width; + Height = src.Bounds.Height; + RenderTransform = new MatrixTransform(src.RenderTransform.ToWpf()); + foreach (var ch in src.Children) { - Geometry = ConvertGeometry(geo.Geometry), Brush = ConvertBrush(geo.Brush), Pen = ConvertPen(geo.Pen) - }; - throw new NotSupportedException(); + var c = _children[ch]; + this.Children.Add(c); + } + } + + protected override WSize MeasureOverride(WSize availableSize) + { + foreach (var ch in _children) + ch.Value.Measure(ch.Key.Bounds.Size.ToWpf()); + return _src.Bounds.Size.ToWpf(); + } + + protected override WSize ArrangeOverride(WSize finalSize) + { + foreach (var ch in _children) + ch.Value.Arrange(ch.Key.Bounds.ToWpf()); + return base.ArrangeOverride(finalSize); + } + + protected override void OnRender(DrawingContext context) + { + _src.Render(new WpfCrossDrawingContext(context)); + } } - private static Brush? ConvertBrush(CrossBrush? brush) + internal class WpfCrossDrawingContext : ICrossDrawingContext { - if (brush == null) - return null; - static Brush Sync(Brush dst, CrossBrush src) + private readonly DrawingContext _ctx; + + public WpfCrossDrawingContext(DrawingContext ctx) { - dst.Opacity = src.Opacity; - dst.Transform = ConvertTransform(src.Transform); - dst.RelativeTransform = ConvertTransform(src.RelativeTransform); - return dst; + _ctx = ctx; } - static Brush SyncTile(TileBrush dst, CrossTileBrush src) + private static Transform? ConvertTransform(Matrix? m) => m == null ? null : new MatrixTransform(m.Value.ToWpf()); + + private static Geometry ConvertGeometry(CrossGeometry g) { - dst.Stretch = (Stretch)src.Stretch; - dst.AlignmentX = (AlignmentX)src.AlignmentX; - dst.AlignmentY = (AlignmentY)src.AlignmentY; - dst.TileMode = (TileMode)src.TileMode; - dst.Viewbox = src.Viewbox.ToWpf(); - dst.ViewboxUnits = (BrushMappingMode)src.ViewboxUnits; - dst.Viewport = src.Viewport.ToWpf(); - dst.ViewportUnits = (BrushMappingMode)src.ViewportUnits; - return Sync(dst, src); + if (g is CrossRectangleGeometry rg) + return new RectangleGeometry(rg.Rect.ToWpf()); + else if (g is CrossSvgGeometry svg) + return Geometry.Parse(svg.Path); + else if (g is CrossEllipseGeometry ellipse) + return new EllipseGeometry(ellipse.Rect.ToWpf()); + else if (g is CrossStreamGeometry streamGeometry) + return (StreamGeometry)streamGeometry.GetContext().GetGeometry(); + else if (g is CrossPathGeometry pathGeometry) + return new PathGeometry() + { + Figures = new PathFigureCollection(pathGeometry.Figures.Select(f => new PathFigure( + f.Start.ToWpf(), f.Segments.Select(s => + s switch + { + CrossPathSegment.Line line => new LineSegment(line.To.ToWpf(), s.IsStroked), + CrossPathSegment.Arc arc => new ArcSegment(arc.Point.ToWpf(), arc.Size.ToWpf(), arc.RotationAngle, arc.IsLargeArc, (SweepDirection)arc.SweepDirection, s.IsStroked), + 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), + _ => throw new NotImplementedException(), + }), f.Closed))) + }; + throw new NotSupportedException(); } - static Brush SyncGradient(GradientBrush dst, CrossGradientBrush src) + private static Drawing ConvertDrawing(CrossDrawing src) { - dst.MappingMode = (BrushMappingMode)src.MappingMode; - dst.SpreadMethod = (GradientSpreadMethod)src.SpreadMethod; - dst.GradientStops = - new GradientStopCollection(src.GradientStops.Select(s => new GradientStop(s.Color.ToWpf(), s.Offset))); - return Sync(dst, src); + if (src is CrossDrawingGroup g) + return new DrawingGroup() { Children = new DrawingCollection(g.Children.Select(ConvertDrawing)) }; + if (src is CrossGeometryDrawing geo) + return new GeometryDrawing() + { + Geometry = ConvertGeometry(geo.Geometry), Brush = ConvertBrush(geo.Brush), Pen = ConvertPen(geo.Pen) + }; + throw new NotSupportedException(); } - if (brush is CrossSolidColorBrush br) - return Sync(new SolidColorBrush(br.Color.ToWpf()), brush); - if (brush is CrossDrawingBrush db) - return SyncTile(new DrawingBrush(ConvertDrawing(db.Drawing)), db); - if (brush is CrossRadialGradientBrush radial) - return SyncGradient(new RadialGradientBrush() + private static Brush? ConvertBrush(CrossBrush? brush) + { + if (brush == null) + return null; + static Brush Sync(Brush dst, CrossBrush src) { - RadiusX = radial.RadiusX, - RadiusY = radial.RadiusY, - Center = radial.Center.ToWpf(), - GradientOrigin = radial.GradientOrigin.ToWpf() - }, radial); - throw new NotSupportedException(); - } + dst.Opacity = src.Opacity; + dst.Transform = ConvertTransform(src.Transform); + dst.RelativeTransform = ConvertTransform(src.RelativeTransform); + return dst; + } - private static Pen? ConvertPen(CrossPen? pen) - { - if (pen == null) - return null; - return new Pen(ConvertBrush(pen.Brush), pen.Thickness); - } + static Brush SyncTile(TileBrush dst, CrossTileBrush src) + { + dst.Stretch = (Stretch)src.Stretch; + dst.AlignmentX = (AlignmentX)src.AlignmentX; + dst.AlignmentY = (AlignmentY)src.AlignmentY; + dst.TileMode = (TileMode)src.TileMode; + dst.Viewbox = src.Viewbox.ToWpf(); + dst.ViewboxUnits = (BrushMappingMode)src.ViewboxUnits; + dst.Viewport = src.Viewport.ToWpf(); + dst.ViewportUnits = (BrushMappingMode)src.ViewportUnits; + return Sync(dst, src); + } - private static ImageSource ConvertImage(CrossImage image) - { - if (image is CrossBitmapImage bi) - return new BitmapImage(new Uri(bi.Path, UriKind.Absolute)); - if (image is CrossDrawingImage di) - return new DrawingImage(ConvertDrawing(di.Drawing)); - throw new NotSupportedException(); - } + static Brush SyncGradient(GradientBrush dst, CrossGradientBrush src) + { + dst.MappingMode = (BrushMappingMode)src.MappingMode; + dst.SpreadMethod = (GradientSpreadMethod)src.SpreadMethod; + dst.GradientStops = + new GradientStopCollection(src.GradientStops.Select(s => new GradientStop(s.Color.ToWpf(), s.Offset))); + return Sync(dst, src); + } + + if (brush is CrossSolidColorBrush br) + return Sync(new SolidColorBrush(br.Color.ToWpf()), brush); + if (brush is CrossDrawingBrush db) + return SyncTile(new DrawingBrush(ConvertDrawing(db.Drawing)), db); + if (brush is CrossRadialGradientBrush radial) + return SyncGradient(new RadialGradientBrush() + { + RadiusX = radial.RadiusX, + RadiusY = radial.RadiusY, + Center = radial.Center.ToWpf(), + GradientOrigin = radial.GradientOrigin.ToWpf() + }, radial); + throw new NotSupportedException(); + } + + private static Pen? ConvertPen(CrossPen? pen) + { + if (pen == null) + return null; + + var cap = pen.LineCap switch + { + PenLineCap.Flat => WPenLineCap.Flat, + PenLineCap.Round => WPenLineCap.Round, + PenLineCap.Square => WPenLineCap.Square + }; + var join = pen.LineJoin switch + { + PenLineJoin.Bevel => WPenLineJoin.Bevel, + PenLineJoin.Miter => WPenLineJoin.Miter, + PenLineJoin.Round => WPenLineJoin.Round + }; + + return new Pen(ConvertBrush(pen.Brush), pen.Thickness) + { + StartLineCap = cap, + EndLineCap = cap, + DashCap = cap, + LineJoin = join, + }; + } + + private static ImageSource ConvertImage(CrossImage image) + { + if (image is CrossBitmapImage bi) + return new BitmapImage(new Uri(bi.Path, UriKind.Absolute)); + if (image is CrossDrawingImage di) + return new DrawingImage(ConvertDrawing(di.Drawing)); + throw new NotSupportedException(); + } - public void DrawRectangle(CrossBrush? brush, CrossPen? pen, Rect rc) => _ctx.DrawRectangle(ConvertBrush(brush), ConvertPen(pen), rc.ToWpf()); - public void DrawGeometry(CrossBrush? brush, CrossPen? pen, CrossGeometry geo) => - _ctx.DrawGeometry(ConvertBrush(brush), ConvertPen(pen), ConvertGeometry(geo)); + public void DrawRectangle(CrossBrush? brush, CrossPen? pen, Rect rc) => _ctx.DrawRectangle(ConvertBrush(brush), ConvertPen(pen), rc.ToWpf()); + public void DrawGeometry(CrossBrush? brush, CrossPen? pen, CrossGeometry geo) => + _ctx.DrawGeometry(ConvertBrush(brush), ConvertPen(pen), ConvertGeometry(geo)); - public void DrawImage(CrossImage image, Rect rc) => _ctx.DrawImage(ConvertImage(image), rc.ToWpf()); + public void DrawImage(CrossImage image, Rect rc) => _ctx.DrawImage(ConvertImage(image), rc.ToWpf()); + } } diff --git a/tests/Avalonia.RenderTests/CrossTests/CrossGeometryTests.cs b/tests/Avalonia.RenderTests/CrossTests/CrossGeometryTests.cs new file mode 100644 index 00000000000..a5492328e6f --- /dev/null +++ b/tests/Avalonia.RenderTests/CrossTests/CrossGeometryTests.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Media; +using CrossUI; +using Xunit; + +#if AVALONIA_SKIA +namespace Avalonia.Skia.RenderTests; +#elif AVALONIA_D2D +namespace Avalonia.Direct2D1.RenderTests; +#else +namespace Avalonia.RenderTests.WpfCompare; +#endif + + +public class CrossGeometryTests : CrossTestBase +{ + public CrossGeometryTests() : base("Media/Geometry") + { + } + + [CrossFact] + public void Should_Render_Stream_Geometry() + { + var geometry = new CrossStreamGeometry(); + + var context = geometry.GetContext(); + context.BeginFigure(new Point(150, 15), true, true); + context.LineTo(new Point(258, 77), true); + context.LineTo(new Point(258, 202), true); + context.LineTo(new Point(150, 265), true); + context.LineTo(new Point(42, 202), true); + context.LineTo(new Point(42, 77), true); + context.EndFigure(); + + var brush = new CrossDrawingBrush() + { + TileMode = TileMode.None, + Drawing = new CrossDrawingGroup() + { + Children = new List() + { + new CrossGeometryDrawing(new CrossRectangleGeometry(new(0, 0, 300, 280))) + { + Brush = new CrossSolidColorBrush(Colors.White) + }, + new CrossGeometryDrawing(geometry) + { + Pen = new CrossPen() + { + Brush = new CrossSolidColorBrush(Colors.Black), + Thickness = 2 + } + } + } + } + }; + + RenderAndCompare(new CrossControl() + { + Width = 300, + Height = 280, + Background = brush + }); + } + + [CrossFact] + public void Should_Render_Geometry_With_Strokeless_Lines() + { + var geometry = new CrossStreamGeometry(); + + var context = geometry.GetContext(); + context.BeginFigure(new Point(150, 15), true, true); + context.LineTo(new Point(258, 77), true); + context.LineTo(new Point(258, 202), false); + context.LineTo(new Point(150, 265), true); + context.LineTo(new Point(42, 202), true); + context.LineTo(new Point(42, 77), false); + context.EndFigure(); + + var brush = new CrossDrawingBrush() + { + TileMode = TileMode.None, + Drawing = new CrossDrawingGroup() + { + Children = new List() + { + new CrossGeometryDrawing(new CrossRectangleGeometry(new(0, 0, 300, 280))) + { + Brush = new CrossSolidColorBrush(Colors.White) + }, + new CrossGeometryDrawing(geometry) + { + Pen = new CrossPen() + { + Brush = new CrossSolidColorBrush(Colors.Black), + Thickness = 2 + } + } + } + } + }; + + RenderAndCompare(new CrossControl() + { + Width = 300, + Height = 280, + Background = brush + }); + } + + // Skip the test for now +#if !AVALONIA_SKIA + [CrossTheory, + InlineData(PenLineCap.Flat, PenLineJoin.Round), + InlineData(PenLineCap.Flat, PenLineJoin.Bevel), + InlineData(PenLineCap.Flat, PenLineJoin.Miter), + InlineData(PenLineCap.Round, PenLineJoin.Round), + InlineData(PenLineCap.Round, PenLineJoin.Bevel), + InlineData(PenLineCap.Round, PenLineJoin.Miter), + ] +#endif + public void Should_Properly_CloseFigure(PenLineCap lineCap, PenLineJoin lineJoin) + { + var geometry = new CrossPathGeometry(); + + + var center = new Point(150, 150); + var r = 100d; + + var pointCount = 5; + var points = Enumerable.Range(0, pointCount).Select(a => a * Math.PI / pointCount * 2).Select(a => + new Point(center.X + Math.Sin(a) * r, center.Y + Math.Cos(a) * r)).ToArray(); + + var figure = new CrossPathFigure() { Start = points[0], Closed = true }; + geometry.Figures.Add(figure); + var lineNum = 0; + for (var c = 2; lineNum < pointCount - 1; c = (c + 2) % pointCount, lineNum++) + { + figure.Segments.Add(new CrossPathSegment.Line(points[c], (lineNum) % 3 < 2)); + } + + var control = new CrossFuncControl(ctx => + { + ctx.DrawRectangle(new CrossSolidColorBrush(Colors.White), null, new(0, 0, 300, 300)); + ctx.DrawGeometry(null, + new CrossPen() + { + Brush = new CrossSolidColorBrush(Colors.Black), + Thickness = 20, + LineJoin = lineJoin, + LineCap = lineCap + }, geometry); + }) { Width = 300, Height = 300 }; + RenderAndCompare(control, + $"{nameof(Should_Properly_CloseFigure)}_{lineCap}_{lineJoin}"); + } +} diff --git a/tests/Avalonia.RenderTests/CrossUI/CrossUI.Avalonia.cs b/tests/Avalonia.RenderTests/CrossUI/CrossUI.Avalonia.cs index 29a772b7ae4..8ac89dc220c 100644 --- a/tests/Avalonia.RenderTests/CrossUI/CrossUI.Avalonia.cs +++ b/tests/Avalonia.RenderTests/CrossUI/CrossUI.Avalonia.cs @@ -8,165 +8,293 @@ using Avalonia.Media.Immutable; using CrossUI; -#if AVALONIA_SKIA -namespace Avalonia.Skia.RenderTests.CrossUI; -#else -namespace Avalonia.Direct2D1.RenderTests.CrossUI; -#endif - -class AvaloniaCrossControl : Control +namespace CrossUI { - private readonly CrossControl _src; - private readonly Dictionary _children; + using Avalonia; - public AvaloniaCrossControl(CrossControl src) + public partial class CrossGlobals { - _src = src; - _children = src.Children.ToDictionary(x => x, x => new AvaloniaCrossControl(x)); - Width = src.Bounds.Width; - Height = src.Bounds.Height; - RenderTransform = new MatrixTransform(src.RenderTransform); - RenderTransformOrigin = new RelativePoint(default, RelativeUnit.Relative); - foreach (var ch in src.Children) - { - var c = _children[ch]; - VisualChildren.Add(c); - LogicalChildren.Add(c); + public static ICrossStreamGeometryContextImplProvider GetContextImplProvider() + { + return new AvaloniaCrossStreamGeometryContextImplProvider(); } } - protected override Size MeasureOverride(Size availableSize) + public class AvaloniaCrossStreamGeometryContextImplProvider : ICrossStreamGeometryContextImplProvider { - foreach (var ch in _children) - ch.Value.Measure(ch.Key.Bounds.Size); - return _src.Bounds.Size; + public ICrossStreamGeometryContextImpl Create() + { + return new AvaloniaCrossStreamGeometryContextImpl(); + } } - protected override Size ArrangeOverride(Size finalSize) + public class AvaloniaCrossStreamGeometryContextImpl : ICrossStreamGeometryContextImpl { - foreach (var ch in _children) - ch.Value.Arrange(ch.Key.Bounds); - return finalSize; - } + private StreamGeometry _streamGeometry; + private StreamGeometryContext _context; + private bool _isClosed; - public override void Render(DrawingContext context) - { - _src.Render(new AvaloniaCrossDrawingContext(context)); - } -} + public AvaloniaCrossStreamGeometryContextImpl() + { + _streamGeometry = new StreamGeometry(); + _context = _streamGeometry.Open(); + } -class AvaloniaCrossDrawingContext : ICrossDrawingContext -{ - private readonly DrawingContext _ctx; + public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection, bool isStroked) + { + _context.ArcTo(point, size, rotationAngle, isLargeArc, sweepDirection, isStroked); + } - public AvaloniaCrossDrawingContext(DrawingContext ctx) - { - _ctx = ctx; - } + public void BeginFigure(Point point, bool isFilled, bool isClosed) + { + _isClosed = isClosed; + _context.BeginFigure(point, isFilled); + } - static Transform? ConvertTransform(Matrix? m) => m == null ? null : new MatrixTransform(m.Value); + public void CubicBezierTo(Point controlPoint1, Point controlPoint2, Point endPoint, bool isStroked) + { + _context.CubicBezierTo(controlPoint1, controlPoint2, endPoint, isStroked); + } - static RelativeRect ConvertRect(Rect rc, BrushMappingMode mode) - => new RelativeRect(rc, - mode == BrushMappingMode.RelativeToBoundingBox ? RelativeUnit.Relative : RelativeUnit.Absolute); + public void Dispose() + { + _context.Dispose(); + } - static RelativePoint ConvertPoint(Point pt, BrushMappingMode mode) - => new(pt, mode == BrushMappingMode.RelativeToBoundingBox ? RelativeUnit.Relative : RelativeUnit.Absolute); + public void EndFigure() + { + _context.EndFigure(_isClosed); + Dispose(); + } - static RelativeScalar ConvertScalar(double scalar, BrushMappingMode mode) - => new(scalar, mode == BrushMappingMode.RelativeToBoundingBox ? RelativeUnit.Relative : RelativeUnit.Absolute); + public object GetGeometry() + { + return _streamGeometry; + } - 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); - throw new NotSupportedException(); + public void LineTo(Point point, bool isStroked) + { + _context.LineTo(point, isStroked); + } + + public void QuadraticBezierTo(Point controlPoint, Point endPoint, bool isStroked) + { + _context.QuadraticBezierTo(controlPoint, endPoint, isStroked); + } } - - static Drawing ConvertDrawing(CrossDrawing src) +} + +#if AVALONIA_SKIA +namespace Avalonia.Skia.RenderTests.CrossUI +{ +#else +namespace Avalonia.Direct2D1.RenderTests.CrossUI +{ +#endif + + class AvaloniaCrossControl : Control { - if (src is CrossDrawingGroup g) - return new DrawingGroup() { Children = new DrawingCollection(g.Children.Select(ConvertDrawing)) }; - if (src is CrossGeometryDrawing geo) - return new GeometryDrawing() + private readonly CrossControl _src; + private readonly Dictionary _children; + + public AvaloniaCrossControl(CrossControl src) + { + _src = src; + _children = src.Children.ToDictionary(x => x, x => new AvaloniaCrossControl(x)); + Width = src.Bounds.Width; + Height = src.Bounds.Height; + RenderTransform = new MatrixTransform(src.RenderTransform); + RenderTransformOrigin = new RelativePoint(default, RelativeUnit.Relative); + foreach (var ch in src.Children) { - Geometry = ConvertGeometry(geo.Geometry), Brush = ConvertBrush(geo.Brush), Pen = ConvertPen(geo.Pen) - }; - throw new NotSupportedException(); + var c = _children[ch]; + VisualChildren.Add(c); + LogicalChildren.Add(c); + } + } + + protected override Size MeasureOverride(Size availableSize) + { + foreach (var ch in _children) + ch.Value.Measure(ch.Key.Bounds.Size); + return _src.Bounds.Size; + } + + protected override Size ArrangeOverride(Size finalSize) + { + foreach (var ch in _children) + ch.Value.Arrange(ch.Key.Bounds); + return finalSize; + } + + public override void Render(DrawingContext context) + { + _src.Render(new AvaloniaCrossDrawingContext(context)); + } } - - static IBrush? ConvertBrush(CrossBrush? brush) + + class AvaloniaCrossDrawingContext : ICrossDrawingContext { - if (brush == null) - return null; - static Brush Sync(Brush dst, CrossBrush src) + private readonly DrawingContext _ctx; + + public AvaloniaCrossDrawingContext(DrawingContext ctx) { - dst.Opacity = src.Opacity; - dst.Transform = ConvertTransform(src.Transform); - dst.TransformOrigin = new RelativePoint(default, RelativeUnit.Absolute); - if (src.RelativeTransform != null) - throw new PlatformNotSupportedException(); - return dst; + _ctx = ctx; } - static Brush SyncTile(TileBrush dst, CrossTileBrush src) + static Transform? ConvertTransform(Matrix? m) => m == null ? null : new MatrixTransform(m.Value); + + static RelativeRect ConvertRect(Rect rc, BrushMappingMode mode) + => new RelativeRect(rc, + mode == BrushMappingMode.RelativeToBoundingBox ? RelativeUnit.Relative : RelativeUnit.Absolute); + + static RelativePoint ConvertPoint(Point pt, BrushMappingMode mode) + => new(pt, mode == BrushMappingMode.RelativeToBoundingBox ? RelativeUnit.Relative : RelativeUnit.Absolute); + + static RelativeScalar ConvertScalar(double scalar, BrushMappingMode mode) + => new(scalar, mode == BrushMappingMode.RelativeToBoundingBox ? RelativeUnit.Relative : RelativeUnit.Absolute); + + static Geometry ConvertGeometry(CrossGeometry g) { - dst.Stretch = src.Stretch; - dst.AlignmentX = src.AlignmentX; - dst.AlignmentY = src.AlignmentY; - dst.TileMode = src.TileMode; - dst.SourceRect = ConvertRect(src.Viewbox, src.ViewboxUnits); - dst.DestinationRect = ConvertRect(src.Viewport, src.ViewportUnits); - return Sync(dst, src); + 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(new PathSegments(), f.Segments.Select(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 + } + })) + })) + }; + throw new NotSupportedException(); } - static Brush SyncGradient(GradientBrush dst, CrossGradientBrush src) + static TList RetAddRange(TList l, IEnumerable en) where TList : IList { - dst.GradientStops = new GradientStops(); - dst.GradientStops.AddRange(src.GradientStops); - dst.SpreadMethod = src.SpreadMethod; - return Sync(dst, src); + foreach(var e in en) + l.Add(e); + return l; } - - if (brush is CrossSolidColorBrush br) - return Sync(new SolidColorBrush(br.Color), brush); - if (brush is CrossDrawingBrush db) - return SyncTile(new DrawingBrush(ConvertDrawing(db.Drawing)), db); - if (brush is CrossRadialGradientBrush radial) - return SyncGradient( - new RadialGradientBrush() + + static Drawing ConvertDrawing(CrossDrawing src) + { + if (src is CrossDrawingGroup g) + return new DrawingGroup() { Children = new DrawingCollection(g.Children.Select(ConvertDrawing)) }; + if (src is CrossGeometryDrawing geo) + return new GeometryDrawing() { - Center = ConvertPoint(radial.Center, radial.MappingMode), - GradientOrigin = ConvertPoint(radial.GradientOrigin, radial.MappingMode), - RadiusX = ConvertScalar(radial.RadiusX, radial.MappingMode), - RadiusY = ConvertScalar(radial.RadiusY, radial.MappingMode) - }, radial); - throw new NotSupportedException(); - } + Geometry = ConvertGeometry(geo.Geometry), Brush = ConvertBrush(geo.Brush), Pen = ConvertPen(geo.Pen) + }; + throw new NotSupportedException(); + } + + static IBrush? ConvertBrush(CrossBrush? brush) + { + if (brush == null) + return null; + static Brush Sync(Brush dst, CrossBrush src) + { + dst.Opacity = src.Opacity; + dst.Transform = ConvertTransform(src.Transform); + dst.TransformOrigin = new RelativePoint(default, RelativeUnit.Absolute); + if (src.RelativeTransform != null) + throw new PlatformNotSupportedException(); + return dst; + } - static IPen? ConvertPen(CrossPen? pen) - { - if (pen == null) - return null; - return new Pen(ConvertBrush(pen.Brush), pen.Thickness); - } + static Brush SyncTile(TileBrush dst, CrossTileBrush src) + { + dst.Stretch = src.Stretch; + dst.AlignmentX = src.AlignmentX; + dst.AlignmentY = src.AlignmentY; + dst.TileMode = src.TileMode; + dst.SourceRect = ConvertRect(src.Viewbox, src.ViewboxUnits); + dst.DestinationRect = ConvertRect(src.Viewport, src.ViewportUnits); + return Sync(dst, src); + } - static IImage ConvertImage(CrossImage image) - { - if (image is CrossBitmapImage bi) - return new Bitmap(bi.Path); - if (image is CrossDrawingImage di) - return new DrawingImage(ConvertDrawing(di.Drawing)); - throw new NotSupportedException(); - } + static Brush SyncGradient(GradientBrush dst, CrossGradientBrush src) + { + dst.GradientStops = new GradientStops(); + dst.GradientStops.AddRange(src.GradientStops); + dst.SpreadMethod = src.SpreadMethod; + return Sync(dst, src); + } + + if (brush is CrossSolidColorBrush br) + return Sync(new SolidColorBrush(br.Color), brush); + if (brush is CrossDrawingBrush db) + return SyncTile(new DrawingBrush(ConvertDrawing(db.Drawing)), db); + if (brush is CrossRadialGradientBrush radial) + return SyncGradient( + new RadialGradientBrush() + { + Center = ConvertPoint(radial.Center, radial.MappingMode), + GradientOrigin = ConvertPoint(radial.GradientOrigin, radial.MappingMode), + RadiusX = ConvertScalar(radial.RadiusX, radial.MappingMode), + RadiusY = ConvertScalar(radial.RadiusY, radial.MappingMode) + }, radial); + throw new NotSupportedException(); + } + + static IPen? ConvertPen(CrossPen? pen) + { + if (pen == null) + return null; + return new Pen(ConvertBrush(pen.Brush), pen.Thickness) { LineCap = pen.LineCap, LineJoin = pen.LineJoin }; + } + + static IImage ConvertImage(CrossImage image) + { + if (image is CrossBitmapImage bi) + return new Bitmap(bi.Path); + if (image is CrossDrawingImage di) + return new DrawingImage(ConvertDrawing(di.Drawing)); + throw new NotSupportedException(); + } - public void DrawRectangle(CrossBrush? brush, CrossPen? pen, Rect rc) => _ctx.DrawRectangle(ConvertBrush(brush), ConvertPen(pen), rc); - public void DrawGeometry(CrossBrush? brush, CrossPen? pen, CrossGeometry geometry) => - _ctx.DrawGeometry(ConvertBrush(brush), ConvertPen(pen), ConvertGeometry(geometry)); + public void DrawRectangle(CrossBrush? brush, CrossPen? pen, Rect rc) => _ctx.DrawRectangle(ConvertBrush(brush), ConvertPen(pen), rc); + public void DrawGeometry(CrossBrush? brush, CrossPen? pen, CrossGeometry geometry) => + _ctx.DrawGeometry(ConvertBrush(brush), ConvertPen(pen), ConvertGeometry(geometry)); - public void DrawImage(CrossImage image, Rect rc) => _ctx.DrawImage(ConvertImage(image), rc); + public void DrawImage(CrossImage image, Rect rc) => _ctx.DrawImage(ConvertImage(image), rc); + } } diff --git a/tests/Avalonia.RenderTests/CrossUI/CrossUI.cs b/tests/Avalonia.RenderTests/CrossUI/CrossUI.cs index 449f75b0c63..3710123eaf6 100644 --- a/tests/Avalonia.RenderTests/CrossUI/CrossUI.cs +++ b/tests/Avalonia.RenderTests/CrossUI/CrossUI.cs @@ -8,6 +8,11 @@ namespace CrossUI; +public partial class CrossGlobals +{ + +} + public class CrossBrush { public double Opacity = 1; @@ -108,6 +113,23 @@ public CrossEllipseGeometry() public Rect Rect { get; set; } } +public class CrossStreamGeometry : CrossGeometry +{ + private ICrossStreamGeometryContextImpl? _contextImpl; + + public CrossStreamGeometry() + { + + } + + public ICrossStreamGeometryContextImpl GetContext() + { + _contextImpl ??= CrossGlobals.GetContextImplProvider().Create(); + + return _contextImpl; + } +} + public class CrossRectangleGeometry : CrossGeometry { public Rect Rect; @@ -118,6 +140,26 @@ public CrossRectangleGeometry(Rect rect) } } +public class CrossPathGeometry : CrossGeometry +{ + public List Figures { get; set; } = new(); +} + +public class CrossPathFigure +{ + public Point Start { get; set; } + public List Segments { get; set; } = new(); + public bool Closed { get; set; } +} + +public abstract record class CrossPathSegment(bool IsStroked) +{ + public record Line(Point To, bool IsStroked) : CrossPathSegment(IsStroked); + public record Arc(Point Point, Size Size, double RotationAngle, bool IsLargeArc, SweepDirection SweepDirection, bool IsStroked) : CrossPathSegment(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 class CrossDrawingBrush : CrossTileBrush { public CrossDrawing Drawing; @@ -127,6 +169,24 @@ public class CrossPen { public CrossBrush Brush; public double Thickness = 1; + public PenLineJoin LineJoin { get; set; } = PenLineJoin.Miter; + public PenLineCap LineCap { get; set; } = PenLineCap.Flat; +} + +public interface ICrossStreamGeometryContextImpl : IDisposable +{ + object GetGeometry(); + void BeginFigure(Point point, bool isFilled, bool isClosed); + void EndFigure(); + void LineTo(Point point, bool isStroked); + void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection, bool isStroked); + void CubicBezierTo(Point controlPoint1, Point controlPoint2, Point endPoint, bool isStroked); + void QuadraticBezierTo(Point controlPoint, Point endPoint, bool isStroked); +} + +public interface ICrossStreamGeometryContextImplProvider +{ + ICrossStreamGeometryContextImpl Create(); } public interface ICrossDrawingContext diff --git a/tests/Avalonia.Skia.RenderTests/CrossTestBase.cs b/tests/Avalonia.Skia.RenderTests/CrossTestBase.cs index 215d5b48262..a94736b0c79 100644 --- a/tests/Avalonia.Skia.RenderTests/CrossTestBase.cs +++ b/tests/Avalonia.Skia.RenderTests/CrossTestBase.cs @@ -19,7 +19,7 @@ class CrossFactAttribute : FactAttribute } -class CrossThreoryAttribute : TheoryAttribute +class CrossTheoryAttribute : TheoryAttribute { } diff --git a/tests/TestFiles/CrossTests/Media/Geometry/Should_Properly_CloseFigure_Flat_Bevel.wpf.png b/tests/TestFiles/CrossTests/Media/Geometry/Should_Properly_CloseFigure_Flat_Bevel.wpf.png new file mode 100644 index 0000000000000000000000000000000000000000..e174ccad15f168cc9f2cb3f64706d0f09fb945e5 GIT binary patch literal 6815 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 literal 0 HcmV?d00001 diff --git a/tests/TestFiles/CrossTests/Media/Geometry/Should_Properly_CloseFigure_Flat_Miter.wpf.png b/tests/TestFiles/CrossTests/Media/Geometry/Should_Properly_CloseFigure_Flat_Miter.wpf.png new file mode 100644 index 0000000000000000000000000000000000000000..8cf98ba633671510748dff8e53eaacc6cbc6b73c GIT binary patch literal 7625 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 literal 0 HcmV?d00001 diff --git a/tests/TestFiles/CrossTests/Media/Geometry/Should_Properly_CloseFigure_Flat_Round.wpf.png b/tests/TestFiles/CrossTests/Media/Geometry/Should_Properly_CloseFigure_Flat_Round.wpf.png new file mode 100644 index 0000000000000000000000000000000000000000..f44dd37b36a3d41f6650a3b81c812d77262bebeb GIT binary patch literal 6910 zcmds6hgVZgut!8`Qlu&%C`BN` zO6XmBkw5_Hpb!w=^?QHCd-t4s?%6rJv%7QmH}jj>IQ=J@40K#{6ciK;+FELc6cm@h ze_xubfCLem=ns4@`5J1fP*e=?tOE@yxU!xy1w~a7{h8erpiTQ+%iNcOg0cPYd#T&2 z*nxtA>%F#`vT>mG)|{O)TlYox-d2v{ZUhtCHMQd0Tq!27+Uw-V5t{pW!MxnO$jAaZ zI=bs@FxQ^TeBVbRBd^dSl2zzHzypH~87iYyjB1<>^k3&Dt3=bW!@gZ8o@R_0|4wO| zNt;WX`%RD9w)#O#nGN-9maLYnOqf)oV19Wad2xke=eik1q_XWLnyV34Zr`BdcxlW0 z|6Q;*9he{!>-aQBF(HvpiTBU18L#23nY#RYPqWoLJUsUH2IO|sL`*BBY~qqF8(%=k zOR;>*Et_+V0r_c?aE-04t>Fx>=9?a~pT%80JRaK6ZnBwS-Q3;XzqGW3hfI{2lf{aI z$E;U=zy~tLFX);ZeTalWQ&ZFB1)t@Q;?KGQZEO~fe{?7Hw?yjIV8NsJw%@ReEKeB2 znwpx17Z+`%-~&9qOddQQZ_f=sS_pZzot6!=`XFlc1_iNj+KynLLaFyhk5eJ9 ztE#FR1O(L8Q!C!!~C%i2a2w_v8QEPomU7D6A9kAEcPho7Hx z&&KoLQVe+vYw&Y1vJ-MmYq~f;)oZ!C3!?00Y5MH(MNH}RXEkZ7ZXuCZF~oUWdHx(2 z`Pli_&#Ct(?W*XbleL@-i*IjIhA8nB9AGo%i%AY7>*dRr_ljBf^M_j?7PV#`?EN3b z1%h@LFXhK_47K4FH^xg%+I0?lzPOAQlA@RtUk@&!V1}S>C~8J@DO)L=_oCNDXm=Jf9U=2 zpc_Kz&zLXcx>elg>A>%7f@sztylut(Gnq_`ldwSU|kLfEcLYL7uBk}UVrB-MuW5?DJ#o0*sUTB{MGvs;Jd5t>v}zz z<(y+F({)}s@9UunX4Os%c8;_Wjy= zPDNxF42r_yVq0;}M;qg-eN5@q1uO;^5IN*mviW$!ewikaOxkPy3u!oCa}}jz z%!~QgxZX^9qiEmzNjl5~L=EL=V}zNZ$}Q>!GEdaKT>Vu}hq|5O30BA^PfnRb{$W6Ql-s3Zp@RLv*mXK1gLMUI zX$|^b378q9GbhhN%wu?OQ9X2YePe^ajOR9%=R4LeO6I^q>k>1p_0>KX4FjMKWMRzl zs>Ty3`x^f!&ucT$O%ZuF;?>wrq0jE#kM5t6>l6?$^eV1Y6psY7B89-?4JWQ-u{lL{hWu?Y9E-2G}Wx4S-L zgSs|Jp9FM0SQ|{zRh!ZMgGqAzLp~~)x*G2@1OYZ%0={O);=&~=%?+8kTa}fS6^(pG zg?gr-ps)%KVg1nQVC7>58IbU7w<&DFR{|>?n{>)G89*A~nA}9P@#@dac)MDlK>Q;#G&IiU{TiC-&H4HH z@yWNRN`F$1{mm0$_irdk&kA6DB*es$kL?G2%7D`+PrjIK@OS&t)TFTIb|CLc#5@{Q zz4UNuzA2csMY^{t{37gt^gN-J&_f~>AK~;JzuScWacpkCApNSaYGi3?)`@MiB<}Z) zap{oB;O+-4XZF0eDz2N!_!w|x%lLN5lf&1;_{fR-ifqfEr1!#RDd&2qC?7GKw=ApQ zIzg#LPjjT<7X|c{ZeBxy9)*~pvB=HIN=>;JKgE8ZnKmszIk+ErO2NOy(2B17lW$B$ z&d8?_+Iq&OLKa&uZ4!&UVhM{SVNA<;JR$W-*F1rVM|Xop=#_fC&(9DCq@(qOf+wU; z;e7jOT%EPKeA-pPl=D!O>Zm~S1D&?a`j57O+p{vhCOLO?F=~hY`IAn?v*UxQPIFb& z@WFJ+V8UVJwO7E@9qQ)ACS4u!`BG!|%MB>5hBv&tv*LR^Tth`y+kC!l_IyS*FG; zgkLlv4*f#Xs>ez1r0QH{f!LxOQU<0b+KLO)((ld(@1#ivKgoP-H!x!UCD2)2t^+sN|KCLNR7VYEe`wWX#WXSQt!8J=m(g~ z_vxP0C1Xc73yX~UBJZLnSrYJH&c^#kl#~3W5|pL4MV++Zp}wwj7{2hc;aSjp0{0_{ z2fx8}U}9lab{wSOo}EiyPRp3Fp9 zYO7v8$hP7ByQX^f02Y4jBu^0hF&_6Eljwwmf+}BZ&)!_gemUK-Jo;2A;SoOYko2zp zH?-l!(63jVsZpH*Sh`jFiQuNRxycHv_yE`o@v)q`Qq#&VCEcd@bndK^oLBKJ>|72o zm~e8um8Dchn99^-M-T!6_K36#R!xkvdZQ19nejfa`nN|-MAz{>;U3aJeVdjvVrmo0 z?lly2`fKaB7G_l&Fg<~f0uU}$r_V;&d%41?X)~b|r%g1PVqjwGMjrvOL@o_X&?pHD z)awWPCQ|liJn|wuabVNDwUwWrUxStZhrX8;pdqW*nuiI6(MUcT@YB?il_BGV=4M4- zk&x7=^IXBhXxWanfvim;QQW13-Dl<7JLk=b^1Uq}sA^i&y3>;3BbaMRd`gL{eQD3O zmESqrAymBHN@d(X#^mPYG~lG@2KOES*FXGAIYcd; zw-IPO{-HH|GyXv{jSs_pRLUN`5L(liV<1~W?xU^~n?_VrREdTDBd__l(jL=n|8brl zO2{a(%n~(VDcTaSu@-P@Kh0(ca|4~+xA!un8*i7Lsn{aAeqX;IgCWQv5&d7%#>JJS zq}ceHpR(Kf4JoBYXAcVr0fE9A6|<2q`{JkBtRYUh0Ij2je51&9^Jd&+L#z_89gS*C zD3~7FRgziFZjwh~3~!R%3e5*%&puNG5uKgQ>uVxEDP%dpGRmHFel;+nb$Fu+!N9#Y>c9`&HZ^q7TBT0S9BN?J&q}mOX?r?nOa# zLw>9v#t0G*YV9iVwV7oB3#6*Rz2(4a#$!PB%NO<~$lGyVALWx^G8|s>i}dBY_5Pa^ z9c$@p=y46a3-udKTXrqtD?;L@N7~u3gi6&|#xyuF=UI0mqN;r`{h?b|cUMe^>C6p&i zn+)A<)#M9JJ+59s6stFcU!1pH6~er2CA=fY8k-0<(eBF|TbK0FA+KJMdYpV%O++{+ zt3i}jVi9ICX`Xyd@-BCq;u$OtM&{;jdCb)I&w`$${X$&35%45fpo<1;p46L+j&*jUhp8i^ zpB$x;X{oPXHDj6w_zH*KeCYRp>7KhCx6n{VGOvkYfmPjsnyq=B#6NZgxZ7+179`3=BADta_9vn#t!lGDS=PZ(QrOz&pn663J0Kib=m?-W*b2_b1Y5IP&F2a*k4X zb3Ju`ZVWQwsc&gIn&TfWuk)eo9fzR#z9~Xtt7~KuAoeF8y7;cP3@!ZpD@?&ED-M>s zYG^sy6%%b)R^1=Qa+q9}y69f3hxY5!i$@zgI%1#y?A<;~x6qMU-X6=85uAKA0vVaU zvtc%gGIpKdRF~T*KA5OwCa4^-PV>5qrXQ(S%vC_<0Cvw>4#R^O9X{ozFdw;;7uVL- z9&eoSGnHo*tmO%;U%+K*?xJ=Y8XH-2f>Ej>O|JMWe5k?+Sqnw5-j*Dt8yzfWG}gbg z2DHB7Uus$AzkR?T219lKDrAG%!|kJJ%iRi&%HPZ%t1Q@ThE@%jgi@JMHYo=M2eaB3 zBY0tJe^z?lubMaIJ~ADdgv%U$Z05RgLn~y!JQ46x8Jgis1f+MSgj@7Z7ajfU{YA8U5ns)`w)OsjGo^5eOK=V;=)0ea#e3WO3NhYL7 zk;!BhK7n3uAWj{S&H;X-6H;N$h|e9-Z^>rjj&UC9UQ_Inhbm&ne*Ut1`cG3F*A%P1 zT%Dl86I6RQZb%(b$o#KvIX7Rey^-7CTKV^V!>QI0dV~J0Pn??BvDU9aO6f~^;KPz? z)BHg7)F|z-HDWt;#?>gU6I+oCovWzy$>zRt0*;{}9ye6<)Vu$^PEewGVhzzY<=wmM ztM2S&J`u#xSxg+_pZ<|}>Gi5=o*-4SiV;&v_A`{Kksxl!Rx3PQ$+woyFs*gp#S;l* zlqPk`0_kbT&U>Q1Zk{up@662Trk-bMrXgP*)2Izb8eYosS0`x3ZZVH;GY{C5=quP9{D+kvp62}-mgK4yb2rY zkD|ihjycuP=~hF@dLRJoF=ecwQCS3PQ-G96>lQghA4|IIcErk9Gz`@A`PS;(G;`&h z*}iaI)vwRReSOF$S5!^oFQ)v*Q?YN#@bL%#0h1C+-F`dH0=4{D!et~af)?FUbRGS> zcYSB6J+mAsVv6z{FmVC;*mCw(NCi392BH9X?1rk9Uw6EDGWOfZ#&*QF=fK&+12Udj zZb*f4db7!ZI;8GB_>OloT?P6A1X0?DHXb4>Dg$!$Olvcpgp+rt#}Xi};t~za5J1;?wtB2Tm)Bin=qW-fNnZmV?J&H&**> z@Jt_zIVKEDO+VD%lk+#a#>m)}KUwfBvea*kYb5Ez(GWjEy}#V`l_Jz*@U$l%TsCbT z|6p{n|7#R=`PmjhVhyD2a#^tDL?lE&?1*pn&BwbOE>ahx?v&b zXD4w=M(L`YXQxs=9v16505JzoknHkZy^cSp@v3OQb7+6Xu-camB&}(ZrvR~V8~8c( z**jl`UKb9BpG~{1o|JO006xh&!O{XASz04ich5u8!luXxLCVn=C8*#ZTm7-Nyp|Ge z%s7$HE@_;2?@x%fCOgNTNT_Wb$Dl6x@>9jJYo@+mz?OYy0p8ck3vUUAR)S;CQI6g~ z%rK6qfa+j!$Ap%s^mZ+4-P7uiKo48S{~KUZY3(uaQ^tEaTBdsKc<;w*A8*1Jq&mR2 zHKG>`NefQQ{8!5|r8Yv>M+%O)loRZqLP4luHywy? zVhZAT*RIC)E2VEQW0Re0&i<;2G(ZcJfOO2sa|#_l;xWJ#YeQ3(Vu5qemXeou_na~i zp)ca;(E0%s42HiT4*DQ+e)kDLs?n>!RI&_$vY5P!$L%h6#;=wkI7KbR;J=Dj?v)V| zT0t_LlFc563iY4APjy}kKaNSkQUVSoy{-@nPDdYg0v2}jzZFLK>Y5t^y(D_~os-L* z^=ajqj91PbZ?PaH%2G<;w+u+A&UL;;aH6kW=(Ct~(!wEVDdcE1Nxlx#UJW{X>6VZyHv2ki@ym9N$1J3hVRom=N z`ccKyjx; zr7D)GPZmOdXUSw(3)p0E5N+|ms@_&CjQWR2uiKN>Ck9M=b%?nH6^k+42q{;C)_|b~ z3+n3J7je;`e_+$>{;*7qc-syNp)%=YdkFhRy6v7TSazd7V#GhBA$zc}grLGn%oS}P z#z!^0VjfqfFSS3^2oQ%Ar&RJnM&OM+WBj4f&?9Cu_7(e^sFDgMKku#Ex;@6MLUvaG z^lu`a5n&Hf`5bE~LZbjn*Yfj_z!HthN10mkHQ6vrWCX=KCk+np;3si&li$j3#d^k0)B6rLAXTuhwyji;t*GPrv7n zXiyw91zn*Pp^1q3IM|E-{GE!!E>BRcZw;61!_4YzcW2ejTubJ44-NRt?Z3JjpD4;# z<1^6g?S8&1u4cePNq;fo9|UXepV@qmn3qO`p9_=iwq~VT;x!Aog_;D`rgLwI+ zN;9D7`d_)S^adAxtO3^QP=np2YQB*P=+R-iVy;_bS8ulV zI@_Tu_+=NyaRO!fOc5~+Z%EztIN?GS?br8-<{XBM_Odltu>yIy1$;1*T}%nH*+;`{ zffsK9d1~~>2>^>_UUC-__%dTg447&I9TVe1tcr~KJN4)ttXR3c#`cX{YFw$&S^%Y# zN9=dZ;&eYOy-i3MZfmOnY=)elxAt|)d9it?44|Mf#+IVL+YBYN;!~JQoTH$~nCa)^ zcac#r9SvOq#VDZNm<lah>4nMrS-$Q^y=qJ zZKQ!Lxw5&$SHQS-^66>6`GMle3Tqo3g8+6&Oi%r>A1S$h{Bv`2^WSWCl_QxqBBoH- zFP*mB>EhOHDvtERU77InkpEJPlBc02Kmr=gE`c7sWs)iD50VWf`}qL{mE)lT`M-Av y!=t0D9`j$)6h#~~{$8#K-T6Oov7*NIN9p47oh00)=)X5z6x!-f)GAbLBmW2I13R|> literal 0 HcmV?d00001 diff --git a/tests/TestFiles/CrossTests/Media/Geometry/Should_Properly_CloseFigure_Round_Bevel.wpf.png b/tests/TestFiles/CrossTests/Media/Geometry/Should_Properly_CloseFigure_Round_Bevel.wpf.png new file mode 100644 index 0000000000000000000000000000000000000000..57565c7fe9d2087d2af0ca05f09aa0590b19ca17 GIT binary patch literal 6852 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 literal 0 HcmV?d00001 diff --git a/tests/TestFiles/CrossTests/Media/Geometry/Should_Properly_CloseFigure_Round_Miter.wpf.png b/tests/TestFiles/CrossTests/Media/Geometry/Should_Properly_CloseFigure_Round_Miter.wpf.png new file mode 100644 index 0000000000000000000000000000000000000000..7814ba07c0fa9ad61b0a4b4e53e9c2b8cb7fb922 GIT binary patch literal 7665 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 v(S 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 SZVC7d6S{QX`W)>nhWsy6AB_+I literal 0 HcmV?d00001 diff --git a/tests/TestFiles/CrossTests/Media/Geometry/Should_Render_Stream_Geometry.wpf.png b/tests/TestFiles/CrossTests/Media/Geometry/Should_Render_Stream_Geometry.wpf.png new file mode 100644 index 0000000000000000000000000000000000000000..2fb85630ab250a949ee82e951a35e13782d68cf2 GIT binary patch literal 3500 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 literal 0 HcmV?d00001