Ver Fonte

Rework tile brush calculation (#15157)

Benedikt Stebner há 1 ano atrás
pai
commit
23b3a767dc

+ 63 - 16
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)))
             {

+ 17 - 7
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 - 0
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 - 0
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 - 0
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


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


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


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