From 04e76a92653b71be695dd8385aa1bd2458c28e77 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 24 Sep 2024 14:05:41 +0500 Subject: [PATCH] Make our TileBrush code a bit less byzantine (#17098) * Use scalable rasterization for visual brush * Make ConfigureSceneBrushContentWithPicture less byzantine --------- Co-authored-by: Benedikt Stebner --- src/Avalonia.Base/Media/VisualBrush.cs | 4 +- .../Utilities/TileBrushCalculator.cs | 18 +- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 170 +++++++++--------- src/Skia/Avalonia.Skia/PictureRenderTarget.cs | 12 +- .../VisualBrush_Grip_144_Dpi.expected.png | Bin 177 -> 182 bytes .../VisualBrush_InTree_Visual.expected.png | Bin 370 -> 395 bytes ...h_Is_Properly_Mapped_Relative.expected.png | Bin 9129 -> 9413 bytes ...uld_Be_Usable_As_Opacity_Mask.expected.png | Bin 5247 -> 4559 bytes 8 files changed, 106 insertions(+), 98 deletions(-) diff --git a/src/Avalonia.Base/Media/VisualBrush.cs b/src/Avalonia.Base/Media/VisualBrush.cs index 1e315688e9..3365689f1b 100644 --- a/src/Avalonia.Base/Media/VisualBrush.cs +++ b/src/Avalonia.Base/Media/VisualBrush.cs @@ -57,7 +57,7 @@ namespace Avalonia.Media using var recorder = new RenderDataDrawingContext(null); ImmediateRenderer.Render(recorder, Visual, Visual.Bounds); - return recorder.GetImmediateSceneBrushContent(this, new(Visual.Bounds.Size), false); + return recorder.GetImmediateSceneBrushContent(this, new(Visual.Bounds.Size), true); } internal override Func Factory => @@ -99,7 +99,7 @@ namespace Avalonia.Media } if (data != null) - content = new(data.Data.Server, data.Rect, false); + content = new(data.Data.Server, data.Rect, true); } writer.WriteObject(content); diff --git a/src/Avalonia.Base/Rendering/Utilities/TileBrushCalculator.cs b/src/Avalonia.Base/Rendering/Utilities/TileBrushCalculator.cs index 439bda4494..bf42ca2519 100644 --- a/src/Avalonia.Base/Rendering/Utilities/TileBrushCalculator.cs +++ b/src/Avalonia.Base/Rendering/Utilities/TileBrushCalculator.cs @@ -130,29 +130,35 @@ namespace Avalonia.Rendering.Utilities AlignmentY alignmentY, Rect sourceRect, Rect destinationRect, - Vector scale) + Vector scale) => CalculateTranslate(alignmentX, alignmentY, + sourceRect.Size * scale, destinationRect.Size); + + public static Vector CalculateTranslate( + AlignmentX alignmentX, + AlignmentY alignmentY, + Size sourceSize, + Size destinationSize) { var x = 0.0; var y = 0.0; - var size = sourceRect.Size * scale; switch (alignmentX) { case AlignmentX.Center: - x += (destinationRect.Width - size.Width) / 2; + x += (destinationSize.Width - sourceSize.Width) / 2; break; case AlignmentX.Right: - x += destinationRect.Width - size.Width; + x += destinationSize.Width - sourceSize.Width; break; } switch (alignmentY) { case AlignmentY.Center: - y += (destinationRect.Height - size.Height) / 2; + y += (destinationSize.Height - sourceSize.Height) / 2; break; case AlignmentY.Bottom: - y += destinationRect.Height - size.Height; + y += destinationSize.Height - sourceSize.Height; break; } diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 0a6a2187a3..f4ebd33c3f 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -1149,112 +1149,112 @@ namespace Avalonia.Skia private void ConfigureSceneBrushContentWithPicture(ref PaintWrapper paintWrapper, ISceneBrushContent content, Rect targetRect) { - var tileBrush = content.Brush; - - var contentBounds = content.Rect; - - if (contentBounds.Size.Width <= 0 || contentBounds.Size.Height <= 0) + // To understand what happens here, read + // https://learn.microsoft.com/en-us/dotnet/api/system.windows.media.tilebrush + // and the rest of the docs + + // Avalonia follows WPF and WPF's brushes completely ignore whatever layout bounds visuals have, + // and instead are using content bounds, e. g. + // ╔════════════════════════════════════╗ <--- target control + // ║ ║ layout bounds + // ║ ╔═════╗───────────┐ <--- content ║ + // ║ ║ ║<- content │ bounds ║ + // ║ ╚═════╝ ╔══╗ ║ + // ║ │ ^ content ╚══╝ ║ + // ║ │ ╔═════╗content^ │ ║ + // ║ └─╚═════╝─────────┘ ║ + // ║ ║ + // ╚════════════════════════════════════╝ + // + // Source Rect (aka ViewBox) is relative to the content bounds, not to the visual/drawing + + var contentRect = content.Rect; + var sourceRect = content.Brush.SourceRect.ToPixels(contentRect); + + // Early escape + if (contentRect.Size.Width <= 0 || contentRect.Size.Height <= 0 + || sourceRect.Size.Width <= 0 || sourceRect.Size.Height <= 0) { paintWrapper.Paint.Color = SKColor.Empty; - return; } - - var brushTransform = Matrix.Identity; - - var destinationRect = content.Brush.DestinationRect.ToPixels(targetRect.Size); - - var sourceRect = tileBrush.SourceRect.ToPixels(contentBounds); - - brushTransform *= Matrix.CreateTranslation(-sourceRect.Position); - - var scale = Vector.One; - - if (sourceRect.Size != destinationRect.Size) + + // We are moving the render area to make the top-left corner of the SourceRect (ViewBox) to be at (0,0) + // of the tile + var contentRenderTransform = Matrix.CreateTranslation(-sourceRect.X, -sourceRect.Y); + + // DestinationRect (aka Viewport) is specified relative to the target rect + var destinationRect = content.Brush.DestinationRect.ToPixels(targetRect); + + // Tile size matches the destination rect size + var tileSize = destinationRect.Size; + + // Apply transforms to stretch content to match the tile + if (sourceRect.Size != tileSize) { - //scale source to destination size - scale = tileBrush.Stretch.CalculateScaling(destinationRect.Size, sourceRect.Size); + // Stretch the content rect to match the tile size + var scale = content.Brush.Stretch.CalculateScaling(tileSize, sourceRect.Size); - var scaleTransform = Matrix.CreateScale(scale); + // And move the resulting rect according to alignment rules + var alignmentTranslate = TileBrushCalculator.CalculateTranslate( + content.Brush.AlignmentX, + content.Brush.AlignmentY, sourceRect.Size * scale, tileSize); - brushTransform *= scaleTransform; + contentRenderTransform = contentRenderTransform * Matrix.CreateScale(scale) * + Matrix.CreateTranslation(alignmentTranslate); } - - var transform = Matrix.Identity; - - if (content.Transform is not null) + + // Pre-rasterize the tile into SKPicture + using var pictureTarget = new PictureRenderTarget(_gpu, _grContext, _intermediateSurfaceDpi); + using (var ctx = pictureTarget.CreateDrawingContext(tileSize, false)) { - var transformOrigin = content.TransformOrigin.ToPixels(targetRect); - var offset = Matrix.CreateTranslation(transformOrigin); - transform = -offset * content.Transform.Value * offset; - - if (tileBrush.TileMode == TileMode.None) - { - brushTransform *= transform; - - destinationRect = destinationRect.TransformToAABB(transform); - - destinationRect = new Rect(0, 0, destinationRect.Left + destinationRect.Width, - destinationRect.Top + destinationRect.Height); - } + ctx.PushRenderOptions(RenderOptions); + content.Render(ctx, contentRenderTransform); + ctx.PopRenderOptions(); } - - if (tileBrush.Stretch != Stretch.Fill && transform == Matrix.Identity) + using var tile = pictureTarget.GetPicture(); + + // If there is no BrushTransform and destinationRect is at (0,0) we don't need any transforms + Matrix shaderTransform = Matrix.Identity; + + // Apply Brush.Transform to SKShader + if (content.Transform != null) { - //align content - var alignmentOffset = TileBrushCalculator.CalculateTranslate(tileBrush.AlignmentX, tileBrush.AlignmentY, - contentBounds, destinationRect, tileBrush.Stretch == Stretch.None ? Vector.One : scale); - - brushTransform *= Matrix.CreateTranslation(alignmentOffset); + + var transformOrigin = content.TransformOrigin.ToPixels(targetRect); + var offset = Matrix.CreateTranslation(transformOrigin); + shaderTransform = (-offset) * content.Transform.Value * (offset); } - using var pictureTarget = new PictureRenderTarget(_gpu, _grContext, _intermediateSurfaceDpi); - using (var ctx = pictureTarget.CreateDrawingContext(destinationRect.Size)) + // Apply destinationRect position + if (destinationRect.Position != default) + shaderTransform *= Matrix.CreateTranslation(destinationRect.X, destinationRect.Y); + + // Create shader + var (tileX, tileY) = GetTileModes(content.Brush.TileMode); + using(var shader = tile.ToShader(tileX, tileY, shaderTransform.ToSKMatrix(), + new SKRect(0, 0, tile.CullRect.Width, tile.CullRect.Height))) { - ctx.PushRenderOptions(RenderOptions); - content.Render(ctx, brushTransform); - ctx.PopRenderOptions(); + paintWrapper.Paint.FilterQuality = SKFilterQuality.None; + paintWrapper.Paint.Shader = shader; } + } - using var picture = pictureTarget.GetPicture(); - - var paintTransform = - tileBrush.TileMode != TileMode.None - ? SKMatrix.CreateTranslation(-(float)destinationRect.X, -(float)destinationRect.Y) - : SKMatrix.CreateIdentity(); - - SKShaderTileMode tileX = - tileBrush.TileMode == TileMode.None + (SKShaderTileMode x, SKShaderTileMode y) GetTileModes(TileMode mode) + { + return ( + mode == TileMode.None ? SKShaderTileMode.Decal - : tileBrush.TileMode == TileMode.FlipX || tileBrush.TileMode == TileMode.FlipXY + : mode == TileMode.FlipX || mode == TileMode.FlipXY ? SKShaderTileMode.Mirror - : SKShaderTileMode.Repeat; + : SKShaderTileMode.Repeat, - SKShaderTileMode tileY = - tileBrush.TileMode == TileMode.None + + mode == TileMode.None ? SKShaderTileMode.Decal - : tileBrush.TileMode == TileMode.FlipY || tileBrush.TileMode == TileMode.FlipXY + : mode == TileMode.FlipY || mode == TileMode.FlipXY ? SKShaderTileMode.Mirror - : SKShaderTileMode.Repeat; - - paintTransform = SKMatrix.Concat(paintTransform, - SKMatrix.CreateScale((float)(96.0 / _intermediateSurfaceDpi.X), (float)(96.0 / _intermediateSurfaceDpi.Y))); - - if (tileBrush.DestinationRect.Unit == RelativeUnit.Relative) - paintTransform = - paintTransform.PreConcat(SKMatrix.CreateTranslation((float)targetRect.X, (float)targetRect.Y)); - - if (tileBrush.TileMode != TileMode.None) - { - paintTransform = paintTransform.PreConcat(transform.ToSKMatrix()); - } - - using (var shader = picture.ToShader(tileX, tileY, paintTransform, - new SKRect(0, 0, picture.CullRect.Width, picture.CullRect.Height))) - { - paintWrapper.Paint.FilterQuality = SKFilterQuality.None; - paintWrapper.Paint.Shader = shader; - } + : SKShaderTileMode.Repeat); } private static SKColorFilter CreateAlphaColorFilter(double opacity) diff --git a/src/Skia/Avalonia.Skia/PictureRenderTarget.cs b/src/Skia/Avalonia.Skia/PictureRenderTarget.cs index 5d5494452a..bc8c145dfa 100644 --- a/src/Skia/Avalonia.Skia/PictureRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/PictureRenderTarget.cs @@ -25,12 +25,14 @@ internal class PictureRenderTarget : IDisposable _picture = null; return rv; } - - public IDrawingContextImpl CreateDrawingContext(Size size) + + public IDrawingContextImpl CreateDrawingContext(Size size, bool scaleToDpi = true) { + if (scaleToDpi) + size *= (_dpi / 96); var recorder = new SKPictureRecorder(); - var canvas = recorder.BeginRecording(new SKRect(0, 0, (float)(size.Width * _dpi.X / 96), - (float)(size.Height * _dpi.Y / 96))); + var canvas = recorder.BeginRecording(new SKRect(0, 0, (float)size.Width, + (float)size.Height)); canvas.RestoreToCount(-1); canvas.ResetMatrix(); @@ -38,7 +40,7 @@ internal class PictureRenderTarget : IDisposable var createInfo = new DrawingContextImpl.CreateInfo { Canvas = canvas, - ScaleDrawingToDpi = true, + ScaleDrawingToDpi = scaleToDpi, Dpi = _dpi, DisableSubpixelTextRendering = true, GrContext = _grContext, diff --git a/tests/TestFiles/Skia/Media/VisualBrush/VisualBrush_Grip_144_Dpi.expected.png b/tests/TestFiles/Skia/Media/VisualBrush/VisualBrush_Grip_144_Dpi.expected.png index d530c7251963cfb32eaab6c1021bf90f819793df..1c666e62e12c28f984f0a7c730169298ce364a45 100644 GIT binary patch delta 138 zcmV;50CoSd0k#24H$rx;6h^9jFoyZ(Rwlg zxmOPTlU}~wTM!Yy!vLQ#a%<|cZc<7aO_%pzYw=B8#up;uLABp9N39tEZs)qi9s<~d+J*$05Ciobvb_kNAlCR`eph`00000NkvXXt^-0~f(`LJx&QzG delta 132 zcmV-~0DJ$o0kHv)G<|4EL_t(YiS5-f4uBvGh2dw|1D-DH#B+l)8`Idzg=R$3%Iy mv0d41WldIh)MpvnmHh^SQ1O!Ua|$N_0000B-qK*#WAE}&fCj|e1{A~SRJJc!*&QyH?=*=rpUo~cFWIAab2>^3?T5p z{_NW3t;cR9pICl&=il`oUVM4WSdhZhs35?BN=#N{G*yZ-+s=1QYPxOP0rNAKc|U7k zH*P&vbzjC}n&s}s%W=*R#5pH^)SE28$PdLF>~rSt*jZP@3e;^W$-p3ULUzto&f}JT RtZX35Jzf1=);T3K0RSP}Lk0i< delta 150 zcmeBX{=_uF#81=H#WAE}&fCk1VoZhttOrxx*FS$CD0?QYF?^}}A3<9-1`s$b}z58_ykT^V)7Gc`_Tc zhoyU)$~t~T@Jzsfxtq0p-Z!ZA7q(3CVK_78CkO$ql7d^V9tUI0C8V>N5B~8v{*d6759G%edC8ozG>28%1Nn^n1|&bsXx{etFRr3L*jw zE5%TRTlk~p(-xW7=g3Fz=llJm*jUtnbl>(T-M<&do6mZ{Nkc}XsIv(`?X8oXkbt>2 zmOTrL9CFNW)ag~Vs!24)rV>huLc?29JNCY>AZ`+6zRD^Gq{}X`8&@IF+ehQ(_ar7q zny($U$#j2FcLW?&0=*hRak#GzuiotqNk7-CUOU=&(c`73_iK;?9mF=Mv z$DvQJ%%g8ykv@~igGloHPfi4$y-QYxsk1(g>n}lT=W{MDyhr~9oUSbLsvFhY#5`i| zA-YdwiDkbIyIFLc<*Qr{pQ3y9dsK~bf{o+PC~c^~N#jHf5kty;sFb7-xqv=yM3+Qp z?|PvDtiVq@1u+6NdKiQ?d4lo-m8&7!A3pz8rmb9FjAZ5=Ru%>9-cr&bLNAvi><~1n zhkRg7Cqb{$2c3YdPYrA5)jYkHfj2p!EU&uyjW0i8w<26m&F3{MoIN+U^au; zn=J&Zr1R>pzoIdSrcA`)@4Kw@6q#kStL zr!sw|dTFp9m1qvxT0L3D*xUDUczK?=Gv5N~B41ueh~~|hG>+Ni=zvq-v82(@Y$2#; z^D&)!k8-h;iUeP=Afe%24XC!v&l{lPQv9mWD_@yYIDlMk@?0_yJR<7yl%MhPkh>H( zQPeSg6-fyKScL&1W=*@tn-c^3yyp!BB>b_-07JCbuWy^1JUuG&(R{!A>Q2Ac=mG*; zD@9T$KwGt0I^jfaXD=mjc`ARyiH7+P37u&?4oX#`*F zjxM%&eJd64^Yde!fk^4k&DH!qchOJ~8QUGk&Woc-9Au0S!;`wg$CJGHxb6Br-RA3% z7!pXV%5Re@Vi#;)p)pCew>dj}mJyc5ZOKqDuFn^|-F z7T|YNSKi&-C2)7OTQ88Drwqrl<-m7;zqKzkuV&^w%yi1WLnt4I+uXJ6dE;7HO)vR< z0I7~mz?ya!A#>r7;iu=f``bI$l{Vf@XL%->jMA0PjiM*|FX;Voc>7bucl+$GfC;|W zM=lY-(bL|<)%hb#pg_V|bGYXFbX3Dqwo7$L51MD~;9cU10Ue5jeKDHp`N6lc?fJPm zCdv0zIb)KB1rkBtzaV%#E8nXGNpD$RQ|_UF94iTL@(UTgI>UGD)ruZe2)mStd*(O!C+ zzi?Blw&#$;7VY+yu}+6|{sDGukYEY-Or1#4qNODh#S|Jhljk2g-xfLpYRlOoA5pFT zuyhC11`*XcPSPYKBy1`1c*$Gd+%Ifg*6XwSvnae}l{2|qRF0lhpjZEsGi~C7+qR|S zy400DX&GeyOXS|<(s`NkR(EaF120`{LO7Qt)Ofb%j(<^(t#JOs!pfKx*#(ZUaHGdd z*n1b3`;92PKv=SH;NwZ_Vy6c!fP{*@@e7OF4s!KxELS*%)m}viiTOu(Gw6QLgM(=8 zs6IyK6b~UG(N81!UKae7)09Qcmngl2<^XCYh?E&eOeFu>|4oqkozX0^(4?r1W)V`R z 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 D(>$9IC9qK9qFLg5#fSDAM;BaRY!I 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 literal 5247 zcmeHL?@wD*7{2_-phH#{*^HLKoXWl+#UBhpX&JDI1T52#G$gp9E-DKb>KM>sTc{;t z3mKhRGDe_armNdCjaA$A~wRGrNFQ?+%w}D9dMoCxa`H!FSn+SwmS3JzHuM%1id3zbZ$GAvawl2T+@vei0b& zGPM?OE+b+us#|(i?R-x$N_Hs+2tR9@_eQ#RrkgWuLjvCd+}sh>8XO^Pyyhdg4i+b6 z$4fXGL~)NC56W?Ee8n$D5607JYtu)fB1sR1PVCA-a>uclScQSKx{v3HiOf#4;2ZFf` z3>EAzJxZk;koN}%IA8r8LiB-gUPI#XjR2wpy|sQBuuZ2J(4 zP{|s5y}QNZFxQ}{ccz|pTepLkz)h_!OVZWrm3J6#b4rFUFA->WvRB@_Cno5~g#33h zBe2pOvZ>;plQWq~rK<;CNn5aTN>r>)!W;sMk_06FO> zRk3_Wqf<--AEUZqBcK4>2m9BiR4t8rw4)d#Gsl9;XIW6^*~&<4k+!O(hQJoGBo4h) z(QD>4+XH_0t59uTc#*c9p*c$D?o(Mv*Gx#W^I$Ws#o*MOqj$|X3&vS6&SHO^1@n4i zp03cfwn8=}VbdQPa?l8Z2*GuDY5uj(;QzE~Z5jVjvqh_ZZqa^8x{y%LrQ=R5e{Q?r zj($~rqQ#Js9#P}9(|$BbYz)bO{*X3SD$1?)ukLJaILo25ng4Z0@Q9j4=kU