@@ -1149,118 +1149,111 @@ private void ConfigureSceneBrushContentWithSurface(ref PaintWrapper paintWrapper
1149
1149
private void ConfigureSceneBrushContentWithPicture ( ref PaintWrapper paintWrapper , ISceneBrushContent content ,
1150
1150
Rect targetRect )
1151
1151
{
1152
- var tileBrush = content . Brush ;
1153
-
1154
- var contentBounds = content . Rect ;
1155
-
1156
- if ( contentBounds . Size . Width <= 0 || contentBounds . Size . Height <= 0 )
1152
+ // To understand what happens here, read
1153
+ // https://learn.microsoft.com/en-us/dotnet/api/system.windows.media.tilebrush
1154
+ // and the rest of the docs
1155
+
1156
+ // Avalonia follows WPF and WPF's brushes completely ignore whatever layout bounds visuals have,
1157
+ // and instead are using content bounds, e. g.
1158
+ // ┌────────────────────────────────────┐ <--- layout bounds
1159
+ // │ │
1160
+ // │ ┏━━━━━┱┄┄┄┄┄┄┄┄┄┄┄┐ <--- content │
1161
+ // │ ┃ ┃<- content ┊ bounds │
1162
+ // │ ┞━━━━━┛ ┏━━┧ │
1163
+ // │ ┊ ^ content ┗━━┦ │
1164
+ // │ ┊ ┏━━━━━┓content^ ┊ │
1165
+ // │ └┄┺━━━━━┹┄┄┄┄┄┄┄┄┄┘ │
1166
+ // │ │
1167
+ // └────────────────────────────────────┘
1168
+ //
1169
+ // Source Rect (aka ViewBox) is relative to the content bounds, not to the visual/drawing
1170
+
1171
+ var contentRect = content . Rect ;
1172
+ var sourceRect = content . Brush . SourceRect . ToPixels ( contentRect ) ;
1173
+
1174
+ // Early escape
1175
+ if ( contentRect . Size . Width <= 0 || contentRect . Size . Height <= 0
1176
+ || sourceRect . Size . Width <= 0 || sourceRect . Size . Height <= 0 )
1157
1177
{
1158
1178
paintWrapper . Paint . Color = SKColor . Empty ;
1159
-
1160
1179
return ;
1161
1180
}
1162
-
1163
- var brushTransform = Matrix . Identity ;
1164
-
1165
- var destinationRect = content . Brush . DestinationRect . ToPixels ( targetRect . Size ) ;
1166
-
1167
- var sourceRect = tileBrush . SourceRect . ToPixels ( contentBounds ) ;
1168
-
1169
- brushTransform *= Matrix . CreateTranslation ( - sourceRect . Position ) ;
1170
-
1171
- var scale = Vector . One ;
1172
-
1173
- if ( sourceRect . Size != destinationRect . Size )
1181
+
1182
+ // We are moving the render area to make the top-left corner of the SourceRect (ViewBox) to be at (0,0)
1183
+ // of the tile
1184
+ var contentRenderTransform = Matrix . CreateTranslation ( - sourceRect . X , - sourceRect . Y ) ;
1185
+
1186
+ // Tile size can be specified relatively to the destination rect size
1187
+ var destinationRect = content . Brush . DestinationRect . ToPixels ( targetRect ) ;
1188
+
1189
+ var tileSize = destinationRect . Size ;
1190
+
1191
+ // Apply transforms to stretch content to match the tile
1192
+ if ( sourceRect . Size != tileSize )
1174
1193
{
1175
- //scale source to destination size
1176
- scale = tileBrush . Stretch . CalculateScaling ( destinationRect . Size , sourceRect . Size ) ;
1194
+ // Stretch the content rect to match the tile size
1195
+ var scale = content . Brush . Stretch . CalculateScaling ( tileSize , sourceRect . Size ) ;
1177
1196
1178
- var scaleTransform = Matrix . CreateScale ( scale ) ;
1197
+ // And move the resulting rect according to alignment rules
1198
+ var alignmentTranslate = TileBrushCalculator . CalculateTranslate (
1199
+ content . Brush . AlignmentX ,
1200
+ content . Brush . AlignmentY , sourceRect . Size * scale , tileSize ) ;
1179
1201
1180
- brushTransform *= scaleTransform ;
1202
+ contentRenderTransform = contentRenderTransform * Matrix . CreateScale ( scale ) *
1203
+ Matrix . CreateTranslation ( alignmentTranslate ) ;
1181
1204
}
1182
-
1183
- var transform = Matrix . Identity ;
1184
-
1185
- if ( content . Transform is not null )
1205
+
1206
+ // Pre-rasterize the tile into SKPicture
1207
+ using var pictureTarget = new PictureRenderTarget ( _gpu , _grContext , _intermediateSurfaceDpi ) ;
1208
+ using ( var ctx = pictureTarget . CreateDrawingContext ( tileSize , false ) )
1186
1209
{
1187
- var transformOrigin = content . TransformOrigin . ToPixels ( targetRect ) ;
1188
- var offset = Matrix . CreateTranslation ( transformOrigin ) ;
1189
- transform = - offset * content . Transform . Value * offset ;
1190
-
1191
- if ( tileBrush . TileMode == TileMode . None )
1192
- {
1193
- brushTransform *= transform ;
1194
-
1195
- destinationRect = destinationRect . TransformToAABB ( transform ) ;
1196
-
1197
- destinationRect = new Rect ( 0 , 0 , destinationRect . Left + destinationRect . Width ,
1198
- destinationRect . Top + destinationRect . Height ) ;
1199
- }
1210
+ ctx . PushRenderOptions ( RenderOptions ) ;
1211
+ content . Render ( ctx , contentRenderTransform ) ;
1212
+ ctx . PopRenderOptions ( ) ;
1200
1213
}
1201
-
1202
- if ( tileBrush . Stretch != Stretch . Fill && transform == Matrix . Identity )
1214
+ using var tile = pictureTarget . GetPicture ( ) ;
1215
+
1216
+ // If there is no BrushTransform and destinationRect is at (0,0) we don't need any transforms
1217
+ Matrix shaderTransform = Matrix . Identity ;
1218
+
1219
+ // Apply Brush.Transform to SKShader
1220
+ if ( content . Transform != null )
1203
1221
{
1204
- //align content
1205
- var alignmentOffset = TileBrushCalculator . CalculateTranslate ( tileBrush . AlignmentX , tileBrush . AlignmentY ,
1206
- sourceRect , destinationRect , tileBrush . Stretch == Stretch . None ? Vector . One : scale ) ;
1207
-
1208
- brushTransform *= Matrix . CreateTranslation ( alignmentOffset ) ;
1222
+
1223
+ var transformOrigin = content . TransformOrigin . ToPixels ( targetRect ) ;
1224
+ var offset = Matrix . CreateTranslation ( transformOrigin ) ;
1225
+ shaderTransform = ( - offset ) * content . Transform . Value * ( offset ) ;
1209
1226
}
1210
1227
1211
- using var pictureTarget = new PictureRenderTarget ( _gpu , _grContext , _intermediateSurfaceDpi ) ;
1212
- using ( var ctx = pictureTarget . CreateDrawingContext ( destinationRect . Size ) )
1228
+ // Apply destinationRect position
1229
+ if ( destinationRect . Position != default )
1230
+ shaderTransform *= Matrix . CreateTranslation ( destinationRect . X , destinationRect . Y ) ;
1231
+
1232
+ // Create shader
1233
+ var ( tileX , tileY ) = GetTileModes ( content . Brush . TileMode ) ;
1234
+ using ( var shader = tile . ToShader ( tileX , tileY , shaderTransform . ToSKMatrix ( ) ,
1235
+ new SKRect ( 0 , 0 , tile . CullRect . Width , tile . CullRect . Height ) ) )
1213
1236
{
1214
- ctx . PushRenderOptions ( RenderOptions ) ;
1215
- content . Render ( ctx , brushTransform ) ;
1216
- ctx . PopRenderOptions ( ) ;
1237
+ paintWrapper . Paint . FilterQuality = SKFilterQuality . None ;
1238
+ paintWrapper . Paint . Shader = shader ;
1217
1239
}
1240
+ }
1218
1241
1219
- using var picture = pictureTarget . GetPicture ( ) ;
1220
-
1221
- var paintTransform =
1222
- tileBrush . TileMode != TileMode . None
1223
- ? SKMatrix . CreateTranslation ( - ( float ) destinationRect . X , - ( float ) destinationRect . Y )
1224
- : SKMatrix . CreateIdentity ( ) ;
1225
-
1226
- SKShaderTileMode tileX =
1227
- tileBrush . TileMode == TileMode . None
1242
+ ( SKShaderTileMode x , SKShaderTileMode y ) GetTileModes ( TileMode mode )
1243
+ {
1244
+ return (
1245
+ mode == TileMode . None
1228
1246
? SKShaderTileMode . Decal
1229
- : tileBrush . TileMode == TileMode . FlipX || tileBrush . TileMode == TileMode . FlipXY
1247
+ : mode == TileMode . FlipX || mode == TileMode . FlipXY
1230
1248
? SKShaderTileMode . Mirror
1231
- : SKShaderTileMode . Repeat ;
1249
+ : SKShaderTileMode . Repeat ,
1232
1250
1233
- SKShaderTileMode tileY =
1234
- tileBrush . TileMode == TileMode . None
1251
+
1252
+ mode == TileMode . None
1235
1253
? SKShaderTileMode . Decal
1236
- : tileBrush . TileMode == TileMode . FlipY || tileBrush . TileMode == TileMode . FlipXY
1254
+ : mode == TileMode . FlipY || mode == TileMode . FlipXY
1237
1255
? SKShaderTileMode . Mirror
1238
- : SKShaderTileMode . Repeat ;
1239
-
1240
- paintTransform = SKMatrix . Concat ( paintTransform ,
1241
- SKMatrix . CreateScale ( ( float ) ( 96.0 / _intermediateSurfaceDpi . X ) , ( float ) ( 96.0 / _intermediateSurfaceDpi . Y ) ) ) ;
1242
-
1243
- if ( tileBrush . DestinationRect . Unit == RelativeUnit . Relative )
1244
- paintTransform =
1245
- paintTransform . PreConcat ( SKMatrix . CreateTranslation ( ( float ) targetRect . X , ( float ) targetRect . Y ) ) ;
1246
-
1247
- if ( tileBrush . TileMode != TileMode . None )
1248
- {
1249
- paintTransform = paintTransform . PreConcat ( transform . ToSKMatrix ( ) ) ;
1250
- }
1251
- else
1252
- {
1253
- paintTransform =
1254
- paintTransform . PreConcat ( SKMatrix . CreateTranslation ( ( float ) destinationRect . Left ,
1255
- ( float ) destinationRect . Top ) ) ;
1256
- }
1257
-
1258
- using ( var shader = picture . ToShader ( tileX , tileY , paintTransform ,
1259
- new SKRect ( 0 , 0 , picture . CullRect . Width , picture . CullRect . Height ) ) )
1260
- {
1261
- paintWrapper . Paint . FilterQuality = SKFilterQuality . None ;
1262
- paintWrapper . Paint . Shader = shader ;
1263
- }
1256
+ : SKShaderTileMode . Repeat ) ;
1264
1257
}
1265
1258
1266
1259
private static SKColorFilter CreateAlphaColorFilter ( double opacity )
0 commit comments