Browse Source

Rework tile brush calculation (#15157)

pull/15203/head
Benedikt Stebner 2 years ago
committed by GitHub
parent
commit
23b3a767dc
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 79
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  2. 24
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  3. 8
      src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs
  4. 29
      tests/Avalonia.RenderTests/Media/ImageBrushTests.cs
  5. 56
      tests/Avalonia.RenderTests/Media/ImageDrawingTests.cs
  6. BIN
      tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_Tile_Small_Image_With_Transform.expected.png
  7. BIN
      tests/TestFiles/Direct2D1/Media/ImageDrawing/Should_Render_DrawingBrushTransform.expected.png
  8. BIN
      tests/TestFiles/Skia/Media/ImageBrush/ImageBrush_Tile_Small_Image_With_Transform.expected.png
  9. BIN
      tests/TestFiles/Skia/Media/ImageDrawing/Should_Render_DrawingBrushTransform.expected.png

79
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -1149,43 +1149,85 @@ namespace Avalonia.Skia
private void ConfigureSceneBrushContentWithPicture(ref PaintWrapper paintWrapper, ISceneBrushContent content, private void ConfigureSceneBrushContentWithPicture(ref PaintWrapper paintWrapper, ISceneBrushContent content,
Rect targetRect) Rect targetRect)
{ {
var rect = content.Rect; var tileBrush = content.Brush;
var contentSize = rect.Size;
if (contentSize.Width <= 0 || contentSize.Height <= 0) var contentBounds = content.Rect;
if (contentBounds.Size.Width <= 0 || contentBounds.Size.Height <= 0)
{ {
paintWrapper.Paint.Color = SKColor.Empty; paintWrapper.Paint.Color = SKColor.Empty;
return; return;
} }
var tileBrush = content.Brush; var brushTransform = Matrix.CreateTranslation(-contentBounds.Position);
var transform = rect.TopLeft == default ? Matrix.Identity : Matrix.CreateTranslation(-rect.X, -rect.Y);
contentBounds = contentBounds.TransformToAABB(brushTransform);
var destinationRect = content.Brush.DestinationRect.ToPixels(targetRect.Size);
if (tileBrush.Stretch != Stretch.None)
{
//scale content to destination size
var scale = tileBrush.Stretch.CalculateScaling(destinationRect.Size, contentBounds.Size);
var scaleTransform = Matrix.CreateScale(scale);
contentBounds = contentBounds.TransformToAABB(scaleTransform);
brushTransform *= scaleTransform;
}
var sourceRect = tileBrush.SourceRect.ToPixels(contentBounds);
//scale content to source size
if (contentBounds.Size != sourceRect.Size)
{
var scale = tileBrush.Stretch.CalculateScaling(sourceRect.Size, contentBounds.Size);
var scaleTransform = Matrix.CreateScale(scale);
contentBounds = contentBounds.TransformToAABB(scaleTransform);
brushTransform *= scaleTransform;
}
var transform = Matrix.Identity;
if (content.Transform is not null) if (content.Transform is not null)
{ {
var transformOrigin = content.TransformOrigin.ToPixels(targetRect); var transformOrigin = content.TransformOrigin.ToPixels(targetRect);
var offset = Matrix.CreateTranslation(transformOrigin); var offset = Matrix.CreateTranslation(transformOrigin);
transform = -offset * content.Transform.Value * offset;
}
transform *= -offset * content.Transform.Value * offset; if (content.Brush.TileMode == TileMode.None)
{
brushTransform *= transform;
}
if (tileBrush.Stretch == Stretch.None && transform == Matrix.Identity)
{
//align content
var alignmentOffset = TileBrushCalculator.CalculateTranslate(tileBrush.AlignmentX, tileBrush.AlignmentY,
contentBounds, destinationRect, Vector.One);
brushTransform *= Matrix.CreateTranslation(alignmentOffset);
} }
var calc = new TileBrushCalculator(tileBrush, contentSize, targetRect.Size);
transform *= calc.IntermediateTransform;
using var pictureTarget = new PictureRenderTarget(_gpu, _grContext, _intermediateSurfaceDpi); using var pictureTarget = new PictureRenderTarget(_gpu, _grContext, _intermediateSurfaceDpi);
using (var ctx = pictureTarget.CreateDrawingContext(calc.IntermediateSize)) using (var ctx = pictureTarget.CreateDrawingContext(destinationRect.Size))
{ {
ctx.PushClip(calc.IntermediateClip);
ctx.PushRenderOptions(RenderOptions); ctx.PushRenderOptions(RenderOptions);
content.Render(ctx, transform); content.Render(ctx, brushTransform);
ctx.PopRenderOptions(); ctx.PopRenderOptions();
ctx.PopClip();
} }
using var picture = pictureTarget.GetPicture(); using var picture = pictureTarget.GetPicture();
var paintTransform = var paintTransform =
tileBrush.TileMode != TileMode.None tileBrush.TileMode != TileMode.None
? SKMatrix.CreateTranslation(-(float)calc.DestinationRect.X, -(float)calc.DestinationRect.Y) ? SKMatrix.CreateTranslation(-(float)destinationRect.X, -(float)destinationRect.Y)
: SKMatrix.CreateIdentity(); : SKMatrix.CreateIdentity();
SKShaderTileMode tileX = SKShaderTileMode tileX =
@ -1204,11 +1246,16 @@ namespace Avalonia.Skia
paintTransform = SKMatrix.Concat(paintTransform, paintTransform = SKMatrix.Concat(paintTransform,
SKMatrix.CreateScale((float)(96.0 / _intermediateSurfaceDpi.X), (float)(96.0 / _intermediateSurfaceDpi.Y))); SKMatrix.CreateScale((float)(96.0 / _intermediateSurfaceDpi.X), (float)(96.0 / _intermediateSurfaceDpi.Y)));
if (tileBrush.DestinationRect.Unit == RelativeUnit.Relative) if (tileBrush.DestinationRect.Unit == RelativeUnit.Relative)
paintTransform = paintTransform =
paintTransform.PreConcat(SKMatrix.CreateTranslation((float)targetRect.X, (float)targetRect.Y)); 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, using (var shader = picture.ToShader(tileX, tileY, paintTransform,
new SKRect(0, 0, picture.CullRect.Width, picture.CullRect.Height))) new SKRect(0, 0, picture.CullRect.Width, picture.CullRect.Height)))
{ {

24
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@ -614,6 +614,20 @@ namespace Avalonia.Direct2D1.Media
var dpi = new Vector(_deviceContext.DotsPerInch.Width, _deviceContext.DotsPerInch.Height); var dpi = new Vector(_deviceContext.DotsPerInch.Width, _deviceContext.DotsPerInch.Height);
var pixelSize = PixelSize.FromSizeWithDpi(intermediateSize, dpi); var pixelSize = PixelSize.FromSizeWithDpi(intermediateSize, dpi);
var transform = rect.TopLeft == default ?
Matrix.Identity :
Matrix.CreateTranslation(-rect.X, -rect.Y);
var brushTransform = Matrix.Identity;
if (sceneBrushContent.Transform != null)
{
var transformOrigin = sceneBrushContent.TransformOrigin.ToPixels(rect);
var offset = Matrix.CreateTranslation(transformOrigin);
brushTransform = -offset * sceneBrushContent.Transform.Value * offset;
}
using (var intermediate = new BitmapRenderTarget( using (var intermediate = new BitmapRenderTarget(
_deviceContext, _deviceContext,
CompatibleRenderTargetOptions.None, CompatibleRenderTargetOptions.None,
@ -623,16 +637,12 @@ namespace Avalonia.Direct2D1.Media
{ {
intermediate.Clear(null); intermediate.Clear(null);
if (sceneBrush?.Transform is not null) if (sceneBrush?.TileMode == TileMode.None)
{ {
var transformOrigin = sceneBrushContent.TransformOrigin.ToPixels(rect); transform = brushTransform * transform;
var offset = Matrix.CreateTranslation(transformOrigin);
ctx.Transform = -offset * sceneBrush.Transform.Value * offset;
} }
sceneBrushContent.Render(ctx, sceneBrushContent.Render(ctx, transform);
rect.TopLeft == default ? null : Matrix.CreateTranslation(-rect.X, -rect.Y));
} }
return new ImageBrushImpl( return new ImageBrushImpl(

8
src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs

@ -71,6 +71,14 @@ namespace Avalonia.Direct2D1.Media
if (offset != default) if (offset != default)
tileTransform = Matrix.CreateTranslation(offset); tileTransform = Matrix.CreateTranslation(offset);
if (brush.Transform != null && brush.TileMode != TileMode.None)
{
var transformOrigin = brush.TransformOrigin.ToPixels(destinationRect);
var originOffset = Matrix.CreateTranslation(transformOrigin);
tileTransform = -originOffset * brush.Transform.Value * originOffset * tileTransform;
}
return new BrushProperties return new BrushProperties
{ {
Opacity = (float)brush.Opacity, Opacity = (float)brush.Opacity,

29
tests/Avalonia.RenderTests/Media/ImageBrushTests.cs

@ -521,5 +521,34 @@ namespace Avalonia.Direct2D1.RenderTests.Media
CompareImages(); CompareImages();
} }
[Fact]
public async Task ImageBrush_Tile_Small_Image_With_Transform()
{
Decorator target = new Decorator
{
Width = 200,
Height = 200,
Child = new Rectangle
{
Margin = new Thickness(8),
Fill = new DrawingBrush
{
DestinationRect = new RelativeRect(0,0,32,32, RelativeUnit.Absolute),
Transform = new TranslateTransform(10,10),
Stretch = Stretch.None,
TileMode = TileMode.Tile,
Drawing = new ImageDrawing
{
Rect = new Rect(0,0,32,32),
ImageSource = new Bitmap(SmallBitmapPath)
}
}
}
};
await RenderToFile(target);
CompareImages();
}
} }
} }

56
tests/Avalonia.RenderTests/Media/ImageDrawingTests.cs

@ -83,5 +83,61 @@ namespace Avalonia.Direct2D1.RenderTests.Media
await RenderToFile(target); await RenderToFile(target);
CompareImages(); CompareImages();
} }
[Fact]
public async Task Should_Render_DrawingBrushTransform()
{
var target = new Border
{
Width = 400,
Height = 400,
Child = new DrawingBrushTransformTest()
};
await RenderToFile(target);
CompareImages();
}
public class DrawingBrushTransformTest : Control
{
private readonly DrawingBrush _brush;
public DrawingBrushTransformTest()
{
_brush = new DrawingBrush()
{
TileMode = TileMode.None,
SourceRect = new RelativeRect(0, 0, 50, 50, RelativeUnit.Absolute),
DestinationRect = new RelativeRect(0, 0, 1, 1, RelativeUnit.Relative),
Transform = new TranslateTransform(150, 150),
Drawing = new DrawingGroup()
{
Children = new DrawingCollection()
{
new GeometryDrawing
{
Brush = Brushes.Crimson,
Geometry = new RectangleGeometry(new(0, 0, 100, 100))
},
new GeometryDrawing
{
Brush = Brushes.Blue,
Geometry = new RectangleGeometry(new(20, 20, 60, 60))
}
}
}
};
}
public override void Render(DrawingContext drawingContext)
{
var pop = drawingContext.PushTransform(Matrix.CreateTranslation(100, 100));
var rc = new Rect(0, 0, 200, 200);
drawingContext.DrawRectangle(new SolidColorBrush(Colors.DimGray), null, rc);
drawingContext.DrawRectangle(_brush, null, rc);
pop.Dispose();
}
}
} }
} }

BIN
tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_Tile_Small_Image_With_Transform.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
tests/TestFiles/Direct2D1/Media/ImageDrawing/Should_Render_DrawingBrushTransform.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
tests/TestFiles/Skia/Media/ImageBrush/ImageBrush_Tile_Small_Image_With_Transform.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
tests/TestFiles/Skia/Media/ImageDrawing/Should_Render_DrawingBrushTransform.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Loading…
Cancel
Save