Browse Source

Fixed gradient brush target rectangles.

When drawn via the `DrawingContext` gradient brushes were not taking the origin of the target rect into account, only the size.

Fixes #5947.
Steven Kirk 4 years ago
parent
commit
82754cb163

+ 24 - 23
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@@ -164,7 +164,7 @@ namespace Avalonia.Skia
         /// <inheritdoc />
         public void DrawLine(IPen pen, Point p1, Point p2)
         {
-            using (var paint = CreatePaint(_strokePaint, pen, new Size(Math.Abs(p2.X - p1.X), Math.Abs(p2.Y - p1.Y))))
+            using (var paint = CreatePaint(_strokePaint, pen, new Rect(p1, p2).Normalize()))
             {
                 if (paint.Paint is object)
                 {
@@ -177,10 +177,10 @@ namespace Avalonia.Skia
         public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry)
         {
             var impl = (GeometryImpl) geometry;
-            var size = geometry.Bounds.Size;
+            var rect = geometry.Bounds;
 
-            using (var fill = brush != null ? CreatePaint(_fillPaint, brush, size) : default(PaintWrapper))
-            using (var stroke = pen?.Brush != null ? CreatePaint(_strokePaint, pen, size) : default(PaintWrapper))
+            using (var fill = brush != null ? CreatePaint(_fillPaint, brush, rect) : default(PaintWrapper))
+            using (var stroke = pen?.Brush != null ? CreatePaint(_strokePaint, pen, rect) : default(PaintWrapper))
             {
                 if (fill.Paint != null)
                 {
@@ -354,7 +354,7 @@ namespace Avalonia.Skia
 
             if (brush != null)
             {
-                using (var paint = CreatePaint(_fillPaint, brush, rect.Rect.Size))
+                using (var paint = CreatePaint(_fillPaint, brush, rect.Rect))
                 {
                     if (isRounded)
                     {
@@ -397,7 +397,7 @@ namespace Avalonia.Skia
 
             if (pen?.Brush != null)
             {
-                using (var paint = CreatePaint(_strokePaint, pen, rect.Rect.Size))
+                using (var paint = CreatePaint(_strokePaint, pen, rect.Rect))
                 {
                     if (paint.Paint is object)
                     {
@@ -417,7 +417,7 @@ namespace Avalonia.Skia
         /// <inheritdoc />
         public void DrawText(IBrush foreground, Point origin, IFormattedTextImpl text)
         {
-            using (var paint = CreatePaint(_fillPaint, foreground, text.Bounds.Size))
+            using (var paint = CreatePaint(_fillPaint, foreground, text.Bounds))
             {
                 var textImpl = (FormattedTextImpl) text;
                 textImpl.Draw(this, Canvas, origin.ToSKPoint(), paint, _canTextUseLcdRendering);
@@ -427,7 +427,7 @@ namespace Avalonia.Skia
         /// <inheritdoc />
         public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
         {
-            using (var paintWrapper = CreatePaint(_fillPaint, foreground, glyphRun.Size))
+            using (var paintWrapper = CreatePaint(_fillPaint, foreground, new Rect(glyphRun.Size)))
             {
                 var glyphRunImpl = (GlyphRunImpl)glyphRun.GlyphRunImpl;
 
@@ -537,7 +537,7 @@ namespace Avalonia.Skia
             var paint = new SKPaint();
 
             Canvas.SaveLayer(paint);
-            _maskStack.Push(CreatePaint(paint, mask, bounds.Size, true));
+            _maskStack.Push(CreatePaint(paint, mask, bounds, true));
         }
 
         /// <inheritdoc />
@@ -593,18 +593,19 @@ namespace Avalonia.Skia
         /// <param name="paintWrapper">Paint wrapper.</param>
         /// <param name="targetSize">Target size.</param>
         /// <param name="gradientBrush">Gradient brush.</param>
-        private void ConfigureGradientBrush(ref PaintWrapper paintWrapper, Size targetSize, IGradientBrush gradientBrush)
+        private void ConfigureGradientBrush(ref PaintWrapper paintWrapper, Rect targetRect, IGradientBrush gradientBrush)
         {
             var tileMode = gradientBrush.SpreadMethod.ToSKShaderTileMode();
             var stopColors = gradientBrush.GradientStops.Select(s => s.Color.ToSKColor()).ToArray();
             var stopOffsets = gradientBrush.GradientStops.Select(s => (float)s.Offset).ToArray();
+            var position = targetRect.Position.ToSKPoint();
 
             switch (gradientBrush)
             {
                 case ILinearGradientBrush linearGradient:
                 {
-                    var start = linearGradient.StartPoint.ToPixels(targetSize).ToSKPoint();
-                    var end = linearGradient.EndPoint.ToPixels(targetSize).ToSKPoint();
+                    var start = position + linearGradient.StartPoint.ToPixels(targetRect.Size).ToSKPoint();
+                    var end = position + linearGradient.EndPoint.ToPixels(targetRect.Size).ToSKPoint();
 
                     // would be nice to cache these shaders possibly?
                     using (var shader =
@@ -617,10 +618,10 @@ namespace Avalonia.Skia
                 }
                 case IRadialGradientBrush radialGradient:
                 {
-                    var center = radialGradient.Center.ToPixels(targetSize).ToSKPoint();
-                    var radius = (float)(radialGradient.Radius * targetSize.Width);
+                    var center = position + radialGradient.Center.ToPixels(targetRect.Size).ToSKPoint();
+                    var radius = (float)(radialGradient.Radius * targetRect.Width);
 
-                    var origin = radialGradient.GradientOrigin.ToPixels(targetSize).ToSKPoint();
+                    var origin = position + radialGradient.GradientOrigin.ToPixels(targetRect.Size).ToSKPoint();
 
                     if (origin.Equals(center))
                     {
@@ -665,7 +666,7 @@ namespace Avalonia.Skia
                 }
                 case IConicGradientBrush conicGradient:
                 {
-                    var center = conicGradient.Center.ToPixels(targetSize).ToSKPoint();
+                    var center = position + conicGradient.Center.ToPixels(targetRect.Size).ToSKPoint();
 
                     // Skia's default is that angle 0 is from the right hand side of the center point
                     // but we are matching CSS where the vertical point above the center is 0.
@@ -867,10 +868,10 @@ namespace Avalonia.Skia
         /// </summary>
         /// <param name="paint">The paint to wrap.</param>
         /// <param name="brush">Source brush.</param>
-        /// <param name="targetSize">Target size.</param>
+        /// <param name="targetRect">Target rect.</param>
         /// <param name="disposePaint">Optional dispose of the supplied paint.</param>
         /// <returns>Paint wrapper for given brush.</returns>
-        internal PaintWrapper CreatePaint(SKPaint paint, IBrush brush, Size targetSize, bool disposePaint = false)
+        internal PaintWrapper CreatePaint(SKPaint paint, IBrush brush, Rect targetRect, bool disposePaint = false)
         {
             var paintWrapper = new PaintWrapper(paint, disposePaint);
 
@@ -889,7 +890,7 @@ namespace Avalonia.Skia
 
             if (brush is IGradientBrush gradient)
             {
-                ConfigureGradientBrush(ref paintWrapper, targetSize, gradient);
+                ConfigureGradientBrush(ref paintWrapper, targetRect, gradient);
 
                 return paintWrapper;
             }
@@ -909,7 +910,7 @@ namespace Avalonia.Skia
 
             if (tileBrush != null && tileBrushImage != null)
             {
-                ConfigureTileBrush(ref paintWrapper, targetSize, tileBrush, tileBrushImage);
+                ConfigureTileBrush(ref paintWrapper, targetRect.Size, tileBrush, tileBrushImage);
             }
             else
             {
@@ -924,10 +925,10 @@ namespace Avalonia.Skia
         /// </summary>
         /// <param name="paint">The paint to wrap.</param>
         /// <param name="pen">Source pen.</param>
-        /// <param name="targetSize">Target size.</param>
+        /// <param name="targetRect">Target rect.</param>
         /// <param name="disposePaint">Optional dispose of the supplied paint.</param>
         /// <returns></returns>
-        private PaintWrapper CreatePaint(SKPaint paint, IPen pen, Size targetSize, bool disposePaint = false)
+        private PaintWrapper CreatePaint(SKPaint paint, IPen pen, Rect targetRect, bool disposePaint = false)
         {
             // In Skia 0 thickness means - use hairline rendering
             // and for us it means - there is nothing rendered.
@@ -936,7 +937,7 @@ namespace Avalonia.Skia
                 return default;
             }
 
-            var rv = CreatePaint(paint, pen.Brush, targetSize, disposePaint);
+            var rv = CreatePaint(paint, pen.Brush, targetRect, disposePaint);
 
             paint.IsStroke = true;
             paint.StrokeWidth = (float) pen.Thickness;

+ 2 - 2
src/Skia/Avalonia.Skia/FormattedTextImpl.cs

@@ -278,9 +278,9 @@ namespace Avalonia.Skia
 
                                 if (fb != null)
                                 {
-                                    //TODO: figure out how to get the brush size
+                                    //TODO: figure out how to get the brush rect
                                     currentWrapper = context.CreatePaint(new SKPaint { IsAntialias = true }, fb,
-                                        new Size());
+                                        default);
                                 }
                                 else
                                 {

+ 2 - 2
src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs

@@ -34,10 +34,10 @@ namespace Avalonia.Direct2D1.Media
         {
             var wrapper = clientDrawingEffect as BrushWrapper;
 
-            // TODO: Work out how to get the size below rather than passing new Size().
+            // TODO: Work out how to get the rect below rather than passing default.
             var brush = (wrapper == null) ?
                 _foreground :
-                _context.CreateBrush(wrapper.Brush, new Size()).PlatformBrush;
+                _context.CreateBrush(wrapper.Brush, default).PlatformBrush;
 
             _renderTarget.DrawGlyphRun(
                 new RawVector2 { X = baselineOriginX, Y = baselineOriginY },

+ 15 - 17
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@@ -192,7 +192,7 @@ namespace Avalonia.Direct2D1.Media
         {
             using (var d2dSource = ((BitmapImpl)source.Item).GetDirect2DBitmap(_deviceContext))
             using (var sourceBrush = new BitmapBrush(_deviceContext, d2dSource.Value))
-            using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect.Size))
+            using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect))
             using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(Direct2D1Platform.Direct2D1Factory, destRect.ToDirect2D()))
             {
                 if (d2dOpacityMask.PlatformBrush != null)
@@ -217,9 +217,7 @@ namespace Avalonia.Direct2D1.Media
         {
             if (pen != null)
             {
-                var size = new Rect(p1, p2).Size;
-
-                using (var d2dBrush = CreateBrush(pen.Brush, size))
+                using (var d2dBrush = CreateBrush(pen.Brush, new Rect(p1, p2).Normalize()))
                 using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext))
                 {
                     if (d2dBrush.PlatformBrush != null)
@@ -245,7 +243,7 @@ namespace Avalonia.Direct2D1.Media
         {
             if (brush != null)
             {
-                using (var d2dBrush = CreateBrush(brush, geometry.Bounds.Size))
+                using (var d2dBrush = CreateBrush(brush, geometry.Bounds))
                 {
                     if (d2dBrush.PlatformBrush != null)
                     {
@@ -257,7 +255,7 @@ namespace Avalonia.Direct2D1.Media
 
             if (pen != null)
             {
-                using (var d2dBrush = CreateBrush(pen.Brush, geometry.GetRenderBounds(pen).Size))
+                using (var d2dBrush = CreateBrush(pen.Brush, geometry.GetRenderBounds(pen)))
                 using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext))
                 {
                     if (d2dBrush.PlatformBrush != null)
@@ -282,7 +280,7 @@ namespace Avalonia.Direct2D1.Media
 
             if (brush != null)
             {
-                using (var b = CreateBrush(brush, rect.Size))
+                using (var b = CreateBrush(brush, rect))
                 {
                     if (b.PlatformBrush != null)
                     {
@@ -311,7 +309,7 @@ namespace Avalonia.Direct2D1.Media
 
             if (pen?.Brush != null)
             {
-                using (var wrapper = CreateBrush(pen.Brush, rect.Size))
+                using (var wrapper = CreateBrush(pen.Brush, rect))
                 using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext))
                 {
                     if (wrapper.PlatformBrush != null)
@@ -349,7 +347,7 @@ namespace Avalonia.Direct2D1.Media
             {
                 var impl = (FormattedTextImpl)text;
 
-                using (var brush = CreateBrush(foreground, impl.Bounds.Size))
+                using (var brush = CreateBrush(foreground, impl.Bounds))
                 using (var renderer = new AvaloniaTextRenderer(this, _deviceContext, brush.PlatformBrush))
                 {
                     if (brush.PlatformBrush != null)
@@ -367,7 +365,7 @@ namespace Avalonia.Direct2D1.Media
         /// <param name="glyphRun">The glyph run.</param>
         public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
         {
-            using (var brush = CreateBrush(foreground, glyphRun.Size))
+            using (var brush = CreateBrush(foreground, new Rect(glyphRun.Size)))
             {
                 var glyphRunImpl = (GlyphRunImpl)glyphRun.GlyphRunImpl;
 
@@ -458,9 +456,9 @@ namespace Avalonia.Direct2D1.Media
         /// Creates a Direct2D brush wrapper for a Avalonia brush.
         /// </summary>
         /// <param name="brush">The avalonia brush.</param>
-        /// <param name="destinationSize">The size of the brush's target area.</param>
+        /// <param name="destinationRect">The brush's target area.</param>
         /// <returns>The Direct2D brush wrapper.</returns>
-        public BrushImpl CreateBrush(IBrush brush, Size destinationSize)
+        public BrushImpl CreateBrush(IBrush brush, Rect destinationRect)
         {
             var solidColorBrush = brush as ISolidColorBrush;
             var linearGradientBrush = brush as ILinearGradientBrush;
@@ -475,11 +473,11 @@ namespace Avalonia.Direct2D1.Media
             }
             else if (linearGradientBrush != null)
             {
-                return new LinearGradientBrushImpl(linearGradientBrush, _deviceContext, destinationSize);
+                return new LinearGradientBrushImpl(linearGradientBrush, _deviceContext, destinationRect);
             }
             else if (radialGradientBrush != null)
             {
-                return new RadialGradientBrushImpl(radialGradientBrush, _deviceContext, destinationSize);
+                return new RadialGradientBrushImpl(radialGradientBrush, _deviceContext, destinationRect);
             }
             else if (conicGradientBrush != null)
             {
@@ -492,7 +490,7 @@ namespace Avalonia.Direct2D1.Media
                     imageBrush,
                     _deviceContext,
                     (BitmapImpl)imageBrush.Source.PlatformImpl.Item,
-                    destinationSize);
+                    destinationRect.Size);
             }
             else if (visualBrush != null)
             {
@@ -523,7 +521,7 @@ namespace Avalonia.Direct2D1.Media
                                 visualBrush,
                                 _deviceContext,
                                 new D2DBitmapImpl(intermediate.Bitmap),
-                                destinationSize);
+                                destinationRect.Size);
                         }
                     }
                 }
@@ -574,7 +572,7 @@ namespace Avalonia.Direct2D1.Media
                 ContentBounds = PrimitiveExtensions.RectangleInfinite,
                 MaskTransform = PrimitiveExtensions.Matrix3x2Identity,
                 Opacity = 1,
-                OpacityBrush = CreateBrush(mask, bounds.Size).PlatformBrush
+                OpacityBrush = CreateBrush(mask, bounds).PlatformBrush
             };
             var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_deviceContext);
             _deviceContext.PushLayer(ref parameters, layer);

+ 4 - 3
src/Windows/Avalonia.Direct2D1/Media/LinearGradientBrushImpl.cs

@@ -8,7 +8,7 @@ namespace Avalonia.Direct2D1.Media
         public LinearGradientBrushImpl(
             ILinearGradientBrush brush,
             SharpDX.Direct2D1.RenderTarget target,
-            Size destinationSize)
+            Rect destinationRect)
         {
             if (brush.GradientStops.Count == 0)
             {
@@ -21,8 +21,9 @@ namespace Avalonia.Direct2D1.Media
                 Position = (float)s.Offset
             }).ToArray();
 
-            var startPoint = brush.StartPoint.ToPixels(destinationSize);
-            var endPoint = brush.EndPoint.ToPixels(destinationSize);
+            var position = destinationRect.Position;
+            var startPoint = position + brush.StartPoint.ToPixels(destinationRect.Size);
+            var endPoint = position + brush.EndPoint.ToPixels(destinationRect.Size);
 
             using (var stops = new SharpDX.Direct2D1.GradientStopCollection(
                 target,

+ 6 - 5
src/Windows/Avalonia.Direct2D1/Media/RadialGradientBrushImpl.cs

@@ -8,7 +8,7 @@ namespace Avalonia.Direct2D1.Media
         public RadialGradientBrushImpl(
             IRadialGradientBrush brush,
             SharpDX.Direct2D1.RenderTarget target,
-            Size destinationSize)
+            Rect destinationRect)
         {
             if (brush.GradientStops.Count == 0)
             {
@@ -21,12 +21,13 @@ namespace Avalonia.Direct2D1.Media
                 Position = (float)s.Offset
             }).ToArray();
 
-            var centerPoint = brush.Center.ToPixels(destinationSize);
-            var gradientOrigin = brush.GradientOrigin.ToPixels(destinationSize) - centerPoint;
+            var position = destinationRect.Position;
+            var centerPoint = position + brush.Center.ToPixels(destinationRect.Size);
+            var gradientOrigin = position + brush.GradientOrigin.ToPixels(destinationRect.Size) - centerPoint;
             
             // Note: Direct2D supports RadiusX and RadiusY but Cairo backend supports only Radius property
-            var radiusX = brush.Radius * destinationSize.Width;
-            var radiusY = brush.Radius * destinationSize.Height;
+            var radiusX = brush.Radius * destinationRect.Width;
+            var radiusY = brush.Radius * destinationRect.Height;
 
             using (var stops = new SharpDX.Direct2D1.GradientStopCollection(
                 target,

+ 40 - 0
tests/Avalonia.RenderTests/Media/ConicGradientBrushTests.cs

@@ -1,5 +1,6 @@
 using Avalonia.Controls;
 using Avalonia.Media;
+using System;
 using System.Threading.Tasks;
 using Xunit;
 
@@ -174,5 +175,44 @@ namespace Avalonia.Direct2D1.RenderTests.Media
             await RenderToFile(target);
             CompareImages();
         }
+
+        [Fact]
+        public async Task ConicGradientBrush_DrawingContext()
+        {
+            var brush = new ConicGradientBrush
+            {
+                GradientStops =
+                {
+                    new GradientStop { Color = Colors.Red, Offset = 0 },
+                    new GradientStop { Color = Colors.Yellow, Offset = 0.1667 },
+                    new GradientStop { Color = Colors.Lime, Offset = 0.3333 },
+                    new GradientStop { Color = Colors.Aqua, Offset = 0.5000 },
+                    new GradientStop { Color = Colors.Blue, Offset = 0.6667 },
+                    new GradientStop { Color = Colors.Magenta, Offset = 0.8333 },
+                    new GradientStop { Color = Colors.Red, Offset = 1 },
+                }
+            };
+
+            Decorator target = new Decorator
+            {
+                Width = 200,
+                Height = 200,
+                Child = new DrawnControl(c =>
+                {
+                    c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100));
+                    c.DrawRectangle(brush, null, new Rect(100, 100, 100, 100));
+                }),
+            };
+
+            await RenderToFile(target);
+            CompareImages();
+        }
+
+        private class DrawnControl : Control
+        {
+            private readonly Action<DrawingContext> _render;
+            public DrawnControl(Action<DrawingContext> render) => _render = render;
+            public override void Render(DrawingContext context) => _render(context);
+        }
     }
 }

+ 36 - 0
tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs

@@ -72,5 +72,41 @@ namespace Avalonia.Direct2D1.RenderTests.Media
             await RenderToFile(target);
             CompareImages();
         }
+
+        [Fact]
+        public async Task LinearGradientBrush_DrawingContext()
+        {
+            var brush = new LinearGradientBrush
+            {
+                StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
+                EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative),
+                GradientStops =
+                        {
+                            new GradientStop { Color = Colors.Red, Offset = 0 },
+                            new GradientStop { Color = Colors.Blue, Offset = 1 }
+                        }
+            };
+
+            Decorator target = new Decorator
+            {
+                Width = 200,
+                Height = 200,
+                Child = new DrawnControl(c =>
+                {
+                    c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100));
+                    c.DrawRectangle(brush, null, new Rect(100, 100, 100, 100));
+                }),
+            };
+
+            await RenderToFile(target);
+            CompareImages();
+        }
+
+        private class DrawnControl : Control
+        {
+            private readonly Action<DrawingContext> _render;
+            public DrawnControl(Action<DrawingContext> render) => _render = render;
+            public override void Render(DrawingContext context) => _render(context);
+        }
     }
 }

+ 34 - 0
tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs

@@ -165,5 +165,39 @@ namespace Avalonia.Direct2D1.RenderTests.Media
             await RenderToFile(target);
             CompareImages();
         }
+
+        [Fact]
+        public async Task RadialGradientBrush_DrawingContext()
+        {
+            var brush = new RadialGradientBrush
+            {
+                GradientStops =
+                {
+                    new GradientStop { Color = Colors.Red, Offset = 0 },
+                    new GradientStop { Color = Colors.Blue, Offset = 1 }
+                }
+            };
+
+            Decorator target = new Decorator
+            {
+                Width = 200,
+                Height = 200,
+                Child = new DrawnControl(c =>
+                {
+                    c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100));
+                    c.DrawRectangle(brush, null, new Rect(100, 100, 100, 100));
+                }),
+            };
+
+            await RenderToFile(target);
+            CompareImages();
+        }
+
+        private class DrawnControl : Control
+        {
+            private readonly Action<DrawingContext> _render;
+            public DrawnControl(Action<DrawingContext> render) => _render = render;
+            public override void Render(DrawingContext context) => _render(context);
+        }
     }
 }

BIN
tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_DrawingContext.expected.png


BIN
tests/TestFiles/Direct2D1/Media/LinearGradientBrush/LinearGradientBrush_DrawingContext.expected.png


BIN
tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_DrawingContext.expected.png


BIN
tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_DrawingContext.expected.png


BIN
tests/TestFiles/Skia/Media/LinearGradientBrush/LinearGradientBrush_DrawingContext.expected.png


BIN
tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_DrawingContext.expected.png