From e4eaf729a9c2e1bdc8c5005c4c772cef8c85485e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 6 Feb 2017 19:52:47 +0100 Subject: [PATCH] Make RadialGradientBrush work With immediate renderer. Also added a render test and fixed some problems with D2D implementation. --- src/Avalonia.Visuals/Avalonia.Visuals.csproj | 2 + .../Media/IRadialGradientBrush.cs | 23 +++++++ .../Immutable/ImmutableLinearGradientBrush.cs | 4 +- .../Immutable/ImmutableRadialGradientBrush.cs | 59 ++++++++++++++++++ .../Media/RadialGradientBrush.cs | 14 ++++- .../Media/DrawingContextImpl.cs | 2 +- .../Media/RadialGradientBrushImpl.cs | 10 +-- ...alonia.Cairo.RenderTests.v3.ncrunchproject | 3 + .../Avalonia.RenderTests.projitems | 1 + .../Media/RadialGradientBrushTests.cs | 56 +++++++++++++++++ .../RadialGradientBrush_RedBlue.expected.png | Bin 0 -> 9792 bytes 11 files changed, 163 insertions(+), 11 deletions(-) create mode 100644 src/Avalonia.Visuals/Media/IRadialGradientBrush.cs create mode 100644 src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs create mode 100644 tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs create mode 100644 tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedBlue.expected.png diff --git a/src/Avalonia.Visuals/Avalonia.Visuals.csproj b/src/Avalonia.Visuals/Avalonia.Visuals.csproj index a167cbe753..41e8857e6f 100644 --- a/src/Avalonia.Visuals/Avalonia.Visuals.csproj +++ b/src/Avalonia.Visuals/Avalonia.Visuals.csproj @@ -73,10 +73,12 @@ + + diff --git a/src/Avalonia.Visuals/Media/IRadialGradientBrush.cs b/src/Avalonia.Visuals/Media/IRadialGradientBrush.cs new file mode 100644 index 0000000000..a9d902dabe --- /dev/null +++ b/src/Avalonia.Visuals/Media/IRadialGradientBrush.cs @@ -0,0 +1,23 @@ +namespace Avalonia.Media +{ + /// + /// Paints an area with a radial gradient. + /// + public interface IRadialGradientBrush : IGradientBrush + { + /// + /// Gets the start point for the gradient. + /// + RelativePoint Center { get; } + + /// + /// Gets the location of the two-dimensional focal point that defines the beginning of the gradient. + /// + RelativePoint GradientOrigin { get; } + + /// + /// Gets the horizontal and vertical radius of the outermost circle of the radial gradient. + /// + double Radius { get; } + } +} \ No newline at end of file diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs index 985e535b22..b46ee951f7 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs @@ -12,7 +12,7 @@ namespace Avalonia.Media.Immutable public class ImmutableLinearGradientBrush : ImmutableGradientBrush, ILinearGradientBrush { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The gradient stops. /// The opacity of the brush. @@ -32,7 +32,7 @@ namespace Avalonia.Media.Immutable } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The brush from which this brush's properties should be copied. public ImmutableLinearGradientBrush(ILinearGradientBrush source) diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs new file mode 100644 index 0000000000..cc2c7b3697 --- /dev/null +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; + +namespace Avalonia.Media.Immutable +{ + /// + /// A brush that draws with a radial gradient. + /// + public class ImmutableRadialGradientBrush : ImmutableGradientBrush, IRadialGradientBrush + { + /// + /// Initializes a new instance of the class. + /// + /// The gradient stops. + /// The opacity of the brush. + /// The spread method. + /// The start point for the gradient. + /// + /// The location of the two-dimensional focal point that defines the beginning of the gradient. + /// + /// + /// The horizontal and vertical radius of the outermost circle of the radial gradient. + /// + public ImmutableRadialGradientBrush( + IReadOnlyList gradientStops, + double opacity = 1, + GradientSpreadMethod spreadMethod = GradientSpreadMethod.Pad, + RelativePoint? center = null, + RelativePoint? gradientOrigin = null, + double radius = 0.5) + : base(gradientStops, opacity, spreadMethod) + { + Center = center ?? RelativePoint.Center; + GradientOrigin = gradientOrigin ?? RelativePoint.Center; + Radius = radius; + } + + /// + /// Initializes a new instance of the class. + /// + /// The brush from which this brush's properties should be copied. + public ImmutableRadialGradientBrush(IRadialGradientBrush source) + : base(source) + { + Center = source.Center; + GradientOrigin = source.GradientOrigin; + Radius = source.Radius; + } + + /// + public RelativePoint Center { get; } + + /// + public RelativePoint GradientOrigin { get; } + + /// + public double Radius { get; } + } +} diff --git a/src/Avalonia.Visuals/Media/RadialGradientBrush.cs b/src/Avalonia.Visuals/Media/RadialGradientBrush.cs index 70657abe88..a78cd86afe 100644 --- a/src/Avalonia.Visuals/Media/RadialGradientBrush.cs +++ b/src/Avalonia.Visuals/Media/RadialGradientBrush.cs @@ -1,13 +1,14 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; + namespace Avalonia.Media { /// - /// Paints an area with a radial gradient. A focal point defines the beginning of the gradient, - /// and a circle defines the end point of the gradient. + /// Paints an area with a radial gradient. /// - public sealed class RadialGradientBrush : GradientBrush + public sealed class RadialGradientBrush : GradientBrush, IRadialGradientBrush, IMutableBrush { /// /// Defines the property. @@ -54,10 +55,17 @@ namespace Avalonia.Media /// /// Gets or sets the horizontal and vertical radius of the outermost circle of the radial gradient. /// + // TODO: This appears to always be relative so should use a RelativeSize struct or something. public double Radius { get { return GetValue(RadiusProperty); } set { SetValue(RadiusProperty, value); } } + + /// + IBrush IMutableBrush.ToImmutable() + { + return new Immutable.ImmutableRadialGradientBrush(this); + } } } \ No newline at end of file diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 20b5f54784..623f18ea0c 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -320,7 +320,7 @@ namespace Avalonia.Direct2D1.Media { var solidColorBrush = brush as Avalonia.Media.ISolidColorBrush; var linearGradientBrush = brush as Avalonia.Media.ILinearGradientBrush; - var radialGradientBrush = brush as Avalonia.Media.RadialGradientBrush; + var radialGradientBrush = brush as Avalonia.Media.IRadialGradientBrush; var imageBrush = brush as Avalonia.Media.IImageBrush; var visualBrush = brush as Avalonia.Media.IVisualBrush; diff --git a/src/Windows/Avalonia.Direct2D1/Media/RadialGradientBrushImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/RadialGradientBrushImpl.cs index 72779096ff..be972c0173 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/RadialGradientBrushImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/RadialGradientBrushImpl.cs @@ -8,7 +8,7 @@ namespace Avalonia.Direct2D1.Media public class RadialGradientBrushImpl : BrushImpl { public RadialGradientBrushImpl( - Avalonia.Media.RadialGradientBrush brush, + Avalonia.Media.IRadialGradientBrush brush, SharpDX.Direct2D1.RenderTarget target, Size destinationSize) { @@ -24,11 +24,11 @@ namespace Avalonia.Direct2D1.Media }).ToArray(); var centerPoint = brush.Center.ToPixels(destinationSize); - var GradientOriginOffset = brush.GradientOrigin.ToPixels(destinationSize); + var gradientOrigin = brush.GradientOrigin.ToPixels(destinationSize) - centerPoint; // Note: Direct2D supports RadiusX and RadiusY but Cairo backend supports only Radius property - var radiusX = brush.Radius; - var radiusY = brush.Radius; + var radiusX = brush.Radius * destinationSize.Width; + var radiusY = brush.Radius * destinationSize.Height; using (var stops = new SharpDX.Direct2D1.GradientStopCollection( target, @@ -40,7 +40,7 @@ namespace Avalonia.Direct2D1.Media new SharpDX.Direct2D1.RadialGradientBrushProperties { Center = centerPoint.ToSharpDX(), - GradientOriginOffset = GradientOriginOffset.ToSharpDX(), + GradientOriginOffset = gradientOrigin.ToSharpDX(), RadiusX = (float)radiusX, RadiusY = (float)radiusY }, diff --git a/tests/Avalonia.RenderTests/Avalonia.Cairo.RenderTests.v3.ncrunchproject b/tests/Avalonia.RenderTests/Avalonia.Cairo.RenderTests.v3.ncrunchproject index bc70262c50..5c2560547a 100644 --- a/tests/Avalonia.RenderTests/Avalonia.Cairo.RenderTests.v3.ncrunchproject +++ b/tests/Avalonia.RenderTests/Avalonia.Cairo.RenderTests.v3.ncrunchproject @@ -70,6 +70,9 @@ Avalonia.Cairo.RenderTests.Media.LinearGradientBrushTests.LinearGradientBrush_RedBlue_Horizontal_Fill + + Avalonia.Cairo.RenderTests.Media.RadialGradientBrushTests.RadialGradientBrush_RedBlue + True diff --git a/tests/Avalonia.RenderTests/Avalonia.RenderTests.projitems b/tests/Avalonia.RenderTests/Avalonia.RenderTests.projitems index ad3b182bdf..cf86e80d92 100644 --- a/tests/Avalonia.RenderTests/Avalonia.RenderTests.projitems +++ b/tests/Avalonia.RenderTests/Avalonia.RenderTests.projitems @@ -10,6 +10,7 @@ + diff --git a/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs b/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs new file mode 100644 index 0000000000..af4c17b328 --- /dev/null +++ b/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs @@ -0,0 +1,56 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using Avalonia.Controls; +using Avalonia.Media; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +#if AVALONIA_CAIRO +namespace Avalonia.Cairo.RenderTests.Media +#elif AVALONIA_SKIA +namespace Avalonia.Skia.RenderTests +#else +namespace Avalonia.Direct2D1.RenderTests.Media +#endif +{ + public class RadialGradientBrushTests : TestBase + { + public RadialGradientBrushTests() : base(@"Media\RadialGradientBrush") + { + } + +#if AVALONIA_SKIA_SKIP_FAIL + [Fact(Skip = "FIXME")] +#else + [Fact] +#endif + public async Task RadialGradientBrush_RedBlue() + { + Decorator target = new Decorator + { + Padding = new Thickness(8), + Width = 200, + Height = 200, + Child = new Border + { + Background = new RadialGradientBrush + { + GradientStops = + { + new GradientStop { Color = Colors.Red, Offset = 0 }, + new GradientStop { Color = Colors.Blue, Offset = 1 } + } + } + } + }; + + await RenderToFile(target); + CompareImages(); + } + } +} diff --git a/tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedBlue.expected.png b/tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedBlue.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..fce116d98d208e8165a655c1d3cb2924c6e7be00 GIT binary patch literal 9792 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 literal 0 HcmV?d00001