Skip to content

Commit

Permalink
Gradient builder performance adjustments (#79)
Browse files Browse the repository at this point in the history
* Gradient builder performance adjustments, unit tests fixes
* Fix unit test name
* Update Xamarin.Forms dependency where HSL bug is fixed
  • Loading branch information
mgierlasinski authored Apr 27, 2020
1 parent 74d5420 commit 0914aae
Show file tree
Hide file tree
Showing 11 changed files with 191 additions and 45 deletions.
38 changes: 38 additions & 0 deletions MagicGradients.Tests/GradientBuilderTestCases.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using Xamarin.Forms;
using Xunit;

namespace MagicGradients.Tests
{
public class GradientBuilderTestCases : TheoryData<GradientBuilderTestCase>
{
public GradientBuilderTestCases()
{
Add(new LinearTestCase());
Add(new RadialTestCase());
}
}

public abstract class GradientBuilderTestCase
{
public abstract void AddGradient(GradientBuilder builder);
}

public class LinearTestCase : GradientBuilderTestCase
{
public override void AddGradient(GradientBuilder builder)
{
builder.AddLinearGradient(45);
}
}

public class RadialTestCase : GradientBuilderTestCase
{
public override void AddGradient(GradientBuilder builder)
{
builder.AddRadialGradient(
new Point(0.5, 0.5),
RadialGradientShape.Circle,
RadialGradientSize.ClosestSide);
}
}
}
77 changes: 77 additions & 0 deletions MagicGradients.Tests/GradientBuilderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using FluentAssertions;
using FluentAssertions.Execution;
using Xamarin.Forms;
using Xunit;

namespace MagicGradients.Tests
{
public class GradientBuilderTests
{
[Theory]
[ClassData(typeof(GradientBuilderTestCases))]
public void AddGradient_AndStops_SingleGradientWithStops(GradientBuilderTestCase testCase)
{
// Arrange
var builder = new GradientBuilder();

// Act
testCase.AddGradient(builder);
builder.AddStop(Color.White);
builder.AddStop(Color.Black);

var gradients = builder.Build();

// Assert
using (new AssertionScope())
{
gradients.Should().HaveCount(1);
gradients[0].Stops.Should().HaveCount(2);
}
}

[Theory]
[ClassData(typeof(GradientBuilderTestCases))]
public void AddStops_AndGradient_AndStops_TwoGradientsWithStops(GradientBuilderTestCase testCase)
{
// Arrange
var builder = new GradientBuilder();

// Act
builder.AddStop(Color.Red);
testCase.AddGradient(builder);
builder.AddStop(Color.White);
builder.AddStop(Color.Black);

var gradients = builder.Build();

// Assert
using (new AssertionScope())
{
gradients.Should().HaveCount(2);
gradients[0].Stops.Should().HaveCount(1);
gradients[1].Stops.Should().HaveCount(2);
}
}

[Fact]
public void AddOnlyStops_DefaultGradientWithStops()
{
// Arrange
var builder = new GradientBuilder();

// Act
builder.AddStop(Color.White);
builder.AddStop(Color.Black);

var gradients = builder.Build();

// Assert
using (new AssertionScope())
{
gradients.Should().HaveCount(1);
gradients[0].Should().BeOfType<LinearGradient>();
gradients[0].Stops.Should().HaveCount(2);
}
}
}
}
26 changes: 13 additions & 13 deletions MagicGradients.Tests/GradientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ public void SetupUndefinedOffsets_HasDefinedOffsets_NothingChanged()
// Assert
using (new AssertionScope())
{
gradient.Stops[0].Offset.Should().Be(0.1f);
gradient.Stops[1].Offset.Should().Be(0.2f);
gradient.Stops[0].RenderOffset.Should().Be(0.1f);
gradient.Stops[1].RenderOffset.Should().Be(0.2f);
}
}

