From 7da58ba205b1a6bff56bc7cc0a1f9c7656906b58 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Thu, 12 Sep 2024 10:27:04 +0200 Subject: [PATCH] Fix transform desync (#16363) * Make sure wrapper and platform DrawingContext have the same transform after Flush * Add some tests * Update Avalonia.RenderTests.WpfCompare.csproj * Remove comments * Use test font --- .../DrawingContextProxy.PendingCommands.cs | 7 +- .../CrossUI.Wpf.cs | 17 ++++- .../CrossTests/Media/DrawingContextTests.cs | 51 +++++++++++++ .../CrossUI/CrossUI.Avalonia.cs | 27 ++++++- tests/Avalonia.RenderTests/CrossUI/CrossUI.cs | 3 + .../Media/DrawingContextTests.cs | 69 ++++++++++++++++++ .../Transform_Should_Work_As_Expected.wpf.png | Bin 0 -> 793 bytes .../Should_Render_LinesAndText.expected.png | Bin 0 -> 2287 bytes 8 files changed, 171 insertions(+), 3 deletions(-) create mode 100644 tests/Avalonia.RenderTests/CrossTests/Media/DrawingContextTests.cs create mode 100644 tests/Avalonia.RenderTests/Media/DrawingContextTests.cs create mode 100644 tests/TestFiles/CrossTests/Media/DrawingContext/Transform_Should_Work_As_Expected.wpf.png create mode 100644 tests/TestFiles/Skia/Media/DrawingContext/Should_Render_LinesAndText.expected.png diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.PendingCommands.cs b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.PendingCommands.cs index bbf648d71f..5a2b5eb68d 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.PendingCommands.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.PendingCommands.cs @@ -148,5 +148,10 @@ internal partial class CompositorDrawingContextProxy ExecCommand(ref commands[index]); _commands.Clear(); + + if (Transform != _impl.Transform) + { + _impl.Transform = Transform; + } } -} \ No newline at end of file +} diff --git a/tests/Avalonia.RenderTests.WpfCompare/CrossUI.Wpf.cs b/tests/Avalonia.RenderTests.WpfCompare/CrossUI.Wpf.cs index 9f183729fe..633a0fff78 100644 --- a/tests/Avalonia.RenderTests.WpfCompare/CrossUI.Wpf.cs +++ b/tests/Avalonia.RenderTests.WpfCompare/CrossUI.Wpf.cs @@ -296,7 +296,22 @@ namespace Avalonia.RenderTests.WpfCompare return new DrawingImage(ConvertDrawing(di.Drawing)); throw new NotSupportedException(); } - + + public void PushTransform(Matrix matrix) + { + _ctx.PushTransform(new MatrixTransform(matrix.ToWpf())); + } + + public void Pop() + { + _ctx.Pop(); + } + + public void DrawLine(CrossPen pen, Point p1, Point p2) + { + _ctx.DrawLine(ConvertPen(pen), p1.ToWpf(), p2.ToWpf()); + } + 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)); diff --git a/tests/Avalonia.RenderTests/CrossTests/Media/DrawingContextTests.cs b/tests/Avalonia.RenderTests/CrossTests/Media/DrawingContextTests.cs new file mode 100644 index 0000000000..5ce8f258d1 --- /dev/null +++ b/tests/Avalonia.RenderTests/CrossTests/Media/DrawingContextTests.cs @@ -0,0 +1,51 @@ +using Avalonia.Media; +using CrossUI; + +#if AVALONIA_SKIA +namespace Avalonia.Skia.RenderTests.CrossTests; +#elif AVALONIA_D2D +namespace Avalonia.Direct2D1.RenderTests.CrossTests; +#else +namespace Avalonia.RenderTests.WpfCompare.CrossTests; +#endif + + +public class DrawingContextTests : CrossTestBase +{ + public DrawingContextTests() : base("Media/DrawingContext") + { + } + + [CrossFact] + public void Transform_Should_Work_As_Expected() + { + RenderAndCompare( + + new CrossFuncControl(ctx => + { + ctx.PushTransform(Matrix.CreateTranslation(100, 100)); + ctx.DrawLine(new CrossPen { Brush = new CrossSolidColorBrush(Colors.Red), Thickness = 1 }, + new Point(0, 0), new Point(100, 0)); + ctx.Pop(); + + ctx.PushTransform(Matrix.CreateTranslation(200, 100)); + ctx.DrawLine(new CrossPen { Brush = new CrossSolidColorBrush(Colors.Orange), Thickness = 1 }, + new Point(0, 0), new Point(0, 100)); + ctx.Pop(); + + ctx.PushTransform(Matrix.CreateTranslation(200, 200)); + ctx.DrawLine( + new CrossPen { Brush = new CrossSolidColorBrush(Colors.Yellow), Thickness = 1 }, + new Point(0, 0), new Point(-100, 0)); + ctx.Pop(); + + ctx.PushTransform(Matrix.CreateTranslation(100, 200)); + ctx.DrawLine(new CrossPen { Brush = new CrossSolidColorBrush(Colors.Green), Thickness = 1 }, + new Point(0, 0), new Point(0, -100)); + ctx.Pop(); + }) { Width = 300, Height = 300 } + + ); + + } +} diff --git a/tests/Avalonia.RenderTests/CrossUI/CrossUI.Avalonia.cs b/tests/Avalonia.RenderTests/CrossUI/CrossUI.Avalonia.cs index 42929a3690..f3ec416ad1 100644 --- a/tests/Avalonia.RenderTests/CrossUI/CrossUI.Avalonia.cs +++ b/tests/Avalonia.RenderTests/CrossUI/CrossUI.Avalonia.cs @@ -136,6 +136,7 @@ namespace Avalonia.Direct2D1.RenderTests.CrossUI class AvaloniaCrossDrawingContext : ICrossDrawingContext { private readonly DrawingContext _ctx; + private readonly Stack _stack = new(); public AvaloniaCrossDrawingContext(DrawingContext ctx) { @@ -303,7 +304,31 @@ namespace Avalonia.Direct2D1.RenderTests.CrossUI return new DrawingImage(ConvertDrawing(di.Drawing)); throw new NotSupportedException(); } - + + public void PushTransform(Matrix matrix) + { + _stack.Push(_ctx.PushTransform(matrix)); + } + + public void Pop() + { + var state = _stack.Pop(); + + state.Dispose(); + } + + public void DrawLine(CrossPen pen, Point p1, Point p2) + { + var avPen = ConvertPen(pen); + + if (avPen == null) + { + return; + } + + _ctx.DrawLine(avPen, p1, p2); + } + 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)); diff --git a/tests/Avalonia.RenderTests/CrossUI/CrossUI.cs b/tests/Avalonia.RenderTests/CrossUI/CrossUI.cs index 028ace0eff..7c690fc824 100644 --- a/tests/Avalonia.RenderTests/CrossUI/CrossUI.cs +++ b/tests/Avalonia.RenderTests/CrossUI/CrossUI.cs @@ -193,6 +193,9 @@ public interface ICrossStreamGeometryContextImplProvider public interface ICrossDrawingContext { + void PushTransform(Matrix matrix); + void Pop(); + void DrawLine(CrossPen pen, Point p1, Point p2); void DrawRectangle(CrossBrush? brush, CrossPen? pen, Rect rc); void DrawGeometry(CrossBrush? brush, CrossPen? pen, CrossGeometry geometry); void DrawImage(CrossImage image, Rect rc); diff --git a/tests/Avalonia.RenderTests/Media/DrawingContextTests.cs b/tests/Avalonia.RenderTests/Media/DrawingContextTests.cs new file mode 100644 index 0000000000..c201e7502a --- /dev/null +++ b/tests/Avalonia.RenderTests/Media/DrawingContextTests.cs @@ -0,0 +1,69 @@ +using System.Globalization; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Media; +using Xunit; +#pragma warning disable CS0649 + +#if AVALONIA_SKIA +namespace Avalonia.Skia.RenderTests; + +public class DrawingContextTests : TestBase +{ + public DrawingContextTests() : base(@"Media\DrawingContext") + { + } + + [Fact] + public async Task Should_Render_LinesAndText() + { + var target = new Border + { + Width = 300, + Height = 300, + Background = Brushes.White, + Child = new RenderControl() + }; + + await RenderToFile(target); + CompareImages(skipImmediate: true); + } + + internal class RenderControl : Control + { + private static readonly Typeface s_typeface = new Typeface(TestFontFamily); + + public override void Render(DrawingContext context) + { + var pen = new Pen(Brushes.LightGray, 10); + RenderLine1(context, pen); + RenderLine2(context, pen); + RenderLine3(context, pen); + RenderLine4(context, pen); + + RenderLine1(context, new Pen(Brushes.Red)); + RenderAText(context, new Point(50, 20)); + RenderLine2(context, new Pen(Brushes.Orange)); + RenderAText(context, new Point(50, -50)); + RenderLine3(context, new Pen(Brushes.Yellow)); + RenderAText(context, new Point(0, 0)); + RenderLine4(context, new Pen(Brushes.Green)); + } + + private static void RenderLine1(DrawingContext context, IPen pen) => context.DrawLine(pen, new Point(100, 100), new Point(200, 100)); + private static void RenderLine2(DrawingContext context, IPen pen) => context.DrawLine(pen, new Point(200, 100), new Point(200, 200)); + private static void RenderLine3(DrawingContext context, IPen pen) => context.DrawLine(pen, new Point(200, 200), new Point(100, 200)); + private static void RenderLine4(DrawingContext context, IPen pen) => context.DrawLine(pen, new Point(100, 200), new Point(100, 100)); + + private static void RenderAText(DrawingContext context, Point point) + { + using (context.PushOpacity(0.7)) + { + context.DrawText( + new FormattedText("any text to render", CultureInfo.CurrentCulture, FlowDirection.LeftToRight, + s_typeface, 12, Brushes.Black), point); + } + } + } +} +#endif diff --git a/tests/TestFiles/CrossTests/Media/DrawingContext/Transform_Should_Work_As_Expected.wpf.png b/tests/TestFiles/CrossTests/Media/DrawingContext/Transform_Should_Work_As_Expected.wpf.png new file mode 100644 index 0000000000000000000000000000000000000000..0c3b8c2b14a0ebc2a2b5416dcf7798e4e150a9f6 GIT binary patch literal 793 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4mO}jWo=(6kYX$ja(7}_cTVOdki(Mh=DT2=>j{iwv+|K^UPVAF` zy7rE~$1jb(?RmdKI;Vst0Om}v4FCWD literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Skia/Media/DrawingContext/Should_Render_LinesAndText.expected.png b/tests/TestFiles/Skia/Media/DrawingContext/Should_Render_LinesAndText.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..3e775c44a380bd8aed14b2938aa33fce460dd0ba GIT binary patch literal 2287 zcmds3eNfVO7>6=-CDP0FGGB1Jn%*tTUZ$m_R923avP={eUvQNMAzem*NH*<#GuOGa zgUHr(&JeY9iXt!LoN}2~^b=H+s7MJB!AL;GpTpg)>yPcbdvPr{exPJ(73q-DMAihw_L z_uha^D2J9(#IlGRkzFBb+3`fX+O)RB!x5(P;TOXJc|S=*6O?X6yhvpzsA;rKBqB2( z?088}(Glu-Jny5iEP7;J$KiM@k4GfYR0-1Z(8}MaEul44g>O5DMS&&)xraU42lGCv z|k%uT2K#h%;{)_^1kIX?A!$+LZ!p8t)3%igfJI zfYF{@rOf~i0uPcl=*x&f+04k+x*+*XEAguvy@&HV82qc*{fVgn?|NnXd80*16`$E* zc}=bGH_?zBa~mdzQ~8{7nuDCtD7tKqL4{$TUCWd< zHU;>2>WRL_@ zS!hP_Z$H{=>d}W*Zd)xG%JmOu;3c(BiDF#NmlIUp{J2Nx=5NjqAAAatrYO5{aFmnZ35Mp&-;C!H;o!ltX(dE!g6R z={>8N*!8q5yUMpWXL~K&qfh+e_EFPa_qIm1DUpmBX)1WLnewdgkg1@4RklZXH5?$6 zxWkbE$Y~y7{-8zKo*R97yC#!263t^Csv21i4lsrw_Aryh1aea>xpD``ORggtEhDtF zr}YX)1u>{t&7HlK@hTlAP!pRux`&gE4_{hiBvv^aK-TFVIy&ena!gUI$(v0KTEF5^ z2KR}7poZdyk+Kcdf#gGFfMV?NXk42JExl*Ri6?IjpLwR~RM#Jks6#VPvM%U+1XSA! zBTU|eB=M3Nfv4mhc@uYx2vBizxFFv2vRoPlEgkiBqWHyX&d-E@VPCyDho&Ic}$yY(bXiD$KbiLn}9vwM12Q z(Y*V<1WE$h?GyYc_IKob5@7AF&0~$k93fBd=f`3s9(Etv!6tE%*$(xYSzVY6x+ z=azYRAWoRd0hhZjcGbhhpxBSA7g<}RAS?ip$^Cyj!u}-v&1Vk?` zJ-5?q-u}X|H2VJ8e}VWP35E1ULN-o2CUnA!zh4PB;~;3FGtVR6Gfwa}ULK&^b6s8~ z235;+O^6#rufB>{t2p% z>B;Z!cj#TxFQ(xzKW0X5@bxt!pfw+8Q#QWSi}rWheY~gre`=s}4l?*am<;{oz;@RU O27=xjx2JA*X3?MjgX%c| literal 0 HcmV?d00001