Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make our TileBrush code a bit less byzantine #17098

Merged
merged 3 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/Avalonia.Base/Media/VisualBrush.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public Visual? Visual

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<Compositor, ServerCompositionSimpleBrush> Factory =>
Expand Down Expand Up @@ -99,7 +99,7 @@ private protected override void SerializeChanges(Compositor c, BatchStreamWriter
}

if (data != null)
content = new(data.Data.Server, data.Rect, false);
content = new(data.Data.Server, data.Rect, true);
}

writer.WriteObject(content);
Expand Down
18 changes: 12 additions & 6 deletions src/Avalonia.Base/Rendering/Utilities/TileBrushCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,29 +130,35 @@ public static Vector CalculateTranslate(
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;
}

Expand Down
170 changes: 85 additions & 85 deletions src/Skia/Avalonia.Skia/DrawingContextImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1149,112 +1149,112 @@ private void ConfigureSceneBrushContentWithSurface(ref PaintWrapper paintWrapper
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)
Expand Down
12 changes: 7 additions & 5 deletions src/Skia/Avalonia.Skia/PictureRenderTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,22 @@ public SKPicture GetPicture()
_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();

var createInfo = new DrawingContextImpl.CreateInfo
{
Canvas = canvas,
ScaleDrawingToDpi = true,
ScaleDrawingToDpi = scaleToDpi,
Dpi = _dpi,
DisableSubpixelTextRendering = true,
GrContext = _grContext,
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading