diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index eecb56e90a..493d87988b 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/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))) { diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index d889e394e6..c40c5e9871 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/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( diff --git a/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs index ef4d91df9d..7f595f8272 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs +++ b/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, diff --git a/tests/Avalonia.RenderTests/Media/ImageBrushTests.cs b/tests/Avalonia.RenderTests/Media/ImageBrushTests.cs index 2a49444a10..aef33bc131 100644 --- a/tests/Avalonia.RenderTests/Media/ImageBrushTests.cs +++ b/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(); + } } } diff --git a/tests/Avalonia.RenderTests/Media/ImageDrawingTests.cs b/tests/Avalonia.RenderTests/Media/ImageDrawingTests.cs index a86113b4ef..3d593cd677 100644 --- a/tests/Avalonia.RenderTests/Media/ImageDrawingTests.cs +++ b/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(); + } + } } } diff --git a/tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_Tile_Small_Image_With_Transform.expected.png b/tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_Tile_Small_Image_With_Transform.expected.png new file mode 100644 index 0000000000..ac400eae2a Binary files /dev/null and b/tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_Tile_Small_Image_With_Transform.expected.png differ diff --git a/tests/TestFiles/Direct2D1/Media/ImageDrawing/Should_Render_DrawingBrushTransform.expected.png b/tests/TestFiles/Direct2D1/Media/ImageDrawing/Should_Render_DrawingBrushTransform.expected.png new file mode 100644 index 0000000000..fa8bd2f004 Binary files /dev/null and b/tests/TestFiles/Direct2D1/Media/ImageDrawing/Should_Render_DrawingBrushTransform.expected.png differ diff --git a/tests/TestFiles/Skia/Media/ImageBrush/ImageBrush_Tile_Small_Image_With_Transform.expected.png b/tests/TestFiles/Skia/Media/ImageBrush/ImageBrush_Tile_Small_Image_With_Transform.expected.png new file mode 100644 index 0000000000..a5e7bf6187 Binary files /dev/null and b/tests/TestFiles/Skia/Media/ImageBrush/ImageBrush_Tile_Small_Image_With_Transform.expected.png differ diff --git a/tests/TestFiles/Skia/Media/ImageDrawing/Should_Render_DrawingBrushTransform.expected.png b/tests/TestFiles/Skia/Media/ImageDrawing/Should_Render_DrawingBrushTransform.expected.png new file mode 100644 index 0000000000..35f1ca62e4 Binary files /dev/null and b/tests/TestFiles/Skia/Media/ImageDrawing/Should_Render_DrawingBrushTransform.expected.png differ