瀏覽代碼

Add Skia support for Conic Gradients. Add a place holder implementation for Direct2D using SolidColorBrushImpl.

[email protected] 5 年之前
父節點
當前提交
8bb8f3c79f
共有 19 個文件被更改,包括 339 次插入0 次删除
  1. 55 0
      src/Avalonia.Visuals/Media/ConicGradientBrush.cs
  2. 19 0
      src/Avalonia.Visuals/Media/IConicGradientBrush.cs
  3. 47 0
      src/Avalonia.Visuals/Media/Immutable/ImmutableConicGradientBrush.cs
  4. 17 0
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  5. 6 0
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  6. 17 0
      src/Windows/Avalonia.Direct2D1/Media/SolidColorBrushImpl.cs
  7. 178 0
      tests/Avalonia.RenderTests/Media/ConicGradientBrushTests.cs
  8. 二進制
      tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue.expected.png
  9. 二進制
      tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Center.expected.png
  10. 二進制
      tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Center_and_Rotation.expected.png
  11. 二進制
      tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Rotation.expected.png
  12. 二進制
      tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_SoftEdge.expected.png
  13. 二進制
      tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_Umbrella.expected.png
  14. 二進制
      tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue.expected.png
  15. 二進制
      tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Center.expected.png
  16. 二進制
      tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Center_and_Rotation.expected.png
  17. 二進制
      tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Rotation.expected.png
  18. 二進制
      tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_SoftEdge.expected.png
  19. 二進制
      tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_Umbrella.expected.png

+ 55 - 0
src/Avalonia.Visuals/Media/ConicGradientBrush.cs

@@ -0,0 +1,55 @@
+using Avalonia.Media.Immutable;
+
+namespace Avalonia.Media
+{
+    /// <summary>
+    /// Paints an area with a swept circular gradient.
+    /// </summary>
+    public sealed class ConicGradientBrush : GradientBrush, IConicGradientBrush
+    {
+        /// <summary>
+        /// Defines the <see cref="Center"/> property.
+        /// </summary>
+        public static readonly StyledProperty<RelativePoint> CenterProperty =
+            AvaloniaProperty.Register<RadialGradientBrush, RelativePoint>(
+                nameof(Center),
+                RelativePoint.Center);
+
+        /// <summary>
+        /// Defines the <see cref="Angle"/> property.
+        /// </summary>
+        public static readonly StyledProperty<double> AngleProperty =
+            AvaloniaProperty.Register<RadialGradientBrush, double>(
+                nameof(Angle),
+                0);
+        
+        static ConicGradientBrush()
+        {
+            AffectsRender<ConicGradientBrush>(CenterProperty, AngleProperty);
+        }
+
+        /// <summary>
+        /// Gets or sets the center point of the gradient.
+        /// </summary>
+        public RelativePoint Center
+        {
+            get { return GetValue(CenterProperty); }
+            set { SetValue(CenterProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the angle of the start and end of the sweep, measured from above the center point.
+        /// </summary>
+        public double Angle
+        {
+            get { return GetValue(AngleProperty); }
+            set { SetValue(AngleProperty, value); }
+        }
+
+        /// <inheritdoc/>
+        public override IBrush ToImmutable()
+        {
+            return new ImmutableConicGradientBrush(this);
+        }
+    }
+}

+ 19 - 0
src/Avalonia.Visuals/Media/IConicGradientBrush.cs

@@ -0,0 +1,19 @@
+namespace Avalonia.Media
+{
+    /// <summary>
+    /// Paints an area with a conic gradient.
+    /// </summary>
+    public interface IConicGradientBrush : IGradientBrush
+    {
+        /// <summary>
+        /// Gets the center point for the gradient.
+        /// </summary>
+        RelativePoint Center { get; }
+
+        /// <summary>
+        /// Gets the starting angle for the gradient in degrees, measured from
+        /// the point above the center point.
+        /// </summary>
+        double Angle { get; }
+    }
+}

+ 47 - 0
src/Avalonia.Visuals/Media/Immutable/ImmutableConicGradientBrush.cs

@@ -0,0 +1,47 @@
+using System.Collections.Generic;
+
+namespace Avalonia.Media.Immutable
+{
+    /// <summary>
+    /// A brush that draws with a sweep gradient.
+    /// </summary>
+    public class ImmutableConicGradientBrush : ImmutableGradientBrush, IConicGradientBrush
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ImmutableConicGradientBrush"/> class.
+        /// </summary>
+        /// <param name="gradientStops">The gradient stops.</param>
+        /// <param name="opacity">The opacity of the brush.</param>
+        /// <param name="spreadMethod">The spread method.</param>
+        /// <param name="center">The center point for the gradient.</param>
+        /// <param name="angle">The starting angle for the gradient.</param>
+        public ImmutableConicGradientBrush(
+            IReadOnlyList<ImmutableGradientStop> gradientStops,
+            double opacity = 1,
+            GradientSpreadMethod spreadMethod = GradientSpreadMethod.Pad,
+            RelativePoint? center = null,
+            double angle = 0)
+            : base(gradientStops, opacity, spreadMethod)
+        {
+            Center = center ?? RelativePoint.Center;
+            Angle = angle;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ImmutableConicGradientBrush"/> class.
+        /// </summary>
+        /// <param name="source">The brush from which this brush's properties should be copied.</param>
+        public ImmutableConicGradientBrush(ConicGradientBrush source)
+            : base(source)
+        {
+            Center = source.Center;
+            Angle = source.Angle;
+        }
+
+        /// <inheritdoc/>
+        public RelativePoint Center { get; }
+
+        /// <inheritdoc/>
+        public double Angle { get; }
+    }
+}

+ 17 - 0
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@@ -624,6 +624,23 @@ namespace Avalonia.Skia
                         }
                     }
 
+                    break;
+                }
+                case IConicGradientBrush conicGradient:
+                {
+                    var center = conicGradient.Center.ToPixels(targetSize).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.
+                    var angle = (float)(conicGradient.Angle - 90);
+                    var rotation = SKMatrix.CreateRotationDegrees(angle, center.X, center.Y);
+
+                    using (var shader = 
+                        SKShader.CreateSweepGradient(center, stopColors, stopOffsets, rotation))
+                    {
+                        paintWrapper.Paint.Shader = shader;
+                    }
+
                     break;
                 }
             }

