@ -1149,112 +1149,112 @@ namespace Avalonia.Skia
private void ConfigureSceneBrushContentWithPicture ( ref PaintWrapper paintWrapper , ISceneBrushContent content ,
private void ConfigureSceneBrushContentWithPicture ( ref PaintWrapper paintWrapper , ISceneBrushContent content ,
Rect targetRect )
Rect targetRect )
{
{
var tileBrush = content . Brush ;
// To understand what happens here, read
// https://learn.microsoft.com/en-us/dotnet/api/system.windows.media.tilebrush
var contentBounds = content . Rect ;
// and the rest of the docs
if ( contentBounds . Size . Width < = 0 | | contentBounds . Size . Height < = 0 )
// 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 ;
paintWrapper . Paint . Color = SKColor . Empty ;
return ;
return ;
}
}
var brushTransform = Matrix . Identity ;
// 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 destinationRect = content . Brush . DestinationRect . ToPixels ( targetRect . Size ) ;
var contentRenderTransform = Matrix . CreateTranslation ( - sourceRect . X , - sourceRect . Y ) ;
var sourceRect = tileBrush . SourceRect . ToPixels ( contentBounds ) ;
// DestinationRect (aka Viewport) is specified relative to the target rect
var destinationRect = content . Brush . DestinationRect . ToPixels ( targetRect ) ;
brushTransform * = Matrix . CreateTranslation ( - sourceRect . Position ) ;
// Tile size matches the destination rect size
var scale = Vector . One ;
var tileSize = destinationRect . Size ;
if ( sourceRect . Size ! = destinationRect . Size )
// Apply transforms to stretch content to match the tile
if ( sourceRect . Size ! = tileSize )
{
{
//scale source to destination size
// Stretch the content rect to match the tile size
scale = tileBrush . Stretch . CalculateScaling ( destinationRect . Size , sourceRect . Size ) ;
var scale = con tent . Brush . Stretch . CalculateScaling ( tile Size, 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 ;
// Pre-rasterize the tile into SKPicture
using var pictureTarget = new PictureRenderTarget ( _ gpu , _ grContext , _ intermediateSurfaceDpi ) ;
if ( content . Transform is not null )
using ( var ctx = pictureTarget . CreateDrawingContext ( tileSize , false ) )
{
{
var transformOrigin = content . TransformOrigin . ToPixels ( targetRect ) ;
ctx . PushRenderOptions ( RenderOptions ) ;
var offset = Matrix . CreateTranslation ( transformOrigin ) ;
content . Render ( ctx , contentRenderTransform ) ;
transform = - offset * content . Transform . Value * offset ;
ctx . PopRenderOptions ( ) ;
if ( tileBrush . TileMode = = TileMode . None )
{
brushTransform * = transform ;
destinationRect = destinationRect . TransformToAABB ( transform ) ;
destinationRect = new Rect ( 0 , 0 , destinationRect . Left + destinationRect . Width ,
destinationRect . Top + destinationRect . Height ) ;
}
}
}
using var tile = pictureTarget . GetPicture ( ) ;
if ( tileBrush . Stretch ! = Stretch . Fill & & transform = = Matrix . Identity )
// 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 ,
var transformOrigin = content . TransformOrigin . ToPixels ( targetRect ) ;
contentBounds , destinationRect , tileBrush . Stretch = = Stretch . None ? Vector . One : scale ) ;
var offset = Matrix . CreateTranslation ( transformOrigin ) ;
shaderTransform = ( - offset ) * content . Transform . Value * ( offset ) ;
brushTransform * = Matrix . CreateTranslation ( alignmentOffset ) ;
}
}
using var pictureTarget = new PictureRenderTarget ( _ gpu , _ grContext , _ intermediateSurfaceDpi ) ;
// Apply destinationRect position
using ( var ctx = pictureTarget . CreateDrawingContext ( destinationRect . Size ) )
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 ) ;
paintWrapper . Paint . FilterQuality = SKFilterQuality . None ;
content . Render ( ctx , brushTransform ) ;
paintWrapper . Paint . Shader = shader ;
ctx . PopRenderOptions ( ) ;
}
}
}
using var picture = pictureTarget . GetPicture ( ) ;
( SKShaderTileMode x , SKShaderTileMode y ) GetTileModes ( TileMode mode )
{
var paintTransform =
return (
tileBrush . TileMode ! = TileMode . None
mode = = TileMode . None
? SKMatrix . CreateTranslation ( - ( float ) destinationRect . X , - ( float ) destinationRect . Y )
: SKMatrix . CreateIdentity ( ) ;
SKShaderTileMode tileX =
tileBrush . TileMode = = TileMode . None
? SKShaderTileMode . Decal
? SKShaderTileMode . Decal
: tileBrush . TileM ode = = TileMode . FlipX | | tileBrush . TileM ode = = TileMode . FlipXY
: mode = = TileMode . FlipX | | mode = = TileMode . FlipXY
? SKShaderTileMode . Mirror
? SKShaderTileMode . Mirror
: SKShaderTileMode . Repeat ;
: SKShaderTileMode . Repeat ,
SKShaderTileMode tileY =
tileBrush . TileM ode = = TileMode . None
m ode = = TileMode . None
? SKShaderTileMode . Decal
? SKShaderTileMode . Decal
: tileBrush . TileM ode = = TileMode . FlipY | | tileBrush . TileM ode = = TileMode . FlipXY
: m ode = = TileMode . FlipY | | m ode = = TileMode . FlipXY
? SKShaderTileMode . Mirror
? SKShaderTileMode . Mirror
: SKShaderTileMode . Repeat ;
: SKShaderTileMode . Repeat ) ;
paintTransform = SKMatrix . Concat ( paintTransform ,
SKMatrix . CreateScale ( ( float ) ( 9 6.0 / _ intermediateSurfaceDpi . X ) , ( float ) ( 9 6.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 ;
}
}
}
private static SKColorFilter CreateAlphaColorFilter ( double opacity )
private static SKColorFilter CreateAlphaColorFilter ( double opacity )