Expand All @@ -50,9 +50,9 @@ public void SetupUndefinedOffsets_HasUndefinedOffsets_AutomaticallySetUp()
// Assert
using (new AssertionScope())
{
gradient.Stops[0].Offset.Should().Be(0f);
gradient.Stops[1].Offset.Should().Be(0.5f);
gradient.Stops[2].Offset.Should().Be(1f);
gradient.Stops[0].RenderOffset.Should().Be(0f);
gradient.Stops[1].RenderOffset.Should().Be(0.5f);
gradient.Stops[2].RenderOffset.Should().Be(1f);
}
}

Expand Down Expand Up @@ -81,14 +81,14 @@ public void SetupUndefinedOffsets_HasMixedOffsets_OnlySetUpUndefined()
// Assert
using (new AssertionScope())
{
gradient.Stops[0].Offset.Should().Be(0f);
gradient.Stops[1].Offset.Should().BeInRange(0.19f, 0.21f);
gradient.Stops[2].Offset.Should().BeInRange(0.39f, 0.41f);
gradient.Stops[3].Offset.Should().Be(0.6f);
gradient.Stops[4].Offset.Should().BeInRange(0.69f, 0.71f);
gradient.Stops[5].Offset.Should().BeInRange(0.79f, 0.81f);
gradient.Stops[6].Offset.Should().Be(0.9f);
gradient.Stops[7].Offset.Should().Be(1f);
gradient.Stops[0].RenderOffset.Should().Be(0f);
gradient.Stops[1].RenderOffset.Should().BeInRange(0.19f, 0.21f);
gradient.Stops[2].RenderOffset.Should().BeInRange(0.39f, 0.41f);
gradient.Stops[3].RenderOffset.Should().Be(0.6f);
gradient.Stops[4].RenderOffset.Should().BeInRange(0.69f, 0.71f);
gradient.Stops[5].RenderOffset.Should().BeInRange(0.79f, 0.81f);
gradient.Stops[6].RenderOffset.Should().Be(0.9f);
gradient.Stops[7].RenderOffset.Should().Be(1f);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions MagicGradients.Tests/Parser/CssGradientParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public void ParseCss_SimpleGradients_CorrectlyParsed(string css, LinearGradient

// Assert
gradients.Should().HaveCount(1);
gradients[0].Should().BeEquivalentTo(expected);
gradients[0].Should().BeEquivalentTo(expected, options => options.IgnoringCyclicReferences());
}

[Theory]
Expand All @@ -58,7 +58,7 @@ public void ParseCss_GradientsWithoutOffsets_AutomaticallyAssignedOffsets(string

// Assert
gradients.Should().HaveCount(1);
gradients[0].Should().BeEquivalentTo(expected);
gradients[0].Should().BeEquivalentTo(expected, options => options.IgnoringCyclicReferences());
}

[Fact]
Expand Down
32 changes: 18 additions & 14 deletions MagicGradients.Tests/Parser/CssGradientParserTestsData.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Xamarin.Forms;
using static MagicGradients.Parser.CssHelpers;
Expand All @@ -10,15 +9,20 @@ public class CssGradientParserTestData
{
public static string ComplexGradientsCss = "linear-gradient(242deg, rgba(195, 195, 195, 0.02) 0%, rgba(195, 195, 195, 0.02) 16.667%,rgba(91, 91, 91, 0.02) 16.667%, rgba(91, 91, 91, 0.02) 33.334%,rgba(230, 230, 230, 0.02) 33.334%, rgba(230, 230, 230, 0.02) 50.001000000000005%,rgba(18, 18, 18, 0.02) 50.001%, rgba(18, 18, 18, 0.02) 66.668%,rgba(163, 163, 163, 0.02) 66.668%, rgba(163, 163, 163, 0.02) 83.33500000000001%,rgba(140, 140, 140, 0.02) 83.335%, rgba(140, 140, 140, 0.02) 100.002%),linear-gradient(152deg, rgba(151, 151, 151, 0.02) 0%, rgba(151, 151, 151, 0.02) 16.667%,rgba(11, 11, 11, 0.02) 16.667%, rgba(11, 11, 11, 0.02) 33.334%,rgba(162, 162, 162, 0.02) 33.334%, rgba(162, 162, 162, 0.02) 50.001000000000005%,rgba(171, 171, 171, 0.02) 50.001%, rgba(171, 171, 171, 0.02) 66.668%,rgba(119, 119, 119, 0.02) 66.668%, rgba(119, 119, 119, 0.02) 83.33500000000001%,rgba(106, 106, 106, 0.02) 83.335%, rgba(106, 106, 106, 0.02) 100.002%),linear-gradient(11deg, rgba(245, 245, 245, 0.01) 0%, rgba(245, 245, 245, 0.01) 16.667%,rgba(23, 23, 23, 0.01) 16.667%, rgba(23, 23, 23, 0.01) 33.334%,rgba(96, 96, 96, 0.01) 33.334%, rgba(96, 96, 96, 0.01) 50.001000000000005%,rgba(140, 140, 140, 0.01) 50.001%, rgba(140, 140, 140, 0.01) 66.668%,rgba(120, 120, 120, 0.01) 66.668%, rgba(120, 120, 120, 0.01) 83.33500000000001%,rgba(48, 48, 48, 0.01) 83.335%, rgba(48, 48, 48, 0.01) 100.002%),linear-gradient(27deg, rgba(106, 106, 106, 0.03) 0%, rgba(106, 106, 106, 0.03) 14.286%,rgba(203, 203, 203, 0.03) 14.286%, rgba(203, 203, 203, 0.03) 28.572%,rgba(54, 54, 54, 0.03) 28.572%, rgba(54, 54, 54, 0.03) 42.858%,rgba(75, 75, 75, 0.03) 42.858%, rgba(75, 75, 75, 0.03) 57.144%,rgba(216, 216, 216, 0.03) 57.144%, rgba(216, 216, 216, 0.03) 71.42999999999999%,rgba(39, 39, 39, 0.03) 71.43%, rgba(39, 39, 39, 0.03) 85.71600000000001%,rgba(246, 246, 246, 0.03) 85.716%, rgba(246, 246, 246, 0.03) 100.002%),linear-gradient(317deg, rgba(215, 215, 215, 0.01) 0%, rgba(215, 215, 215, 0.01) 16.667%,rgba(72, 72, 72, 0.01) 16.667%, rgba(72, 72, 72, 0.01) 33.334%,rgba(253, 253, 253, 0.01) 33.334%, rgba(253, 253, 253, 0.01) 50.001000000000005%,rgba(4, 4, 4, 0.01) 50.001%, rgba(4, 4, 4, 0.01) 66.668%,rgba(183, 183, 183, 0.01) 66.668%, rgba(183, 183, 183, 0.01) 83.33500000000001%,rgba(17, 17, 17, 0.01) 83.335%, rgba(17, 17, 17, 0.01) 100.002%),linear-gradient(128deg, rgba(119, 119, 119, 0.03) 0%, rgba(119, 119, 119, 0.03) 12.5%,rgba(91, 91, 91, 0.03) 12.5%, rgba(91, 91, 91, 0.03) 25%,rgba(45, 45, 45, 0.03) 25%, rgba(45, 45, 45, 0.03) 37.5%,rgba(182, 182, 182, 0.03) 37.5%, rgba(182, 182, 182, 0.03) 50%,rgba(243, 243, 243, 0.03) 50%, rgba(243, 243, 243, 0.03) 62.5%,rgba(162, 162, 162, 0.03) 62.5%, rgba(162, 162, 162, 0.03) 75%,rgba(190, 190, 190, 0.03) 75%, rgba(190, 190, 190, 0.03) 87.5%,rgba(148, 148, 148, 0.03) 87.5%, rgba(148, 148, 148, 0.03) 100%),linear-gradient(90deg, rgb(185, 139, 80),rgb(176, 26, 6))";

private static GradientElements<GradientStop> CreateStops(int count)
{
return new GradientElements<GradientStop>(Enumerable.Repeat(new GradientStop(), count));
}

public static LinearGradient[] ComplexGradientsExpected = new[]
{
new LinearGradient{ Angle = FromDegrees(242), Stops = new GradientElements<GradientStop>(new GradientStop[12])},
new LinearGradient{ Angle = FromDegrees(152), Stops = new GradientElements<GradientStop>(new GradientStop[12])},
new LinearGradient{ Angle = FromDegrees(11), Stops = new GradientElements<GradientStop>(new GradientStop[12])},
new LinearGradient{ Angle = FromDegrees(27), Stops = new GradientElements<GradientStop>(new GradientStop[14])},
new LinearGradient{ Angle = FromDegrees(317), Stops = new GradientElements<GradientStop>(new GradientStop[12])},
new LinearGradient{ Angle = FromDegrees(128), Stops = new GradientElements<GradientStop>(new GradientStop[16])},
new LinearGradient{ Angle = FromDegrees(90), Stops = new GradientElements<GradientStop>(new GradientStop[2])}
new LinearGradient{ Angle = FromDegrees(242), Stops = CreateStops(12)},
new LinearGradient{ Angle = FromDegrees(152), Stops = CreateStops(12)},
new LinearGradient{ Angle = FromDegrees(11), Stops = CreateStops(12)},
new LinearGradient{ Angle = FromDegrees(27), Stops = CreateStops(14)},
new LinearGradient{ Angle = FromDegrees(317), Stops = CreateStops(12)},
new LinearGradient{ Angle = FromDegrees(128), Stops = CreateStops(16)},
new LinearGradient{ Angle = FromDegrees(90), Stops = CreateStops(2)}
}.Reverse().ToArray();

public static IEnumerable<object[]> SimpleGradients()
Expand Down Expand Up @@ -94,7 +98,7 @@ public static IEnumerable<object[]> GradientsWithoutOffsets()
{
Angle = FromDegrees(224), Stops = new GradientElements<GradientStop>
{
new GradientStop { Color = Color.Black, Offset = 0f },
new GradientStop { Color = Color.Black, RenderOffset = 0f },
}
}
};
Expand All @@ -104,8 +108,8 @@ public static IEnumerable<object[]> GradientsWithoutOffsets()
{
Angle = FromDegrees(224), Stops = new GradientElements<GradientStop>
{
new GradientStop { Color = Color.Black, Offset = 0f },
new GradientStop { Color = Color.Black, Offset = 1f }
new GradientStop { Color = Color.Black, RenderOffset = 0f },
new GradientStop { Color = Color.Black, RenderOffset = 1f }
}
}
};
Expand All @@ -115,9 +119,9 @@ public static IEnumerable<object[]> GradientsWithoutOffsets()
{
Angle = FromDegrees(224), Stops = new GradientElements<GradientStop>
{
new GradientStop { Color = Color.Black, Offset = 0f },
new GradientStop { Color = Color.Black, Offset = 0.5f },
new GradientStop { Color = Color.Black, Offset = 1f }
new GradientStop { Color = Color.Black, RenderOffset = 0f },
new GradientStop { Color = Color.Black, RenderOffset = 0.5f },
new GradientStop { Color = Color.Black, RenderOffset = 1f }
}
}
};
Expand Down
45 changes: 34 additions & 11 deletions MagicGradients/GradientBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
using System.Collections.Generic;
using System.Linq;
using Xamarin.Forms;

