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,
Rect targetRect)
{
var rect = content.Rect;
var contentSize = rect.Size;
if (contentSize.Width <= 0 || contentSize.Height <= 0)
var tileBrush = content.Brush;
var contentBounds = content.Rect;
if (contentBounds.Size.Width <= 0 || contentBounds.Size.Height <= 0)
{
paintWrapper.Paint.Color = SKColor.Empty;
return;
}
var tileBrush = content.Brush;
var transform = rect.TopLeft == default ? Matrix.Identity : Matrix.CreateTranslation(-rect.X, -rect.Y);
var brushTransform = Matrix.CreateTranslation(-contentBounds.Position);
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)
{
var transformOrigin = content.TransformOrigin.ToPixels(targetRect);
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 ctx = pictureTarget.CreateDrawingContext(calc.IntermediateSize))
using (var ctx = pictureTarget.CreateDrawingContext(destinationRect.Size))
{
ctx.PushClip(calc.IntermediateClip);
ctx.PushRenderOptions(RenderOptions);
content.Render(ctx, transform);
content.Render(ctx, brushTransform);
ctx.PopRenderOptions();
ctx.PopClip();
}
using var picture = pictureTarget.GetPicture();
var paintTransform =
tileBrush.TileMode != TileMode.None
? SKMatrix.CreateTranslation(-(float)calc.DestinationRect.X, -(float)calc.DestinationRect.Y)
? SKMatrix.CreateTranslation(-(float)destinationRect.X, -(float)destinationRect.Y)
: SKMatrix.CreateIdentity();
SKShaderTileMode tileX =
@ -1204,11 +1246,16 @@ namespace Avalonia.Skia
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)))
{

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 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(
_deviceContext,
CompatibleRenderTargetOptions.None,
@ -623,16 +637,12 @@ namespace Avalonia.Direct2D1.Media
{
intermediate.Clear(null);
if (sceneBrush?.Transform is not null)
if (sceneBrush?.TileMode == TileMode.None)
{
var transformOrigin = sceneBrushContent.TransformOrigin.ToPixels(rect);
var offset = Matrix.CreateTranslation(transformOrigin);
ctx.Transform = -offset * sceneBrush.Transform.Value * offset;
transform = brushTransform * transform;
}
sceneBrushContent.Render(ctx,
rect.TopLeft == default ? null : Matrix.CreateTranslation(-rect.X, -rect.Y));
sceneBrushContent.Render(ctx, transform);
}
return new ImageBrushImpl(

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

@ -71,6 +71,14 @@ namespace Avalonia.Direct2D1.Media
if (offset != default)
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
{
Opacity = (float)brush.Opacity,

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

@ -521,5 +521,34 @@ namespace Avalonia.Direct2D1.RenderTests.Media
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);
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