Browse Source

Merge pull request #4745 from olliholliday/feature/sweepgradientbrush

Add IConicGradientBrush and a drawing implemention for Skia.
pull/4934/head
danwalmsley 6 years ago
committed by GitHub
parent
commit
157ef810fe
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 55
      src/Avalonia.Visuals/Media/ConicGradientBrush.cs
  2. 19
      src/Avalonia.Visuals/Media/IConicGradientBrush.cs
  3. 47
      src/Avalonia.Visuals/Media/Immutable/ImmutableConicGradientBrush.cs
  4. 17
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  5. 6
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  6. 17
      src/Windows/Avalonia.Direct2D1/Media/SolidColorBrushImpl.cs
  7. 178
      tests/Avalonia.RenderTests/Media/ConicGradientBrushTests.cs
  8. BIN
      tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue.expected.png
  9. BIN
      tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Center.expected.png
  10. BIN
      tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Center_and_Rotation.expected.png
  11. BIN
      tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Rotation.expected.png
  12. BIN
      tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_SoftEdge.expected.png
  13. BIN
      tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_Umbrella.expected.png
  14. BIN
      tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue.expected.png
  15. BIN
      tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Center.expected.png
  16. BIN
      tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Center_and_Rotation.expected.png
  17. BIN
      tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Rotation.expected.png
  18. BIN
      tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_SoftEdge.expected.png
  19. BIN
      tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_Umbrella.expected.png

55
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
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
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
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -641,6 +641,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
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@ -423,6 +423,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;
@ -438,6 +439,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
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
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();
}
}
}

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Loading…
Cancel
Save