namespace MagicGradients
{
public class GradientBuilder
{
private readonly List<Gradient> _gradients = new List<Gradient>();
private Gradient _lastGradient;
private readonly List<GradientStop> _stops = new List<GradientStop>();

public GradientBuilder AddLinearGradient(double angle, bool isRepeating = false)
{
_lastGradient = new LinearGradient
AddCachedStopsToLast();

var linearGradient = new LinearGradient
{
Angle = angle,
IsRepeating = isRepeating
};

_gradients.Add(_lastGradient);
_gradients.Add(linearGradient);

return this;
}
Expand All @@ -28,7 +31,9 @@ public GradientBuilder AddRadialGradient(
RadialGradientFlags flags = RadialGradientFlags.PositionProportional,
bool isRepeating = false)
{
_lastGradient = new RadialGradient
AddCachedStopsToLast();

var radialGradient = new RadialGradient
{
Center = center,
Shape = shape,
Expand All @@ -37,25 +42,20 @@ public GradientBuilder AddRadialGradient(
IsRepeating = isRepeating
};

_gradients.Add(_lastGradient);
_gradients.Add(radialGradient);

return this;
}

public GradientBuilder AddStop(Color color, float? offset = null)
{
if (_lastGradient == null)
{
AddLinearGradient(0);
}

var stop = new GradientStop
{
Color = color,
Offset = offset ?? -1
};

_lastGradient.Stops.Add(stop);
_stops.Add(stop);

return this;
}
Expand All @@ -70,8 +70,31 @@ public GradientBuilder AddStops(Color color, IEnumerable<float> offsets)
return this;
}

private void AddCachedStopsToLast()
{
if (!_stops.Any())
return;

var lastGradient = _gradients.LastOrDefault();
if (lastGradient == null)
{
lastGradient = CreateDefaultGradient();
_gradients.Add(lastGradient);
}
lastGradient.Stops = new GradientElements<GradientStop>(_stops);

_stops.Clear();
}

private Gradient CreateDefaultGradient() => new LinearGradient
{
Angle = 0,
IsRepeating = false
};

public Gradient[] Build()
{
AddCachedStopsToLast();
return _gradients.ToArray();
}
}
Expand Down
3 changes: 0 additions & 3 deletions MagicGradients/GradientView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,6 @@ protected override void OnPaintSurface(SKPaintSurfaceEventArgs e)

foreach (var gradient in GradientSource.GetGradients())
{
#if DEBUG_RENDER
System.Diagnostics.Debug.WriteLine($"Rendering Gradient with {gradient.Stops.Count} stops");
#endif
gradient.Measure(e.Info.Width, e.Info.Height);
gradient.Render(context);
}
Expand Down
3 changes: 3 additions & 0 deletions MagicGradients/LinearGradient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ private float GetOffsetFromPixels(float offset, int width, int height)

public override void Render(RenderContext context)
{
#if DEBUG_RENDER
System.Diagnostics.Debug.WriteLine($"Rendering Linear Gradient with {Stops.Count} stops");
#endif
_renderer.Render(context);
}
}
Expand Down
2 changes: 1 addition & 1 deletion MagicGradients/MagicGradients.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

<ItemGroup>
<PackageReference Include="SkiaSharp.Views.Forms" Version="1.68.1.1" />
<PackageReference Include="Xamarin.Forms" Version="4.3.0.991250" />
<PackageReference Include="Xamarin.Forms" Version="4.4.0.991265" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ public bool IsMatch(string token) =>

public void Parse(CssReader reader, GradientBuilder builder)
{
var color = (Color)ColorConverter.ConvertFromInvariantString(GetColorString(reader));
var colorString = GetColorString(reader);
var color = (Color)ColorConverter.ConvertFromInvariantString(colorString);
var parts = reader.ReadNext().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

if (parts.TryConvertOffsets(out var offsets))
Expand Down
3 changes: 3 additions & 0 deletions MagicGradients/RadialGradient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ public RadialGradient()

public override void Render(RenderContext context)
{
#if DEBUG_RENDER
System.Diagnostics.Debug.WriteLine($"Rendering Radial Gradient with {Stops.Count} stops");
#endif
_renderer.Render(context);
}
}
Expand Down

0 comments on commit 0914aae

Please sign in to comment.