Skip to content

Commit fe39d2f

Browse files
committed
Make ConfigureSceneBrushContentWithPicture less byzantine
1 parent d411a86 commit fe39d2f

File tree

3 files changed

+103
-102
lines changed

3 files changed

+103
-102
lines changed

src/Avalonia.Base/Rendering/Utilities/TileBrushCalculator.cs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -130,29 +130,35 @@ public static Vector CalculateTranslate(
130130
AlignmentY alignmentY,
131131
Rect sourceRect,
132132
Rect destinationRect,
133-
Vector scale)
133+
Vector scale) => CalculateTranslate(alignmentX, alignmentY,
134+
sourceRect.Size * scale, destinationRect.Size);
135+
136+
public static Vector CalculateTranslate(
137+
AlignmentX alignmentX,
138+
AlignmentY alignmentY,
139+
Size sourceSize,
140+
Size destinationSize)
134141
{
135142
var x = 0.0;
136143
var y = 0.0;
137-
var size = sourceRect.Size * scale;
138144

139145
switch (alignmentX)
140146
{
141147
case AlignmentX.Center:
142-
x += (destinationRect.Width - size.Width) / 2;
148+
x += (destinationSize.Width - sourceSize.Width) / 2;
143149
break;
144150
case AlignmentX.Right:
145-
x += destinationRect.Width - size.Width;
151+
x += destinationSize.Width - sourceSize.Width;
146152
break;
147153
}
148154

149155
switch (alignmentY)
150156
{
151157
case AlignmentY.Center:
152-
y += (destinationRect.Height - size.Height) / 2;
158+
y += (destinationSize.Height - sourceSize.Height) / 2;
153159
break;
154160
case AlignmentY.Bottom:
155-
y += destinationRect.Height - size.Height;
161+
y += destinationSize.Height - sourceSize.Height;
156162
break;
157163
}
158164

src/Skia/Avalonia.Skia/DrawingContextImpl.cs

Lines changed: 84 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,118 +1149,111 @@ private void ConfigureSceneBrushContentWithSurface(ref PaintWrapper paintWrapper
11491149
private void ConfigureSceneBrushContentWithPicture(ref PaintWrapper paintWrapper, ISceneBrushContent content,
11501150
Rect targetRect)
11511151
{
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)
11571177
{
11581178
paintWrapper.Paint.Color = SKColor.Empty;
1159-
11601179
return;
11611180
}
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)
11741193
{
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);
11771196

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);
11791201

1180-
brushTransform *= scaleTransform;
1202+
contentRenderTransform = contentRenderTransform * Matrix.CreateScale(scale) *
1203+
Matrix.CreateTranslation(alignmentTranslate);
11811204
}
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))
11861209
{
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();
12001213
}
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)
12031221
{
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);
12091226
}
12101227

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)))
12131236
{
1214-
ctx.PushRenderOptions(RenderOptions);
1215-
content.Render(ctx, brushTransform);
1216-
ctx.PopRenderOptions();
1237+
paintWrapper.Paint.FilterQuality = SKFilterQuality.None;
1238+
paintWrapper.Paint.Shader = shader;
12171239
}
1240+
}
12181241

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
12281246
? SKShaderTileMode.Decal
1229-
: tileBrush.TileMode == TileMode.FlipX || tileBrush.TileMode == TileMode.FlipXY
1247+
: mode == TileMode.FlipX || mode == TileMode.FlipXY
12301248
? SKShaderTileMode.Mirror
1231-
: SKShaderTileMode.Repeat;
1249+
: SKShaderTileMode.Repeat,
12321250

1233-
SKShaderTileMode tileY =
1234-
tileBrush.TileMode == TileMode.None
1251+
1252+
mode == TileMode.None
12351253
? SKShaderTileMode.Decal
1236-
: tileBrush.TileMode == TileMode.FlipY || tileBrush.TileMode == TileMode.FlipXY
1254+
: mode == TileMode.FlipY || mode == TileMode.FlipXY
12371255
? 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);
12641257
}
12651258

12661259
private static SKColorFilter CreateAlphaColorFilter(double opacity)

src/Skia/Avalonia.Skia/PictureRenderTarget.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,22 @@ public SKPicture GetPicture()
2525
_picture = null;
2626
return rv;
2727
}
28-
29-
public IDrawingContextImpl CreateDrawingContext(Size size)
28+
29+
public IDrawingContextImpl CreateDrawingContext(Size size, bool scaleToDpi = true)
3030
{
31+
if (scaleToDpi)
32+
size *= (_dpi / 96);
3133
var recorder = new SKPictureRecorder();
32-
var canvas = recorder.BeginRecording(new SKRect(0, 0, (float)(size.Width * _dpi.X / 96),
33-
(float)(size.Height * _dpi.Y / 96)));
34+
var canvas = recorder.BeginRecording(new SKRect(0, 0, (float)size.Width,
35+
(float)size.Height));
3436

3537
canvas.RestoreToCount(-1);
3638
canvas.ResetMatrix();
3739

3840
var createInfo = new DrawingContextImpl.CreateInfo
3941
{
4042
Canvas = canvas,
41-
ScaleDrawingToDpi = true,
43+
ScaleDrawingToDpi = scaleToDpi,
4244
Dpi = _dpi,
4345
DisableSubpixelTextRendering = true,
4446
GrContext = _grContext,

0 commit comments

Comments
 (0)