+ 6 - 0
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@@ -424,6 +424,7 @@ namespace Avalonia.Direct2D1.Media
             var solidColorBrush = brush as ISolidColorBrush;
             var linearGradientBrush = brush as ILinearGradientBrush;
             var radialGradientBrush = brush as IRadialGradientBrush;
+            var conicGradientBrush = brush as IConicGradientBrush;
             var imageBrush = brush as IImageBrush;
             var visualBrush = brush as IVisualBrush;
 
@@ -439,6 +440,11 @@ namespace Avalonia.Direct2D1.Media
             {
                 return new RadialGradientBrushImpl(radialGradientBrush, _deviceContext, destinationSize);
             }
+            else if (conicGradientBrush != null)
+            {
+                // there is no Direct2D implementation of Conic Gradients so use Radial as a stand-in
+                return new SolidColorBrushImpl(conicGradientBrush, _deviceContext);
+            }
             else if (imageBrush?.Source != null)
             {
                 return new ImageBrushImpl(

+ 17 - 0
src/Windows/Avalonia.Direct2D1/Media/SolidColorBrushImpl.cs

@@ -16,5 +16,22 @@ namespace Avalonia.Direct2D1.Media
                 }
             );
         }
+
+        /// <summary>
+        /// Direct2D has no ConicGradient implementation so fall back to a solid colour brush based on 
+        /// the first gradient stop.
+        /// </summary>
+        public SolidColorBrushImpl(IConicGradientBrush brush, SharpDX.Direct2D1.DeviceContext target)
+        {
+            PlatformBrush = new SharpDX.Direct2D1.SolidColorBrush(
+                target,
+                brush?.GradientStops[0].Color.ToDirect2D() ?? new SharpDX.Mathematics.Interop.RawColor4(),
+                new SharpDX.Direct2D1.BrushProperties
+                {
+                    Opacity = brush != null ? (float)brush.Opacity : 1.0f,
+                    Transform = target.Transform
+                }
+            );
+        }
     }
 }

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

@@ -0,0 +1,178 @@
+using Avalonia.Controls;
+using Avalonia.Media;
+using System.Threading.Tasks;
+using Xunit;
+
+#if AVALONIA_SKIA
+namespace Avalonia.Skia.RenderTests
+#else
+namespace Avalonia.Direct2D1.RenderTests.Media
+#endif
+{
+    public class ConicGradientBrushTests : TestBase
+    {
+        public ConicGradientBrushTests() : base(@"Media\ConicGradientBrush")
+        {
+        }
+
+        [Fact]
+        public async Task ConicGradientBrush_RedBlue()
+        {
+            Decorator target = new Decorator
+            {
+                Padding = new Thickness(8),
+                Width = 200,
+                Height = 200,
+                Child = new Border
+                {
+                    Background = new ConicGradientBrush
+                    {
+                        GradientStops =
+                        {
+                            new GradientStop { Color = Colors.Red, Offset = 0 },
+                            new GradientStop { Color = Colors.Blue, Offset = 1 }
+                        }
+                    }
+                }
+            };
+
+            await RenderToFile(target);
+            CompareImages();
+        }
+
+        [Fact]
+        public async Task ConicGradientBrush_RedBlue_Rotation()
+        {
+            Decorator target = new Decorator
+            {
+                Padding = new Thickness(8),
+                Width = 200,
+                Height = 200,
+                Child = new Border
+                {
+                    Background = new ConicGradientBrush
+                    {
+                        GradientStops =
+                        {
+                            new GradientStop { Color = Colors.Red, Offset = 0 },
+                            new GradientStop { Color = Colors.Blue, Offset = 1 }
+                        },
+                        Angle = 90
+                    }
+                }
+            };
+
+            await RenderToFile(target);
+            CompareImages();
+        }
+
+        [Fact]
+        public async Task ConicGradientBrush_RedBlue_Center()
+        {
+            Decorator target = new Decorator
+            {
+                Padding = new Thickness(8),
+                Width = 200,
+                Height = 200,
+                Child = new Border
+                {
+                    Background = new ConicGradientBrush
+                    {
+                        GradientStops =
+                        {
+                            new GradientStop { Color = Colors.Red, Offset = 0 },
+                            new GradientStop { Color = Colors.Blue, Offset = 1 }
+                        },
+                        Center = new RelativePoint(0.25, 0.25, RelativeUnit.Relative)
+                    }
+                }
+            };
+
+            await RenderToFile(target);
+            CompareImages();
+        }
+
+        [Fact]
+        public async Task ConicGradientBrush_RedBlue_Center_and_Rotation()
+        {
+            Decorator target = new Decorator
+            {
+                Padding = new Thickness(8),
+                Width = 200,
+                Height = 200,
+                Child = new Border
+                {
+                    Background = new ConicGradientBrush
+                    {
+                        GradientStops =
+                        {
+                            new GradientStop { Color = Colors.Red, Offset = 0 },
+                            new GradientStop { Color = Colors.Blue, Offset = 1 }
+                        },
+                        Center = new RelativePoint(0.25, 0.25, RelativeUnit.Relative),
+                        Angle = 90
+                    }
+                }
+            };
+
+            await RenderToFile(target);
+            CompareImages();
+        }
+
+        [Fact]
+        public async Task ConicGradientBrush_RedBlue_SoftEdge()
+        {
+            Decorator target = new Decorator
+            {
+                Padding = new Thickness(8),
+                Width = 200,
+                Height = 200,
+                Child = new Border
+                {
+                    Background = new ConicGradientBrush
+                    {
+                        GradientStops =
+                        {
+                            new GradientStop { Color = Colors.Red, Offset = 0 },
+                            new GradientStop { Color = Colors.Blue, Offset = 0.5 },
+                            new GradientStop { Color = Colors.Red, Offset = 1 },
+                        }
+                    }
+                }
+            };
+
+            await RenderToFile(target);
+            CompareImages();
+        }
+
+        [Fact]
+        public async Task ConicGradientBrush_Umbrella()
+        {
+            Decorator target = new Decorator
+            {
+                Padding = new Thickness(8),
+                Width = 200,
+                Height = 200,
+                Child = new Border
+                {
+                    Background = 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 },
+                        }
+                    }
+                }
+            };
+
+            await RenderToFile(target);
+            CompareImages();
+        }
+    }
+}

二進制
tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue.expected.png


二進制
tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Center.expected.png


二進制
tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Center_and_Rotation.expected.png


二進制
tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Rotation.expected.png


二進制
tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_SoftEdge.expected.png


二進制
tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_Umbrella.expected.png


二進制
tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue.expected.png


二進制
tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Center.expected.png


二進制
tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Center_and_Rotation.expected.png


二進制
tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Rotation.expected.png


二進制
tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_SoftEdge.expected.png


二進制
tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_Umbrella.expected.png