From c02f9d883d93685a91c31685a6ac0a429c83c6c7 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 19 Apr 2018 20:13:16 +0200 Subject: [PATCH 01/43] #86: started to work on Gradient Brushes: Linear gradient brush, not yet working - what's wrong? --- .../Drawing/Brushes/LinearGradientBrush.cs | 211 ++++++++++++++++++ .../Drawing/FillLinearGradientBrushTests.cs | 47 ++++ 2 files changed, 258 insertions(+) create mode 100644 src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush.cs create mode 100644 tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush.cs new file mode 100644 index 000000000..bfbeded69 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush.cs @@ -0,0 +1,211 @@ +using System; +using System.Numerics; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.PixelFormats.PixelBlenders; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Drawing.Brushes +{ + /// + /// Provides an implementation of a brush for painting gradients within areas. + /// Supported right now: + /// - a set of colors in relative distances to each other. + /// - two points to gradient along. + /// + /// The pixel format + public class LinearGradientBrush : IBrush + where TPixel : struct, IPixel + { + private readonly Point p1; + + private readonly Point p2; + + private readonly Tuple[] keyColors; + + /// + /// Initializes a new instance of the class. + /// + /// Start point + /// End point + /// a set of color keys and where they are. The double must be in range [0..1] and is relative between p1 and p2. + public LinearGradientBrush(Point p1, Point p2, params Tuple[] keyColors) + { + this.p1 = p1; + this.p2 = p2; + this.keyColors = keyColors; + } + + /// + public BrushApplicator CreateApplicator(ImageFrame source, RectangleF region, GraphicsOptions options) + => new LinearGradientBrushApplicator(source, this.p1, this.p2, this.keyColors, region, options); + + /// + /// The linear gradient brush applicator. + /// + private class LinearGradientBrushApplicator : BrushApplicator + { + private readonly Point start; + + private readonly Point end; + + private readonly Tuple[] colorStops; + + /// + /// the vector along the gradient, x component + /// + private readonly float alongX; + + /// + /// the vector along the gradient, y component + /// + private readonly float alongY; + + /// + /// the vector perpendicular to the gradient, y component + /// + private readonly float acrossY; + + /// + /// the vector perpendicular to the gradient, x component + /// + private readonly float acrossX; + + /// + /// helper to speed up calculation as these dont't change + /// + private readonly float aYcX; + + /// + /// helper to speed up calculation as these dont't change + /// + private readonly float aXcY; + + /// + /// helper to speed up calculation as these dont't change + /// + private readonly float aXcX; + + /// + /// Initializes a new instance of the class. + /// + /// The source + /// start point of the gradient + /// end point of the gradient + /// tuple list of colors and their respective position between 0 and 1 on the line + /// the region, copied from SolidColorBrush, not sure if necessary! TODO + /// the graphics options + public LinearGradientBrushApplicator( + ImageFrame source, + Point start, + Point end, + Tuple[] colorStops, + RectangleF region, // TODO: use region, compare with other Brushes for reference. + GraphicsOptions options) + : base(source, options) + { + this.start = start; + this.end = end; + this.colorStops = colorStops; // TODO: requires colorStops to be sorted by Item1! + + // the along vector: + this.alongX = this.start.X - this.end.X; + this.alongY = this.start.Y - this.end.Y; + + // the cross vector: + this.acrossX = this.alongY; + this.acrossY = -this.alongX; + + // some helpers: + this.aYcX = this.alongY * this.acrossX; + this.aXcY = this.alongX * this.acrossY; + this.aXcX = this.alongX * this.acrossX; + } + + /// + /// Gets the color for a single pixel + /// + /// The x. + /// The y. + internal override TPixel this[int x, int y] + { + get + { + // the following formula is the result of the linear equation system that forms the vector. + // TODO: this formula should be abstracted as it's the only difference between linear and radial gradient! + float onCompleteGradient = this.RatioOnGradient(x, y); + + var localGradientFrom = this.colorStops[0]; + Tuple localGradientTo = null; + + // TODO: ensure colorStops has at least 2 items (technically 1 would be okay, but that's no gradient) + foreach (var colorStop in this.colorStops) + { + localGradientTo = colorStop; + if (colorStop.Item1 >= onCompleteGradient) + { + // we're done here, so break it! + break; + } + + localGradientFrom = localGradientTo; + } + + TPixel resultColor = default; + if (localGradientFrom.Item2.Equals(localGradientTo.Item2)) + { + resultColor = localGradientFrom.Item2; + } + else + { + var fromAsVector = localGradientFrom.Item2.ToVector4(); + var toAsVector = localGradientTo.Item2.ToVector4(); + float onLocalGradient = (onCompleteGradient - localGradientFrom.Item1) / localGradientTo.Item1; // TODO: + + Vector4 result = PorterDuffFunctions.Normal( + fromAsVector, + toAsVector, + onLocalGradient); + + // TODO: when resultColor is a struct, what does PackFromVector4 do here? + resultColor.PackFromVector4(result); + } + + return resultColor; + } + } + + private float RatioOnGradient(int x, int y) + { + return ((x / this.acrossX) - (this.alongX * y / this.aYcX)) + / (1 - (this.aXcY / this.aXcX)); + } + + internal override void Apply(Span scanline, int x, int y) + { + base.Apply(scanline, x, y); + + // Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); + // MemoryManager memoryManager = this.Target.MemoryManager; + // using (IBuffer amountBuffer = memoryManager.Allocate(scanline.Length)) + // { + // Span amountSpan = amountBuffer.Span; + // + // for (int i = 0; i < scanline.Length; i++) + // { + // amountSpan[i] = scanline[i] * this.Options.BlendPercentage; + // } + // + // this.Blender.Blend(memoryManager, destinationRow, destinationRow, this.Colors.Span, amountSpan); + // } + } + + /// + public override void Dispose() + { + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs new file mode 100644 index 000000000..3d613adc0 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs @@ -0,0 +1,47 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Drawing; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Drawing +{ + using System; + + using SixLabors.ImageSharp.Processing; + using SixLabors.ImageSharp.Processing.Drawing.Brushes; + using SixLabors.ImageSharp.Processing.Overlays; + + using Point = SixLabors.Primitives.Point; + + public class FillLinearGradientBrushTests : FileTestBase + { + [Fact] + public void LinearGradientBrushWithEqualColorsReturnsUnicolorImage() + { + string path = TestEnvironment.CreateOutputDirectory("Fill", "LinearGradientBrush"); + using (var image = new Image(500, 500)) + { + LinearGradientBrush unicolorLinearGradientBrush = + new LinearGradientBrush( + new Point(0, 0), + new Point(500, 0), + new Tuple(0, Rgba32.Red), + new Tuple(1, Rgba32.Red)); + + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + image.Save($"{path}/UnicolorGradient.png"); + + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Rgba32.Red, sourcePixels[0, 0]); + Assert.Equal(Rgba32.Red, sourcePixels[9, 9]); + Assert.Equal(Rgba32.Red, sourcePixels[199, 149]); + Assert.Equal(Rgba32.Red, sourcePixels[500, 500]); + } + } + } + } +} From 4975eae7741ba3312915e2f8ecfc7196ec5ac9eb Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 19 Apr 2018 22:25:47 +0200 Subject: [PATCH 02/43] fix some typos in documentation. --- src/ImageSharp/Advanced/IConfigurable.cs | 2 +- src/ImageSharp/PixelFormats/PixelBlenderMode.cs | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Advanced/IConfigurable.cs b/src/ImageSharp/Advanced/IConfigurable.cs index fd97ae921..38fc83ae1 100644 --- a/src/ImageSharp/Advanced/IConfigurable.cs +++ b/src/ImageSharp/Advanced/IConfigurable.cs @@ -4,7 +4,7 @@ namespace SixLabors.ImageSharp.Advanced { /// - /// Encapsulates the properties for configuration + /// Encapsulates the properties for configuration. /// internal interface IConfigurable { diff --git a/src/ImageSharp/PixelFormats/PixelBlenderMode.cs b/src/ImageSharp/PixelFormats/PixelBlenderMode.cs index 4b8f56d76..d0cbff770 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenderMode.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenderMode.cs @@ -64,17 +64,17 @@ namespace SixLabors.ImageSharp.PixelFormats Atop, /// - /// returns the detination over the source + /// returns the destination over the source /// Over, /// - /// the source where the desitnation and source overlap + /// the source where the destination and source overlap /// In, /// - /// the destination where the desitnation and source overlap + /// the destination where the destination and source overlap /// Out, @@ -89,17 +89,17 @@ namespace SixLabors.ImageSharp.PixelFormats DestAtop, /// - /// the destnation over the source + /// the destination over the source /// DestOver, /// - /// the destination where the desitnation and source overlap + /// the destination where the destination and source overlap /// DestIn, /// - /// the source where the desitnation and source overlap + /// the source where the destination and source overlap /// DestOut, From e1ee9b0b63b9934e237222597f1b17d8200a9aff Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 19 Apr 2018 23:59:42 +0200 Subject: [PATCH 03/43] FIX bug in BrushApplicator when applying BlendPercentage thanks @tocsoft for investigation and finding the bug. He proposed to just replace < by <= in line 78, but that would only shift the problem to values > 1. Those values should not be used from a semantic point, but it's not forbidden in a float value. My fix here keeps the <, but adds an else path that uses the original value from scanline[i]. This adds an else to the code (which technically adds a jump after the then-part), but omits the multiplication. The simple solution @tocsoft proposed might be faster, but should be preferred if and only if BlendPercentage can be guaranteed to be <=1. --- .../Processing/Drawing/Brushes/BrushApplicator.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/BrushApplicator.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/BrushApplicator.cs index f665e8408..c54666335 100644 --- a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/BrushApplicator.cs +++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/BrushApplicator.cs @@ -79,6 +79,10 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes { amountSpan[i] = scanline[i] * this.Options.BlendPercentage; } + else + { + amountSpan[i] = scanline[i]; + } overlaySpan[i] = this[x + i, y]; } From 6d878441e74be204c7b34ffb72b5d109b68effe4 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 20 Apr 2018 00:01:33 +0200 Subject: [PATCH 04/43] #542: fix test: indices are 0-based, so bottom left pixel is one smaller --- .../Drawing/FillLinearGradientBrushTests.cs | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs index 3d613adc0..047ffc9b7 100644 --- a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs @@ -1,21 +1,17 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System.Numerics; +using System; + +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Drawing.Brushes; + using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Drawing; using Xunit; namespace SixLabors.ImageSharp.Tests.Drawing { - using System; - - using SixLabors.ImageSharp.Processing; - using SixLabors.ImageSharp.Processing.Drawing.Brushes; - using SixLabors.ImageSharp.Processing.Overlays; - - using Point = SixLabors.Primitives.Point; - public class FillLinearGradientBrushTests : FileTestBase { [Fact] @@ -26,8 +22,8 @@ namespace SixLabors.ImageSharp.Tests.Drawing { LinearGradientBrush unicolorLinearGradientBrush = new LinearGradientBrush( - new Point(0, 0), - new Point(500, 0), + new SixLabors.Primitives.Point(0, 0), + new SixLabors.Primitives.Point(500, 0), new Tuple(0, Rgba32.Red), new Tuple(1, Rgba32.Red)); @@ -39,7 +35,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing Assert.Equal(Rgba32.Red, sourcePixels[0, 0]); Assert.Equal(Rgba32.Red, sourcePixels[9, 9]); Assert.Equal(Rgba32.Red, sourcePixels[199, 149]); - Assert.Equal(Rgba32.Red, sourcePixels[500, 500]); + Assert.Equal(Rgba32.Red, sourcePixels[499, 499]); } } } From 1e587bc689e29a1a03e4faa773bbbafdc897f232 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 20 Apr 2018 00:44:53 +0200 Subject: [PATCH 05/43] #542: use struct for ColorStops as proposed by @antonfirsov: improving readability and memory locality. --- .../Drawing/Brushes/LinearGradientBrush.cs | 59 ++++++++++++++----- .../Drawing/FillLinearGradientBrushTests.cs | 4 +- 2 files changed, 47 insertions(+), 16 deletions(-) diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush.cs index bfbeded69..99edb39f4 100644 --- a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush.cs +++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush.cs @@ -19,28 +19,59 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes public class LinearGradientBrush : IBrush where TPixel : struct, IPixel { + /// + /// A struct that defines a single color stop. + /// + public struct ColorStop + { + /// + /// Create a new ColorStop + /// + /// Where should it be? 0 is at the start, 1 at the end of the . + /// What color should be used at that point? + public ColorStop(float ratio, TPixel color) + { + this.Ratio = ratio; + this.Color = color; + } + + /// + /// The point along the defined gradient axis. + /// + public float Ratio { get; } + + /// + /// The color to be used. + /// + public TPixel Color { get; } + } + private readonly Point p1; private readonly Point p2; - private readonly Tuple[] keyColors; + private readonly ColorStop[] colorStops; /// /// Initializes a new instance of the class. /// /// Start point /// End point - /// a set of color keys and where they are. The double must be in range [0..1] and is relative between p1 and p2. - public LinearGradientBrush(Point p1, Point p2, params Tuple[] keyColors) + /// + /// A set of color keys and where they are. + /// The double should be in range [0..1] and is relative between p1 and p2. + /// TODO: what about the [0..1] restriction? is it necessary? If so, it should be checked, if not, it should be explained what happens for greater/smaller values. + /// + public LinearGradientBrush(Point p1, Point p2, params ColorStop[] colorStops) { this.p1 = p1; this.p2 = p2; - this.keyColors = keyColors; + this.colorStops = colorStops; } /// public BrushApplicator CreateApplicator(ImageFrame source, RectangleF region, GraphicsOptions options) - => new LinearGradientBrushApplicator(source, this.p1, this.p2, this.keyColors, region, options); + => new LinearGradientBrushApplicator(source, this.p1, this.p2, this.colorStops, region, options); /// /// The linear gradient brush applicator. @@ -51,7 +82,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes private readonly Point end; - private readonly Tuple[] colorStops; + private readonly ColorStop[] colorStops; /// /// the vector along the gradient, x component @@ -101,7 +132,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes ImageFrame source, Point start, Point end, - Tuple[] colorStops, + ColorStop[] colorStops, RectangleF region, // TODO: use region, compare with other Brushes for reference. GraphicsOptions options) : base(source, options) @@ -138,13 +169,13 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes float onCompleteGradient = this.RatioOnGradient(x, y); var localGradientFrom = this.colorStops[0]; - Tuple localGradientTo = null; + ColorStop localGradientTo = default; // TODO: ensure colorStops has at least 2 items (technically 1 would be okay, but that's no gradient) foreach (var colorStop in this.colorStops) { localGradientTo = colorStop; - if (colorStop.Item1 >= onCompleteGradient) + if (colorStop.Ratio >= onCompleteGradient) { // we're done here, so break it! break; @@ -154,15 +185,15 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes } TPixel resultColor = default; - if (localGradientFrom.Item2.Equals(localGradientTo.Item2)) + if (localGradientFrom.Color.Equals(localGradientTo.Color)) { - resultColor = localGradientFrom.Item2; + resultColor = localGradientFrom.Color; } else { - var fromAsVector = localGradientFrom.Item2.ToVector4(); - var toAsVector = localGradientTo.Item2.ToVector4(); - float onLocalGradient = (onCompleteGradient - localGradientFrom.Item1) / localGradientTo.Item1; // TODO: + var fromAsVector = localGradientFrom.Color.ToVector4(); + var toAsVector = localGradientTo.Color.ToVector4(); + float onLocalGradient = (onCompleteGradient - localGradientFrom.Ratio) / localGradientTo.Ratio; // TODO: Vector4 result = PorterDuffFunctions.Normal( fromAsVector, diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs index 047ffc9b7..88ebc49c3 100644 --- a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs @@ -24,8 +24,8 @@ namespace SixLabors.ImageSharp.Tests.Drawing new LinearGradientBrush( new SixLabors.Primitives.Point(0, 0), new SixLabors.Primitives.Point(500, 0), - new Tuple(0, Rgba32.Red), - new Tuple(1, Rgba32.Red)); + new LinearGradientBrush.ColorStop(0, Rgba32.Red), + new LinearGradientBrush.ColorStop(1, Rgba32.Red)); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); image.Save($"{path}/UnicolorGradient.png"); From 6dd544d5aad627549152185f759d3604d8722df8 Mon Sep 17 00:00:00 2001 From: Peter Amrehn Date: Fri, 20 Apr 2018 19:18:04 +0200 Subject: [PATCH 06/43] fix code styling issues --- .../Drawing/Brushes/LinearGradientBrush.cs | 54 +++++++++---------- .../Drawing/FillLinearGradientBrushTests.cs | 45 +++++++++++++++- 2 files changed, 71 insertions(+), 28 deletions(-) diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush.cs index 99edb39f4..b955cd750 100644 --- a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush.cs +++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush.cs @@ -19,33 +19,6 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes public class LinearGradientBrush : IBrush where TPixel : struct, IPixel { - /// - /// A struct that defines a single color stop. - /// - public struct ColorStop - { - /// - /// Create a new ColorStop - /// - /// Where should it be? 0 is at the start, 1 at the end of the . - /// What color should be used at that point? - public ColorStop(float ratio, TPixel color) - { - this.Ratio = ratio; - this.Color = color; - } - - /// - /// The point along the defined gradient axis. - /// - public float Ratio { get; } - - /// - /// The color to be used. - /// - public TPixel Color { get; } - } - private readonly Point p1; private readonly Point p2; @@ -73,6 +46,33 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes public BrushApplicator CreateApplicator(ImageFrame source, RectangleF region, GraphicsOptions options) => new LinearGradientBrushApplicator(source, this.p1, this.p2, this.colorStops, region, options); + /// + /// A struct that defines a single color stop. + /// + public struct ColorStop + { + /// + /// Initializes a new instance of the struct. + /// + /// Where should it be? 0 is at the start, 1 at the end of the . + /// What color should be used at that point? + public ColorStop(float ratio, TPixel color) + { + this.Ratio = ratio; + this.Color = color; + } + + /// + /// Gets the point along the defined gradient axis. + /// + public float Ratio { get; } + + /// + /// Gets the color to be used. + /// + public TPixel Color { get; } + } + /// /// The linear gradient brush applicator. /// diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs index 88ebc49c3..e25a998a5 100644 --- a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs @@ -39,5 +39,48 @@ namespace SixLabors.ImageSharp.Tests.Drawing } } } + + [Fact] + public void HorizontalLinearGradientBrushReturnsUnicolorColumns() + { + int width = 500; + int height = 500; + int lastColumnIndex = width - 1; + int lastRowIndex = height - 1; + + + string path = TestEnvironment.CreateOutputDirectory("Fill", "LinearGradientBrush"); + using (var image = new Image(width, height)) + { + LinearGradientBrush unicolorLinearGradientBrush = + new LinearGradientBrush( + new SixLabors.Primitives.Point(0, 0), + new SixLabors.Primitives.Point(500, 0), + new LinearGradientBrush.ColorStop(0, Rgba32.Red), + new LinearGradientBrush.ColorStop(1, Rgba32.Yellow)); + + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + image.Save($"{path}/horizontalGradient.png"); + + using (PixelAccessor sourcePixels = image.Lock()) + { + Rgba32 columnColor23 = sourcePixels[23, 0]; + Rgba32 columnColor42 = sourcePixels[42, 0]; + Rgba32 columnColor333 = sourcePixels[333, 0]; + + for (int i = 0; i < height; i++) + { + // check first and last column, these are known: + Assert.Equal(Rgba32.Red, sourcePixels[0, i]); + Assert.Equal(Rgba32.Yellow, sourcePixels[lastColumnIndex, i]); + + // check the random colors: + Assert.Equal(columnColor23, sourcePixels[23, i]); + Assert.Equal(columnColor42, sourcePixels[42, i]); + Assert.Equal(columnColor333, sourcePixels[333, i]); + } + } + } + } } -} +} \ No newline at end of file From ce72683866f02ed5717b429c11affbadb5fade54 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 21 Apr 2018 15:21:41 +0200 Subject: [PATCH 07/43] #542: reduce test output image sizes test images don't have to be that big for axial gradients. It's sufficient to show they're constant across the axis and correct along the axis. --- .../Drawing/FillLinearGradientBrushTests.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs index e25a998a5..8946626bc 100644 --- a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs @@ -18,12 +18,12 @@ namespace SixLabors.ImageSharp.Tests.Drawing public void LinearGradientBrushWithEqualColorsReturnsUnicolorImage() { string path = TestEnvironment.CreateOutputDirectory("Fill", "LinearGradientBrush"); - using (var image = new Image(500, 500)) + using (var image = new Image(10, 10)) { LinearGradientBrush unicolorLinearGradientBrush = new LinearGradientBrush( new SixLabors.Primitives.Point(0, 0), - new SixLabors.Primitives.Point(500, 0), + new SixLabors.Primitives.Point(10, 0), new LinearGradientBrush.ColorStop(0, Rgba32.Red), new LinearGradientBrush.ColorStop(1, Rgba32.Red)); @@ -34,8 +34,8 @@ namespace SixLabors.ImageSharp.Tests.Drawing { Assert.Equal(Rgba32.Red, sourcePixels[0, 0]); Assert.Equal(Rgba32.Red, sourcePixels[9, 9]); - Assert.Equal(Rgba32.Red, sourcePixels[199, 149]); - Assert.Equal(Rgba32.Red, sourcePixels[499, 499]); + Assert.Equal(Rgba32.Red, sourcePixels[5, 5]); + Assert.Equal(Rgba32.Red, sourcePixels[3, 8]); } } } @@ -44,9 +44,8 @@ namespace SixLabors.ImageSharp.Tests.Drawing public void HorizontalLinearGradientBrushReturnsUnicolorColumns() { int width = 500; - int height = 500; + int height = 10; int lastColumnIndex = width - 1; - int lastRowIndex = height - 1; string path = TestEnvironment.CreateOutputDirectory("Fill", "LinearGradientBrush"); @@ -60,7 +59,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing new LinearGradientBrush.ColorStop(1, Rgba32.Yellow)); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - image.Save($"{path}/horizontalGradient.png"); + image.Save($"{path}/horizontalRedToYellow.png"); using (PixelAccessor sourcePixels = image.Lock()) { From 90293cc6b7a3098c5b2502ade7af7e252e83d4b9 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 21 Apr 2018 15:22:18 +0200 Subject: [PATCH 08/43] #542: add test for vertical gradient --- .../Drawing/FillLinearGradientBrushTests.cs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs index 8946626bc..d1b253680 100644 --- a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs @@ -81,5 +81,47 @@ namespace SixLabors.ImageSharp.Tests.Drawing } } } + + [Fact] + public void VerticalLinearGradientBrushReturnsUnicolorColumns() + { + int width = 10; + int height = 500; + int lastRowIndex = height - 1; + + + string path = TestEnvironment.CreateOutputDirectory("Fill", "LinearGradientBrush"); + using (var image = new Image(width, height)) + { + LinearGradientBrush unicolorLinearGradientBrush = + new LinearGradientBrush( + new SixLabors.Primitives.Point(0, 0), + new SixLabors.Primitives.Point(0, 500), + new LinearGradientBrush.ColorStop(0, Rgba32.Red), + new LinearGradientBrush.ColorStop(1, Rgba32.Yellow)); + + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + image.Save($"{path}/verticalRedToYellow.png"); + + using (PixelAccessor sourcePixels = image.Lock()) + { + Rgba32 columnColor23 = sourcePixels[0, 23]; + Rgba32 columnColor42 = sourcePixels[0, 42]; + Rgba32 columnColor333 = sourcePixels[0, 333]; + + for (int i = 0; i < width; i++) + { + // check first and last column, these are known: + Assert.Equal(Rgba32.Red, sourcePixels[i, 0]); + Assert.Equal(Rgba32.Yellow, sourcePixels[i, lastRowIndex]); + + // check the random colors: + Assert.Equal(columnColor23, sourcePixels[i, 23]); + Assert.Equal(columnColor42, sourcePixels[i, 42]); + Assert.Equal(columnColor333, sourcePixels[i, 333]); + } + } + } + } } } \ No newline at end of file From 94dbb743f75d9f4bd4d0ea0e3f193946921c0d6a Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 21 Apr 2018 15:30:04 +0200 Subject: [PATCH 09/43] #542: fix implementation of non-axial gradients and add tests the theory lacks color checks yet that should be added, but it produces output images for visual control --- .../Drawing/Brushes/LinearGradientBrush.cs | 51 +++++++++++++------ .../Drawing/FillLinearGradientBrushTests.cs | 35 +++++++++++++ 2 files changed, 70 insertions(+), 16 deletions(-) diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush.cs index b955cd750..8ef229548 100644 --- a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush.cs +++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush.cs @@ -105,19 +105,14 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes private readonly float acrossX; /// - /// helper to speed up calculation as these dont't change + /// the result of ^2 + ^2 /// - private readonly float aYcX; + private readonly float alongsSquared; /// - /// helper to speed up calculation as these dont't change + /// the length of the defined gradient (between source and end) /// - private readonly float aXcY; - - /// - /// helper to speed up calculation as these dont't change - /// - private readonly float aXcX; + private readonly float length; /// /// Initializes a new instance of the class. @@ -142,17 +137,16 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes this.colorStops = colorStops; // TODO: requires colorStops to be sorted by Item1! // the along vector: - this.alongX = this.start.X - this.end.X; - this.alongY = this.start.Y - this.end.Y; + this.alongX = this.end.X - this.start.X; + this.alongY = this.end.Y - this.start.Y; // the cross vector: this.acrossX = this.alongY; this.acrossY = -this.alongX; // some helpers: - this.aYcX = this.alongY * this.acrossX; - this.aXcY = this.alongX * this.acrossY; - this.aXcX = this.alongX * this.acrossX; + this.alongsSquared = (this.alongX * this.alongX) + (this.alongY * this.alongY); + this.length = (float)Math.Sqrt(this.alongsSquared); } /// @@ -210,8 +204,33 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes private float RatioOnGradient(int x, int y) { - return ((x / this.acrossX) - (this.alongX * y / this.aYcX)) - / (1 - (this.aXcY / this.aXcX)); + if (this.acrossX == 0) + { + return (x - this.start.X) / (float)(this.end.X - this.start.X); + } + else if (this.acrossY == 0) + { + return (y - this.start.Y) / (float)(this.end.Y - this.start.Y); + } + else + { + float deltaX = x - this.start.X; + float deltaY = y - this.start.Y; + float k = ((this.alongY * deltaX) - (this.alongX * deltaY)) / this.alongsSquared; + + // point on the line: + float x4 = x - (k * this.alongY); + float y4 = y + (k * this.alongX); + + // get distance from (x4,y4) to start + float distance = (float)Math.Sqrt( + Math.Pow(x4 - this.start.X, 2) + + Math.Pow(y4 - this.start.Y, 2)); + + // get and return ratio + float ratio = distance / this.length; + return ratio; + } } internal override void Apply(Span scanline, int x, int y) diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs index d1b253680..52979d792 100644 --- a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs @@ -123,5 +123,40 @@ namespace SixLabors.ImageSharp.Tests.Drawing } } } + + [Theory] + [InlineData(0, 0, 499, 499)] + [InlineData(0, 499, 499, 0)] + [InlineData(499, 499, 0, 0)] + [InlineData(499, 0, 0, 499)] + public void DiagonalLinearGradientBrushReturnsUnicolorColumns( + int startX, int startY, int endX, int endY) + { + int size = 500; + int lastIndex = size - 1; + + + string path = TestEnvironment.CreateOutputDirectory("Fill", "LinearGradientBrush"); + using (var image = new Image(size, size)) + { + LinearGradientBrush unicolorLinearGradientBrush = + new LinearGradientBrush( + new SixLabors.Primitives.Point(startX, startY), + new SixLabors.Primitives.Point(endX, endY), + new LinearGradientBrush.ColorStop(0, Rgba32.Red), + new LinearGradientBrush.ColorStop(1, Rgba32.Yellow)); + + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + image.Save($"{path}/diagonalRedToYellowFrom{startX}_{startY}.png"); + + using (PixelAccessor sourcePixels = image.Lock()) + { + // check first and last pixel, these are known: + Assert.Equal(Rgba32.Red, sourcePixels[startX, startY]); + Assert.Equal(Rgba32.Yellow, sourcePixels[endX, endY]); + + } + } + } } } \ No newline at end of file From 7a3ba8af3c9a41582ef04e46daff7e49f09f55a1 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 21 Apr 2018 16:01:33 +0200 Subject: [PATCH 10/43] #542: add tests for multi-color gradients somehow these don't look correct yet, containing hard edges sometimes. --- .../Drawing/FillLinearGradientBrushTests.cs | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs index 52979d792..9ba7f7e62 100644 --- a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs @@ -158,5 +158,60 @@ namespace SixLabors.ImageSharp.Tests.Drawing } } } + + [Theory] + [InlineData("a", 0, 0, 499, 499, new[] { 0f, .2f, .5f, .9f }, new[] { 0, 0, 1, 1 })] + [InlineData("b", 0, 499, 499, 0, new[] { 0f, 0.2f, 0.5f, 0.9f }, new[] { 0, 1, 2, 3 })] + [InlineData("c", 499, 499, 0, 0, new[] { 0f, 0.7f, 0.8f, 0.9f}, new[] { 0, 1, 2, 0 })] + [InlineData("d", 0, 0, 499, 499, new[] { 0f, .5f, 1f}, new[]{0, 1, 3})] + public void ArbitraryLinearGradientsProduceImages_VisualCheckOnly( + string filenameSuffix, + int startX, int startY, + int endX, int endY, + float[] stopPositions, + int[] stopColorCodes) + { + var colors = new Rgba32[] + { + Rgba32.Navy, + Rgba32.LightGreen, + Rgba32.Yellow, + Rgba32.Red + }; + + var colorStops = new LinearGradientBrush.ColorStop[stopPositions.Length]; + for (int i = 0; i < stopPositions.Length; i++) + { + colorStops[i] = new LinearGradientBrush.ColorStop( + stopPositions[i], + colors[stopColorCodes[i]]); + } + + + int size = 500; + int lastIndex = size - 1; + + + string path = TestEnvironment.CreateOutputDirectory("Fill", "LinearGradientBrush"); + using (var image = new Image(size, size)) + { + LinearGradientBrush unicolorLinearGradientBrush = + new LinearGradientBrush( + new SixLabors.Primitives.Point(startX, startY), + new SixLabors.Primitives.Point(endX, endY), + colorStops); + + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + image.Save($"{path}/arbitraryGradient_{filenameSuffix}.png"); + + using (PixelAccessor sourcePixels = image.Lock()) + { + for (int i = 0; i < size; i++) + { + // it's diagonal, so for any (a, a) on the gradient line, for all (a-x, b+x) - +/- depending on the diagonal direction - must be the same color) + } + } + } + } } } \ No newline at end of file From 28457c876874b2122bc4518a69c6b6523c965685 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 21 Apr 2018 21:48:53 +0200 Subject: [PATCH 11/43] cleanup whitespace --- .../Drawing/FillLinearGradientBrushTests.cs | 48 ++++++++----------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs index 9ba7f7e62..549da3371 100644 --- a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs @@ -46,8 +46,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing int width = 500; int height = 10; int lastColumnIndex = width - 1; - - + string path = TestEnvironment.CreateOutputDirectory("Fill", "LinearGradientBrush"); using (var image = new Image(width, height)) { @@ -81,15 +80,14 @@ namespace SixLabors.ImageSharp.Tests.Drawing } } } - + [Fact] public void VerticalLinearGradientBrushReturnsUnicolorColumns() { int width = 10; int height = 500; int lastRowIndex = height - 1; - - + string path = TestEnvironment.CreateOutputDirectory("Fill", "LinearGradientBrush"); using (var image = new Image(width, height)) { @@ -99,7 +97,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing new SixLabors.Primitives.Point(0, 500), new LinearGradientBrush.ColorStop(0, Rgba32.Red), new LinearGradientBrush.ColorStop(1, Rgba32.Yellow)); - + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); image.Save($"{path}/verticalRedToYellow.png"); @@ -108,13 +106,13 @@ namespace SixLabors.ImageSharp.Tests.Drawing Rgba32 columnColor23 = sourcePixels[0, 23]; Rgba32 columnColor42 = sourcePixels[0, 42]; Rgba32 columnColor333 = sourcePixels[0, 333]; - + for (int i = 0; i < width; i++) { // check first and last column, these are known: Assert.Equal(Rgba32.Red, sourcePixels[i, 0]); Assert.Equal(Rgba32.Yellow, sourcePixels[i, lastRowIndex]); - + // check the random colors: Assert.Equal(columnColor23, sourcePixels[i, 23]); Assert.Equal(columnColor42, sourcePixels[i, 42]); @@ -123,7 +121,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing } } } - + [Theory] [InlineData(0, 0, 499, 499)] [InlineData(0, 499, 499, 0)] @@ -134,8 +132,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing { int size = 500; int lastIndex = size - 1; - - + string path = TestEnvironment.CreateOutputDirectory("Fill", "LinearGradientBrush"); using (var image = new Image(size, size)) { @@ -145,7 +142,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing new SixLabors.Primitives.Point(endX, endY), new LinearGradientBrush.ColorStop(0, Rgba32.Red), new LinearGradientBrush.ColorStop(1, Rgba32.Yellow)); - + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); image.Save($"{path}/diagonalRedToYellowFrom{startX}_{startY}.png"); @@ -155,16 +152,20 @@ namespace SixLabors.ImageSharp.Tests.Drawing Assert.Equal(Rgba32.Red, sourcePixels[startX, startY]); Assert.Equal(Rgba32.Yellow, sourcePixels[endX, endY]); + for (int i = 0; i < size; i++) + { + // it's diagonal, so for any (a, a) on the gradient line, for all (a-x, b+x) - +/- depending on the diagonal direction - must be the same color) + } } } } - + [Theory] [InlineData("a", 0, 0, 499, 499, new[] { 0f, .2f, .5f, .9f }, new[] { 0, 0, 1, 1 })] [InlineData("b", 0, 499, 499, 0, new[] { 0f, 0.2f, 0.5f, 0.9f }, new[] { 0, 1, 2, 3 })] [InlineData("c", 499, 499, 0, 0, new[] { 0f, 0.7f, 0.8f, 0.9f}, new[] { 0, 1, 2, 0 })] [InlineData("d", 0, 0, 499, 499, new[] { 0f, .5f, 1f}, new[]{0, 1, 3})] - public void ArbitraryLinearGradientsProduceImages_VisualCheckOnly( + public void ArbitraryLinearGradientsProduceImagesVisualCheckOnly( string filenameSuffix, int startX, int startY, int endX, int endY, @@ -178,7 +179,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing Rgba32.Yellow, Rgba32.Red }; - + var colorStops = new LinearGradientBrush.ColorStop[stopPositions.Length]; for (int i = 0; i < stopPositions.Length; i++) { @@ -186,12 +187,9 @@ namespace SixLabors.ImageSharp.Tests.Drawing stopPositions[i], colors[stopColorCodes[i]]); } - - + int size = 500; - int lastIndex = size - 1; - - + string path = TestEnvironment.CreateOutputDirectory("Fill", "LinearGradientBrush"); using (var image = new Image(size, size)) { @@ -200,17 +198,9 @@ namespace SixLabors.ImageSharp.Tests.Drawing new SixLabors.Primitives.Point(startX, startY), new SixLabors.Primitives.Point(endX, endY), colorStops); - + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); image.Save($"{path}/arbitraryGradient_{filenameSuffix}.png"); - - using (PixelAccessor sourcePixels = image.Lock()) - { - for (int i = 0; i < size; i++) - { - // it's diagonal, so for any (a, a) on the gradient line, for all (a-x, b+x) - +/- depending on the diagonal direction - must be the same color) - } - } } } } From 35d0f56557006910f915f7fdccdb225b80434dcd Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 21 Apr 2018 21:50:33 +0200 Subject: [PATCH 12/43] #542: add test creating black-white patterns by gradients --- .../Drawing/Brushes/LinearGradientBrush.cs | 3 +- .../Drawing/FillLinearGradientBrushTests.cs | 52 +++++++++++++++++-- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush.cs index 8ef229548..2d92180b7 100644 --- a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush.cs +++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush.cs @@ -169,7 +169,8 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes foreach (var colorStop in this.colorStops) { localGradientTo = colorStop; - if (colorStop.Ratio >= onCompleteGradient) + + if (colorStop.Ratio > onCompleteGradient) { // we're done here, so break it! break; diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs index 549da3371..d6c440f3a 100644 --- a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs @@ -1,8 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; - +using System.Linq; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Drawing.Brushes; @@ -81,6 +80,54 @@ namespace SixLabors.ImageSharp.Tests.Drawing } } + [Theory] + [InlineData(new[] { 0.5f })] + [InlineData(new[] { 0.2f, 0.4f, 0.6f, 0.8f })] + [InlineData(new[] { 0.1f, 0.3f, 0.6f })] + public void LinearGradientsWithDoubledStopsProduceDashedPatterns( + float[] pattern) + { + int width = 20; + int height = 10; + + // ensure the input data is valid + Assert.True(pattern.Length > 0); + + // create the input pattern: 0, followed by each of the arguments twice, followed by 1.0 - toggling black and white. + LinearGradientBrush.ColorStop[] colorStops = + Enumerable.Repeat(new LinearGradientBrush.ColorStop(0, Rgba32.Black), 1) + .Concat( + pattern + .SelectMany((f, index) => new[] + { + new LinearGradientBrush.ColorStop(f, index % 2 == 0 ? Rgba32.Black : Rgba32.White), + new LinearGradientBrush.ColorStop(f, index % 2 == 0 ? Rgba32.White : Rgba32.Black) + })) + .Concat(Enumerable.Repeat(new LinearGradientBrush.ColorStop(1, pattern.Length % 2 == 0 ? Rgba32.Black : Rgba32.White), 1)) + .ToArray(); + + string path = TestEnvironment.CreateOutputDirectory("Fill", "LinearGradientBrush"); + using (var image = new Image(width, height)) + { + LinearGradientBrush unicolorLinearGradientBrush = + new LinearGradientBrush( + new SixLabors.Primitives.Point(0, 0), + new SixLabors.Primitives.Point(width, 0), + colorStops); + + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + image.Save($"{path}/blackAndWhite{pattern[0]}.png"); + + using (PixelAccessor sourcePixels = image.Lock()) + { + // the result must be a black and white pattern, no other color should occur: + Assert.All( + Enumerable.Range(0, width).Select(i => sourcePixels[i, 0]), + color => Assert.True(color == Rgba32.Black || color == Rgba32.White)); + } + } + } + [Fact] public void VerticalLinearGradientBrushReturnsUnicolorColumns() { @@ -131,7 +178,6 @@ namespace SixLabors.ImageSharp.Tests.Drawing int startX, int startY, int endX, int endY) { int size = 500; - int lastIndex = size - 1; string path = TestEnvironment.CreateOutputDirectory("Fill", "LinearGradientBrush"); using (var image = new Image(size, size)) From 0a642ffea74eec18229a7f12f70d3a276eff3783 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 21 Apr 2018 21:51:23 +0200 Subject: [PATCH 13/43] #542: define debuggerDisplay of ColorStop, --- .../Processing/Drawing/Brushes/LinearGradientBrush.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush.cs index 2d92180b7..9bd56f56b 100644 --- a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush.cs +++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush.cs @@ -1,8 +1,7 @@ using System; +using System.Diagnostics; using System.Numerics; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats.PixelBlenders; using SixLabors.Primitives; @@ -49,6 +48,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes /// /// A struct that defines a single color stop. /// + [DebuggerDisplay("ColorStop({Ratio} -> {Color}")] public struct ColorStop { /// @@ -152,8 +152,8 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes /// /// Gets the color for a single pixel /// - /// The x. - /// The y. + /// The x coordinate. + /// The y coordinate. internal override TPixel this[int x, int y] { get From eafdfead3925fc24cd6f4f10d393a187267bc0d1 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 21 Apr 2018 22:39:03 +0200 Subject: [PATCH 14/43] fix whitespacing --- .../Drawing/FillLinearGradientBrushTests.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs index d6c440f3a..89ea8c1fe 100644 --- a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs @@ -19,13 +19,13 @@ namespace SixLabors.ImageSharp.Tests.Drawing string path = TestEnvironment.CreateOutputDirectory("Fill", "LinearGradientBrush"); using (var image = new Image(10, 10)) { - LinearGradientBrush unicolorLinearGradientBrush = + LinearGradientBrush unicolorLinearGradientBrush = new LinearGradientBrush( new SixLabors.Primitives.Point(0, 0), new SixLabors.Primitives.Point(10, 0), new LinearGradientBrush.ColorStop(0, Rgba32.Red), new LinearGradientBrush.ColorStop(1, Rgba32.Red)); - + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); image.Save($"{path}/UnicolorGradient.png"); @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing } } } - + [Fact] public void HorizontalLinearGradientBrushReturnsUnicolorColumns() { @@ -49,13 +49,13 @@ namespace SixLabors.ImageSharp.Tests.Drawing string path = TestEnvironment.CreateOutputDirectory("Fill", "LinearGradientBrush"); using (var image = new Image(width, height)) { - LinearGradientBrush unicolorLinearGradientBrush = + LinearGradientBrush unicolorLinearGradientBrush = new LinearGradientBrush( new SixLabors.Primitives.Point(0, 0), new SixLabors.Primitives.Point(500, 0), new LinearGradientBrush.ColorStop(0, Rgba32.Red), new LinearGradientBrush.ColorStop(1, Rgba32.Yellow)); - + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); image.Save($"{path}/horizontalRedToYellow.png"); @@ -64,13 +64,13 @@ namespace SixLabors.ImageSharp.Tests.Drawing Rgba32 columnColor23 = sourcePixels[23, 0]; Rgba32 columnColor42 = sourcePixels[42, 0]; Rgba32 columnColor333 = sourcePixels[333, 0]; - + for (int i = 0; i < height; i++) { // check first and last column, these are known: Assert.Equal(Rgba32.Red, sourcePixels[0, i]); Assert.Equal(Rgba32.Yellow, sourcePixels[lastColumnIndex, i]); - + // check the random colors: Assert.Equal(columnColor23, sourcePixels[23, i]); Assert.Equal(columnColor42, sourcePixels[42, i]); @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing { int width = 20; int height = 10; - + // ensure the input data is valid Assert.True(pattern.Length > 0); @@ -109,7 +109,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing string path = TestEnvironment.CreateOutputDirectory("Fill", "LinearGradientBrush"); using (var image = new Image(width, height)) { - LinearGradientBrush unicolorLinearGradientBrush = + LinearGradientBrush unicolorLinearGradientBrush = new LinearGradientBrush( new SixLabors.Primitives.Point(0, 0), new SixLabors.Primitives.Point(width, 0), @@ -138,7 +138,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing string path = TestEnvironment.CreateOutputDirectory("Fill", "LinearGradientBrush"); using (var image = new Image(width, height)) { - LinearGradientBrush unicolorLinearGradientBrush = + LinearGradientBrush unicolorLinearGradientBrush = new LinearGradientBrush( new SixLabors.Primitives.Point(0, 0), new SixLabors.Primitives.Point(0, 500), @@ -182,7 +182,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing string path = TestEnvironment.CreateOutputDirectory("Fill", "LinearGradientBrush"); using (var image = new Image(size, size)) { - LinearGradientBrush unicolorLinearGradientBrush = + LinearGradientBrush unicolorLinearGradientBrush = new LinearGradientBrush( new SixLabors.Primitives.Point(startX, startY), new SixLabors.Primitives.Point(endX, endY), @@ -239,7 +239,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing string path = TestEnvironment.CreateOutputDirectory("Fill", "LinearGradientBrush"); using (var image = new Image(size, size)) { - LinearGradientBrush unicolorLinearGradientBrush = + LinearGradientBrush unicolorLinearGradientBrush = new LinearGradientBrush( new SixLabors.Primitives.Point(startX, startY), new SixLabors.Primitives.Point(endX, endY), From 1cd8656b468df3857878c0f356fbf5d493fc420c Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 21 Apr 2018 22:43:25 +0200 Subject: [PATCH 15/43] #542: use bigger files for the tests to "hide" rounding issues: The current implementation lacks accurrancy and prefers to be fast. For usual color gradients that shouldn't matter: They're quite smooth, for the black-white gradient on very small length's it's easy to spot these rounding errors as they affect whole pixels one can count manually. --- tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs index 89ea8c1fe..caece97b9 100644 --- a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs @@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing public void LinearGradientsWithDoubledStopsProduceDashedPatterns( float[] pattern) { - int width = 20; + int width = 200; int height = 10; // ensure the input data is valid From 393444c3ff87ed573f2c8ad9fba10f1180e93ebd Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 21 Apr 2018 22:54:15 +0200 Subject: [PATCH 16/43] #542: fix rounding issues in tests --- .../Drawing/FillLinearGradientBrushTests.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs index caece97b9..c95b165f8 100644 --- a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs @@ -61,18 +61,21 @@ namespace SixLabors.ImageSharp.Tests.Drawing using (PixelAccessor sourcePixels = image.Lock()) { + Rgba32 columnColor0 = sourcePixels[0, 0]; Rgba32 columnColor23 = sourcePixels[23, 0]; Rgba32 columnColor42 = sourcePixels[42, 0]; Rgba32 columnColor333 = sourcePixels[333, 0]; + Rgba32 lastColumnColor = sourcePixels[lastColumnIndex, 0]; + for (int i = 0; i < height; i++) { - // check first and last column, these are known: - Assert.Equal(Rgba32.Red, sourcePixels[0, i]); - Assert.Equal(Rgba32.Yellow, sourcePixels[lastColumnIndex, i]); + // check first and last column: + Assert.Equal(columnColor0, sourcePixels[0, i]); + Assert.Equal(lastColumnColor, sourcePixels[lastColumnIndex, i]); // check the random colors: - Assert.Equal(columnColor23, sourcePixels[23, i]); + Assert.True(columnColor23 == sourcePixels[23, i], $"at {i}"); Assert.Equal(columnColor42, sourcePixels[42, i]); Assert.Equal(columnColor333, sourcePixels[333, i]); } @@ -150,15 +153,19 @@ namespace SixLabors.ImageSharp.Tests.Drawing using (PixelAccessor sourcePixels = image.Lock()) { + Rgba32 firstRowColor = sourcePixels[0, 0]; + Rgba32 columnColor23 = sourcePixels[0, 23]; Rgba32 columnColor42 = sourcePixels[0, 42]; Rgba32 columnColor333 = sourcePixels[0, 333]; + Rgba32 lastRowColor = sourcePixels[0, lastRowIndex]; + for (int i = 0; i < width; i++) { // check first and last column, these are known: - Assert.Equal(Rgba32.Red, sourcePixels[i, 0]); - Assert.Equal(Rgba32.Yellow, sourcePixels[i, lastRowIndex]); + Assert.Equal(firstRowColor, sourcePixels[i, 0]); + Assert.Equal(lastRowColor, sourcePixels[i, lastRowIndex]); // check the random colors: Assert.Equal(columnColor23, sourcePixels[i, 23]); From d977ecfeccfe7e484e4ded866861e9ebd32e7be4 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 21 Apr 2018 23:07:46 +0200 Subject: [PATCH 17/43] #542: rename file to match other files in the solution (include generic) --- ...LinearGradientBrush.cs => LinearGradientBrush{TPixel}.cs} | 5 +++++ 1 file changed, 5 insertions(+) rename src/ImageSharp.Drawing/Processing/Drawing/Brushes/{LinearGradientBrush.cs => LinearGradientBrush{TPixel}.cs} (96%) diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush{TPixel}.cs similarity index 96% rename from src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush.cs rename to src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush{TPixel}.cs index 9bd56f56b..c00ecb193 100644 --- a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush.cs +++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush{TPixel}.cs @@ -238,6 +238,11 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes { base.Apply(scanline, x, y); + // TODO: we should at least(!) speed up the x=0 and y=0 special cases. + // But in fact that could be done by special case Applicators directly: + // - horizontal would apply a precalc. row independent of given row, + // - vertical would get the color of the row once and fill the whole line. + // Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); // MemoryManager memoryManager = this.Target.MemoryManager; // using (IBuffer amountBuffer = memoryManager.Allocate(scanline.Length)) From d43c70acb5c0d3330dd2e896bb3f900eaaee743a Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 22 Apr 2018 12:50:17 +0200 Subject: [PATCH 18/43] fix documentation typo --- src/ImageSharp.Drawing/Processing/Drawing/FillPathExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp.Drawing/Processing/Drawing/FillPathExtensions.cs b/src/ImageSharp.Drawing/Processing/Drawing/FillPathExtensions.cs index 36eef8d63..4273fd8be 100644 --- a/src/ImageSharp.Drawing/Processing/Drawing/FillPathExtensions.cs +++ b/src/ImageSharp.Drawing/Processing/Drawing/FillPathExtensions.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing public static class FillPathExtensions { /// - /// Flood fills the image in the shape of the provided polygon with the specified brush.. + /// Flood fills the image in the shape of the provided polygon with the specified brush. /// /// The type of the color. /// The image this method extends. From 72ae5fa6ba7f5232bbff5b8c2ca3667c8f4b6b8a Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 22 Apr 2018 13:14:03 +0200 Subject: [PATCH 19/43] #542: refactor to prepare for other gradients - move to GradientBrushes namespace - add abstract base class for Gradient Brushes, that implements the GetGradientSegment implementtion and the color picking. --- .../AbstractGradientBrush{TPixel}.cs | 132 ++++++++++++++++++ .../Brushes/GradientBrushes/ColorStop.cs | 36 +++++ .../LinearGradientBrush{TPixel}.cs | 130 +++-------------- .../Drawing/FillLinearGradientBrushTests.cs | 37 ++--- 4 files changed, 204 insertions(+), 131 deletions(-) create mode 100644 src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/AbstractGradientBrush{TPixel}.cs create mode 100644 src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/ColorStop.cs rename src/ImageSharp.Drawing/Processing/Drawing/Brushes/{ => GradientBrushes}/LinearGradientBrush{TPixel}.cs (54%) diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/AbstractGradientBrush{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/AbstractGradientBrush{TPixel}.cs new file mode 100644 index 000000000..2939aed7d --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/AbstractGradientBrush{TPixel}.cs @@ -0,0 +1,132 @@ +using System.Numerics; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.PixelFormats.PixelBlenders; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes +{ + /// + /// Base class for Gradient brushes + /// + /// The pixel format + public abstract class AbstractGradientBrush : IBrush + where TPixel : struct, IPixel + { + /// + /// The gradient colors. + protected AbstractGradientBrush(params ColorStop[] colorStops) + { + this.ColorStops = colorStops; + } + + /// + /// Gets the list of color stops for this gradient. + /// + protected ColorStop[] ColorStops { get; } + + /// + public abstract BrushApplicator CreateApplicator( + ImageFrame source, + RectangleF region, + GraphicsOptions options); + + /// + /// Base class for gradient brush applicators + /// + protected abstract class AbstractGradientBrushApplicator : BrushApplicator + { + private readonly ColorStop[] colorStops; + + /// + /// Initializes a new instance of the class. + /// + /// The target. + /// The options. + /// an array of color stops sorted by their position. + /// TODO: use region, compare with other Brushes for reference + protected AbstractGradientBrushApplicator( + ImageFrame target, + GraphicsOptions options, + ColorStop[] colorStops, + RectangleF region) + : base(target, options) + { + this.colorStops = colorStops; // TODO: requires colorStops to be sorted by position - should that be checked? + } + + /// + /// Base implementation of the indexer for gradients + /// (follows the facade pattern, using abstract methods) + /// + /// X coordinate of the Pixel. + /// Y coordinate of the Pixel. + internal override TPixel this[int x, int y] + { + get + { + float positionOnCompleteGradient = this.PositionOnGradient(x, y); + var (from, to) = this.GetGradientSegment(positionOnCompleteGradient); + + if (from.Color.Equals(to.Color)) + { + return from.Color; + } + else + { + var fromAsVector = from.Color.ToVector4(); + var toAsVector = to.Color.ToVector4(); + float onLocalGradient = (positionOnCompleteGradient - from.Ratio) / to.Ratio; + + // TODO: this should be changeble for different gradienting functions + Vector4 result = PorterDuffFunctions.Normal( + fromAsVector, + toAsVector, + onLocalGradient); + + TPixel resultColor = default; + resultColor.PackFromVector4(result); + return resultColor; + } + } + } + + /// + /// calculates the position on the gradient for a given pixel. + /// This method is abstract as it's content depends on the shape of the gradient. + /// + /// The x coordinate of the pixel + /// The y coordinate of the pixel + /// + /// The position the given pixel has on the gradient. + /// The position is not bound to the [0..1] interval. + /// Values outside of that interval may be treated differently, + /// e.g. for the enum. + /// + protected abstract float PositionOnGradient(int x, int y); + + private (ColorStop from, ColorStop to) GetGradientSegment( + float positionOnCompleteGradient) + { + var localGradientFrom = this.colorStops[0]; + ColorStop localGradientTo = default; + + // TODO: ensure colorStops has at least 2 items (technically 1 would be okay, but that's no gradient) + foreach (var colorStop in this.colorStops) + { + localGradientTo = colorStop; + + if (colorStop.Ratio > positionOnCompleteGradient) + { + // we're done here, so break it! + break; + } + + localGradientFrom = localGradientTo; + } + + return (localGradientFrom, localGradientTo); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/ColorStop.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/ColorStop.cs new file mode 100644 index 000000000..298af5cb5 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/ColorStop.cs @@ -0,0 +1,36 @@ +using System.Diagnostics; + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes +{ + /// + /// A struct that defines a single color stop. + /// + /// The pixel format. + [DebuggerDisplay("ColorStop({Ratio} -> {Color}")] + public struct ColorStop + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the struct. + /// + /// Where should it be? 0 is at the start, 1 at the end of the Gradient. + /// What color should be used at that point? + public ColorStop(float ratio, TPixel color) + { + this.Ratio = ratio; + this.Color = color; + } + + /// + /// Gets the point along the defined gradient axis. + /// + public float Ratio { get; } + + /// + /// Gets the color to be used. + /// + public TPixel Color { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/LinearGradientBrush{TPixel}.cs similarity index 54% rename from src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush{TPixel}.cs rename to src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/LinearGradientBrush{TPixel}.cs index c00ecb193..ba398995b 100644 --- a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/LinearGradientBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/LinearGradientBrush{TPixel}.cs @@ -1,89 +1,49 @@ using System; -using System.Diagnostics; -using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.PixelFormats.PixelBlenders; using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Processing.Drawing.Brushes +namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes { /// - /// Provides an implementation of a brush for painting gradients within areas. + /// Provides an implementation of a brush for painting linear gradients within areas. /// Supported right now: /// - a set of colors in relative distances to each other. - /// - two points to gradient along. /// /// The pixel format - public class LinearGradientBrush : IBrush + public class LinearGradientBrush : AbstractGradientBrush where TPixel : struct, IPixel { private readonly Point p1; private readonly Point p2; - private readonly ColorStop[] colorStops; - /// /// Initializes a new instance of the class. /// /// Start point /// End point - /// - /// A set of color keys and where they are. - /// The double should be in range [0..1] and is relative between p1 and p2. - /// TODO: what about the [0..1] restriction? is it necessary? If so, it should be checked, if not, it should be explained what happens for greater/smaller values. - /// - public LinearGradientBrush(Point p1, Point p2, params ColorStop[] colorStops) + /// + public LinearGradientBrush(Point p1, Point p2, params ColorStop[] colorStops) + : base(colorStops) { this.p1 = p1; this.p2 = p2; - this.colorStops = colorStops; } /// - public BrushApplicator CreateApplicator(ImageFrame source, RectangleF region, GraphicsOptions options) - => new LinearGradientBrushApplicator(source, this.p1, this.p2, this.colorStops, region, options); - - /// - /// A struct that defines a single color stop. - /// - [DebuggerDisplay("ColorStop({Ratio} -> {Color}")] - public struct ColorStop - { - /// - /// Initializes a new instance of the struct. - /// - /// Where should it be? 0 is at the start, 1 at the end of the . - /// What color should be used at that point? - public ColorStop(float ratio, TPixel color) - { - this.Ratio = ratio; - this.Color = color; - } - - /// - /// Gets the point along the defined gradient axis. - /// - public float Ratio { get; } - - /// - /// Gets the color to be used. - /// - public TPixel Color { get; } - } + public override BrushApplicator CreateApplicator(ImageFrame source, RectangleF region, GraphicsOptions options) + => new LinearGradientBrushApplicator(source, this.p1, this.p2, this.ColorStops, region, options); /// /// The linear gradient brush applicator. /// - private class LinearGradientBrushApplicator : BrushApplicator + private class LinearGradientBrushApplicator : AbstractGradientBrushApplicator { private readonly Point start; private readonly Point end; - private readonly ColorStop[] colorStops; - /// /// the vector along the gradient, x component /// @@ -127,14 +87,13 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes ImageFrame source, Point start, Point end, - ColorStop[] colorStops, - RectangleF region, // TODO: use region, compare with other Brushes for reference. + ColorStop[] colorStops, + RectangleF region, GraphicsOptions options) - : base(source, options) + : base(source, options, colorStops, region) { this.start = start; this.end = end; - this.colorStops = colorStops; // TODO: requires colorStops to be sorted by Item1! // the along vector: this.alongX = this.end.X - this.start.X; @@ -149,61 +108,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes this.length = (float)Math.Sqrt(this.alongsSquared); } - /// - /// Gets the color for a single pixel - /// - /// The x coordinate. - /// The y coordinate. - internal override TPixel this[int x, int y] - { - get - { - // the following formula is the result of the linear equation system that forms the vector. - // TODO: this formula should be abstracted as it's the only difference between linear and radial gradient! - float onCompleteGradient = this.RatioOnGradient(x, y); - - var localGradientFrom = this.colorStops[0]; - ColorStop localGradientTo = default; - - // TODO: ensure colorStops has at least 2 items (technically 1 would be okay, but that's no gradient) - foreach (var colorStop in this.colorStops) - { - localGradientTo = colorStop; - - if (colorStop.Ratio > onCompleteGradient) - { - // we're done here, so break it! - break; - } - - localGradientFrom = localGradientTo; - } - - TPixel resultColor = default; - if (localGradientFrom.Color.Equals(localGradientTo.Color)) - { - resultColor = localGradientFrom.Color; - } - else - { - var fromAsVector = localGradientFrom.Color.ToVector4(); - var toAsVector = localGradientTo.Color.ToVector4(); - float onLocalGradient = (onCompleteGradient - localGradientFrom.Ratio) / localGradientTo.Ratio; // TODO: - - Vector4 result = PorterDuffFunctions.Normal( - fromAsVector, - toAsVector, - onLocalGradient); - - // TODO: when resultColor is a struct, what does PackFromVector4 do here? - resultColor.PackFromVector4(result); - } - - return resultColor; - } - } - - private float RatioOnGradient(int x, int y) + protected override float PositionOnGradient(int x, int y) { if (this.acrossX == 0) { @@ -234,6 +139,10 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes } } + public override void Dispose() + { + } + internal override void Apply(Span scanline, int x, int y) { base.Apply(scanline, x, y); @@ -257,11 +166,6 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes // this.Blender.Blend(memoryManager, destinationRow, destinationRow, this.Colors.Span, amountSpan); // } } - - /// - public override void Dispose() - { - } } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs index c95b165f8..b9d37d8e8 100644 --- a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs @@ -2,11 +2,12 @@ // Licensed under the Apache License, Version 2.0. using System.Linq; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Drawing.Brushes; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Drawing; +using SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes; + using Xunit; namespace SixLabors.ImageSharp.Tests.Drawing @@ -23,8 +24,8 @@ namespace SixLabors.ImageSharp.Tests.Drawing new LinearGradientBrush( new SixLabors.Primitives.Point(0, 0), new SixLabors.Primitives.Point(10, 0), - new LinearGradientBrush.ColorStop(0, Rgba32.Red), - new LinearGradientBrush.ColorStop(1, Rgba32.Red)); + new ColorStop(0, Rgba32.Red), + new ColorStop(1, Rgba32.Red)); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); image.Save($"{path}/UnicolorGradient.png"); @@ -53,8 +54,8 @@ namespace SixLabors.ImageSharp.Tests.Drawing new LinearGradientBrush( new SixLabors.Primitives.Point(0, 0), new SixLabors.Primitives.Point(500, 0), - new LinearGradientBrush.ColorStop(0, Rgba32.Red), - new LinearGradientBrush.ColorStop(1, Rgba32.Yellow)); + new ColorStop(0, Rgba32.Red), + new ColorStop(1, Rgba32.Yellow)); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); image.Save($"{path}/horizontalRedToYellow.png"); @@ -97,16 +98,16 @@ namespace SixLabors.ImageSharp.Tests.Drawing Assert.True(pattern.Length > 0); // create the input pattern: 0, followed by each of the arguments twice, followed by 1.0 - toggling black and white. - LinearGradientBrush.ColorStop[] colorStops = - Enumerable.Repeat(new LinearGradientBrush.ColorStop(0, Rgba32.Black), 1) + ColorStop[] colorStops = + Enumerable.Repeat(new ColorStop(0, Rgba32.Black), 1) .Concat( pattern .SelectMany((f, index) => new[] { - new LinearGradientBrush.ColorStop(f, index % 2 == 0 ? Rgba32.Black : Rgba32.White), - new LinearGradientBrush.ColorStop(f, index % 2 == 0 ? Rgba32.White : Rgba32.Black) + new ColorStop(f, index % 2 == 0 ? Rgba32.Black : Rgba32.White), + new ColorStop(f, index % 2 == 0 ? Rgba32.White : Rgba32.Black) })) - .Concat(Enumerable.Repeat(new LinearGradientBrush.ColorStop(1, pattern.Length % 2 == 0 ? Rgba32.Black : Rgba32.White), 1)) + .Concat(Enumerable.Repeat(new ColorStop(1, pattern.Length % 2 == 0 ? Rgba32.Black : Rgba32.White), 1)) .ToArray(); string path = TestEnvironment.CreateOutputDirectory("Fill", "LinearGradientBrush"); @@ -145,8 +146,8 @@ namespace SixLabors.ImageSharp.Tests.Drawing new LinearGradientBrush( new SixLabors.Primitives.Point(0, 0), new SixLabors.Primitives.Point(0, 500), - new LinearGradientBrush.ColorStop(0, Rgba32.Red), - new LinearGradientBrush.ColorStop(1, Rgba32.Yellow)); + new ColorStop(0, Rgba32.Red), + new ColorStop(1, Rgba32.Yellow)); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); image.Save($"{path}/verticalRedToYellow.png"); @@ -154,7 +155,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing using (PixelAccessor sourcePixels = image.Lock()) { Rgba32 firstRowColor = sourcePixels[0, 0]; - + Rgba32 columnColor23 = sourcePixels[0, 23]; Rgba32 columnColor42 = sourcePixels[0, 42]; Rgba32 columnColor333 = sourcePixels[0, 333]; @@ -193,8 +194,8 @@ namespace SixLabors.ImageSharp.Tests.Drawing new LinearGradientBrush( new SixLabors.Primitives.Point(startX, startY), new SixLabors.Primitives.Point(endX, endY), - new LinearGradientBrush.ColorStop(0, Rgba32.Red), - new LinearGradientBrush.ColorStop(1, Rgba32.Yellow)); + new ColorStop(0, Rgba32.Red), + new ColorStop(1, Rgba32.Yellow)); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); image.Save($"{path}/diagonalRedToYellowFrom{startX}_{startY}.png"); @@ -233,10 +234,10 @@ namespace SixLabors.ImageSharp.Tests.Drawing Rgba32.Red }; - var colorStops = new LinearGradientBrush.ColorStop[stopPositions.Length]; + var colorStops = new ColorStop[stopPositions.Length]; for (int i = 0; i < stopPositions.Length; i++) { - colorStops[i] = new LinearGradientBrush.ColorStop( + colorStops[i] = new ColorStop( stopPositions[i], colors[stopColorCodes[i]]); } From 94f1698807009d50ef6abd9fce46b7df28d6cc27 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 22 Apr 2018 14:16:24 +0200 Subject: [PATCH 20/43] implement radial gradient brush. --- .../GradientBrushes/RadialGradientBrush.cs | 100 ++++++++++++++++++ .../Drawing/FillRadialGradientBrushTests.cs | 87 +++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/RadialGradientBrush.cs create mode 100644 tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/RadialGradientBrush.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/RadialGradientBrush.cs new file mode 100644 index 000000000..60040ab3c --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/RadialGradientBrush.cs @@ -0,0 +1,100 @@ +using System; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes +{ + /// + /// A Circular Gradient Brush, defined by center point and radius. + /// + /// The pixel format. + public class RadialGradientBrush : AbstractGradientBrush + where TPixel : struct, IPixel + { + private readonly Point center; + + private readonly float radius; + + /// + /// The center of the circular gradient and 0 for the color stops. + /// The radius of the circular gradient and 1 for the color stops. + /// the color stops as defined in base class. + public RadialGradientBrush( + Point center, + float radius, + params ColorStop[] colorStops) + : base(colorStops) + { + this.center = center; + this.radius = radius; + } + + /// + public override BrushApplicator CreateApplicator( + ImageFrame source, + RectangleF region, + GraphicsOptions options) => + new RadialGradientBrushApplicator( + source, + options, + this.center, + this.radius, + this.ColorStops, + region); + + /// + protected class RadialGradientBrushApplicator : AbstractGradientBrushApplicator + { + private readonly Point center; + + private readonly float radius; + + /// + /// Initializes a new instance of the class. + /// + /// The target image + /// The options. + /// Center point of the gradient. + /// Radius of the gradient. + /// Definition of colors. + /// TODO ! + public RadialGradientBrushApplicator( + ImageFrame target, + GraphicsOptions options, + Point center, + float radius, + ColorStop[] colorStops, + RectangleF region) + : base(target, options, colorStops, region) + { + this.center = center; + this.radius = radius; + } + + /// + public override void Dispose() + { + } + + /// + /// As this is a circular gradient, the position on the gradient is based on + /// the distance of the point to the center. + /// + /// The X coordinate of the target pixel. + /// The Y coordinate of the target pixel. + /// the position on the color gradient. + protected override float PositionOnGradient(int x, int y) + { + float distance = (float)Math.Sqrt(Math.Pow(this.center.X - x, 2) + Math.Pow(this.center.Y - y, 2)); + return distance / this.radius; + } + + internal override void Apply(Span scanline, int x, int y) + { + // TODO: each row is symmetric across center, so we can calculate half of it and mirror it to improve performance. + base.Apply(scanline, x, y); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs new file mode 100644 index 000000000..24a36a252 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs @@ -0,0 +1,87 @@ +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Drawing; +using SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Drawing +{ + public class FillRadialGradientBrushTests : FileTestBase + { + [Fact] + public void RadialGradientBrushWithEqualColorsReturnsUnicolorImage() + { + string path = TestEnvironment.CreateOutputDirectory("Fill", "RadialGradientBrush"); + using (var image = new Image(200, 200)) + { + RadialGradientBrush unicolorRadialGradientBrush = + new RadialGradientBrush( + new SixLabors.Primitives.Point(0, 0), + 100, + new ColorStop(0, Rgba32.Red), + new ColorStop(1, Rgba32.Red)); + + image.Mutate(x => x.Fill(unicolorRadialGradientBrush)); + image.Save($"{path}/UnicolorGradient.png"); + + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Rgba32.Red, sourcePixels[0, 0]); + Assert.Equal(Rgba32.Red, sourcePixels[9, 9]); + Assert.Equal(Rgba32.Red, sourcePixels[5, 5]); + Assert.Equal(Rgba32.Red, sourcePixels[3, 8]); + } + } + } + + [Theory] + [InlineData(250, 250)] + [InlineData(0, 0)] + [InlineData(250, 0)] + [InlineData(0, 250)] + [InlineData(-100, 250)] + public void RadialGradientBrushWithDifferentCentersReturnsImage( + int centerX, + int centerY) + { + int width = 500; + + string path = TestEnvironment.CreateOutputDirectory("Fill", "RadialGradientBrush"); + using (var image = new Image(width, width)) + { + RadialGradientBrush brush = + new RadialGradientBrush( + new SixLabors.Primitives.Point(centerX, centerY), + width / 2f, + new ColorStop(0, Rgba32.Red), + new ColorStop(1, Rgba32.Yellow)); + + image.Mutate(x => x.Fill(brush)); + image.Save($"{path}/CenterAt{centerX}_{centerY}.png"); + + // using (PixelAccessor sourcePixels = image.Lock()) + // { + // Rgba32 columnColor0 = sourcePixels[0, 0]; + // Rgba32 columnColor23 = sourcePixels[23, 0]; + // Rgba32 columnColor42 = sourcePixels[42, 0]; + // Rgba32 columnColor333 = sourcePixels[333, 0]; + // + // Rgba32 lastColumnColor = sourcePixels[lastColumnIndex, 0]; + // + // for (int i = 0; i < width; i++) + // { + // // check first and last column: + // Assert.Equal(columnColor0, sourcePixels[0, i]); + // Assert.Equal(lastColumnColor, sourcePixels[lastColumnIndex, i]); + // + // // check the random colors: + // Assert.True(columnColor23 == sourcePixels[23, i], $"at {i}"); + // Assert.Equal(columnColor42, sourcePixels[42, i]); + // Assert.Equal(columnColor333, sourcePixels[333, i]); + // } + // } + } + } + } +} \ No newline at end of file From e837349952ac4828bd747c27423ee53981e37ba6 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 26 Apr 2018 22:59:53 +0200 Subject: [PATCH 21/43] first implementation of an elliptical gradient brush --- .../GradientBrushes/EllipticGradientBrush.cs | 143 ++++++++++++++++++ .../Drawing/FillEllipticGradientBrushTest.cs | 124 +++++++++++++++ 2 files changed, 267 insertions(+) create mode 100644 src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush.cs create mode 100644 tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush.cs new file mode 100644 index 000000000..21b581397 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush.cs @@ -0,0 +1,143 @@ +using System; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes +{ + /// + /// Gradient Brush with elliptic shape. + /// The ellipse is defined by a center point, + /// a point on the longest extension of the ellipse and + /// the ratio between longest and shortest extension. + /// + /// The Pixel format that is used. + public class EllipticGradientBrush : AbstractGradientBrush + where TPixel : struct, IPixel + { + private readonly Point center; + + private readonly Point referenceAxisEnd; + + private readonly float axisRatio; + + /// + /// The center of the elliptical gradient and 0 for the color stops. + /// The end point of the reference axis of the ellipse. + /// + /// The ratio of the axis widths. + /// The second axis' is perpendicular to the reference axis and + /// it's length is the reference axis' length multiplied by this factor. + /// + /// the color stops as defined in base class. + public EllipticGradientBrush( + Point center, + Point referenceAxisEnd, + float axisRatio, + params ColorStop[] colorStops) + : base(colorStops) + { + this.center = center; + this.referenceAxisEnd = referenceAxisEnd; + this.axisRatio = axisRatio; + } + + /// + public override BrushApplicator CreateApplicator( + ImageFrame source, + RectangleF region, + GraphicsOptions options) => + new RadialGradientBrushApplicator( + source, + options, + this.center, + this.referenceAxisEnd, + this.axisRatio, + this.ColorStops, + region); + + /// + protected class RadialGradientBrushApplicator : AbstractGradientBrushApplicator + { + private readonly Point center; + + private readonly Point referenceAxisEnd; + + private readonly float axisRatio; + + private readonly double rotation; + + private readonly float referenceRadius; + + private readonly float secondRadius; + + /// + /// Initializes a new instance of the class. + /// + /// The target image + /// The options + /// Center of the ellipse + /// Point on one angular points of the ellipse. + /// + /// Ratio of the axis length's. Used to determine the length of the second axis, + /// the first is defined by and . + /// Definition of colors + /// TODO ! + public RadialGradientBrushApplicator( + ImageFrame target, + GraphicsOptions options, + Point center, + Point referenceAxisEnd, + float axisRatio, + ColorStop[] colorStops, + RectangleF region) + : base(target, options, colorStops, region) + { + this.center = center; + this.referenceAxisEnd = referenceAxisEnd; + this.axisRatio = axisRatio; + this.rotation = this.AngleBetween( + this.center, + new PointF(this.center.X + 1, this.center.Y), + this.referenceAxisEnd); + this.referenceRadius = this.DistanceBetween(this.center, this.referenceAxisEnd); + this.secondRadius = this.referenceRadius * this.axisRatio; + } + + /// + public override void Dispose() + { + } + + /// + protected override float PositionOnGradient(int xt, int yt) + { + float x0 = xt - this.center.X; // TODO: rotate this point after translation + float y0 = yt - this.center.Y; + + float x = (float)((x0 * Math.Cos(this.rotation)) - (y0 * Math.Sin(this.rotation))); // TODO: store sin and cos of rotation as constant! + float y = (float)((x0 * Math.Sin(this.rotation)) + (y0 * Math.Cos(this.rotation))); + + var inBoundaryChecker = ((x * x) / (this.referenceRadius * this.referenceRadius)) + + ((y * y) / (this.secondRadius * this.secondRadius)); + + return inBoundaryChecker; + } + + private float AngleBetween(PointF junction, PointF a, PointF b) + { + var vA = a - junction; + var vB = b - junction; + return (float)(Math.Atan2(vB.Y, vB.X) + - Math.Atan2(vA.Y, vA.X)); + } + + private float DistanceBetween( + PointF p1, + PointF p2) + { + return (float)Math.Sqrt(Math.Pow(p1.X - p2.X, 2) + Math.Pow(p1.Y - p2.Y, 2)); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs b/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs new file mode 100644 index 000000000..c789e3e46 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs @@ -0,0 +1,124 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Drawing; +using SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Drawing +{ + public class FillEllipticGradientBrushTests : FileTestBase + { + [Fact] + public void EllipticGradientBrushWithEqualColorsAndReturnsUnicolorImage() + { + string path = TestEnvironment.CreateOutputDirectory("Fill", "EllipticGradientBrush"); + using (var image = new Image(10, 10)) + { + EllipticGradientBrush unicolorLinearGradientBrush = + new EllipticGradientBrush( + new SixLabors.Primitives.Point(0, 0), + new SixLabors.Primitives.Point(10, 0), + 1.0f, + new ColorStop(0, Rgba32.Red), + new ColorStop(1, Rgba32.Red)); + + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + image.Save($"{path}/UnicolorCircleGradient.png"); + + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Rgba32.Red, sourcePixels[0, 0]); + Assert.Equal(Rgba32.Red, sourcePixels[9, 9]); + Assert.Equal(Rgba32.Red, sourcePixels[5, 5]); + Assert.Equal(Rgba32.Red, sourcePixels[3, 8]); + } + } + } + + [Theory] + [InlineData(0.1)] + [InlineData(0.4)] + [InlineData(0.8)] + [InlineData(1.0)] + [InlineData(1.2)] + [InlineData(1.6)] + [InlineData(2.0)] + public void EllipticGradientBrushProducesAxisParallelEllipsesWithDifferentRatio( + float ratio) + { + string path = TestEnvironment.CreateOutputDirectory("Fill", "EllipticGradientBrush"); + using (var image = new Image(1000, 1000)) + { + EllipticGradientBrush unicolorLinearGradientBrush = + new EllipticGradientBrush( + new SixLabors.Primitives.Point(500, 500), + new SixLabors.Primitives.Point(500, 750), + ratio, + new ColorStop(0, Rgba32.Yellow), + new ColorStop(1, Rgba32.Red), + new ColorStop(1, Rgba32.Black)); + + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + image.Save($"{path}/Ellipsis{ratio}.png"); + } + } + + [Theory] + [InlineData(0.1, 0)] + [InlineData(0.4, 0)] + [InlineData(0.8, 0)] + [InlineData(1.0, 0)] + + [InlineData(0.1, 45)] + [InlineData(0.4, 45)] + [InlineData(0.8, 45)] + [InlineData(1.0, 45)] + + [InlineData(0.1, 90)] + [InlineData(0.4, 90)] + [InlineData(0.8, 90)] + [InlineData(1.0, 90)] + + [InlineData(0.1, 30)] + [InlineData(0.4, 30)] + [InlineData(0.8, 30)] + [InlineData(1.0, 30)] + public void EllipticGradientBrushProducesRotatedEllipsesWithDifferentRatio( + float ratio, + float rotationInDegree) + { + var center = new SixLabors.Primitives.Point(500, 500); + + var rotation = (Math.PI * rotationInDegree) / 180.0; + var cos = Math.Cos(rotation); + var sin = Math.Sin(rotation); + + int axisX = (int)((center.X * cos) - (center.Y * sin)); + int axisY = (int)((center.X * sin) + (center.Y * cos)); + + string path = TestEnvironment.CreateOutputDirectory("Fill", "EllipticGradientBrush"); + using (var image = new Image(1000, 1000)) + { + EllipticGradientBrush unicolorLinearGradientBrush = + new EllipticGradientBrush( + center, + new SixLabors.Primitives.Point(axisX, axisY), + ratio, + new ColorStop(0, Rgba32.Yellow), + new ColorStop(1, Rgba32.Red), + new ColorStop(1, Rgba32.Black)); + + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + image.Save($"{path}/Ellipsis{ratio}_rot{rotationInDegree}°.png"); + } + } + } +} \ No newline at end of file From a6e04024f80ee098b9b8c7807f17bf2bb1b5b743 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 26 Apr 2018 19:36:29 +0200 Subject: [PATCH 22/43] optimization of EllipticGradientBrush - precalculate anything that's independent of the pixel coordinate --- .../GradientBrushes/EllipticGradientBrush.cs | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush.cs index 21b581397..9be54358c 100644 --- a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush.cs +++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush.cs @@ -71,6 +71,14 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes private readonly float secondRadius; + private readonly float cosRotation; + + private readonly float sinRotation; + + private readonly float secondRadiusSquared; + + private readonly float referenceRadiusSquared; + /// /// Initializes a new instance of the class. /// @@ -102,6 +110,13 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes this.referenceAxisEnd); this.referenceRadius = this.DistanceBetween(this.center, this.referenceAxisEnd); this.secondRadius = this.referenceRadius * this.axisRatio; + + this.referenceRadiusSquared = this.referenceRadius * this.referenceRadius; + this.secondRadiusSquared = this.secondRadius * this.secondRadius; + + this.sinRotation = (float)Math.Sin(this.rotation); + this.cosRotation = (float)Math.Cos(this.rotation); + } /// @@ -112,14 +127,17 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes /// protected override float PositionOnGradient(int xt, int yt) { - float x0 = xt - this.center.X; // TODO: rotate this point after translation + float x0 = xt - this.center.X; float y0 = yt - this.center.Y; - float x = (float)((x0 * Math.Cos(this.rotation)) - (y0 * Math.Sin(this.rotation))); // TODO: store sin and cos of rotation as constant! - float y = (float)((x0 * Math.Sin(this.rotation)) + (y0 * Math.Cos(this.rotation))); + float x = (x0 * this.cosRotation) - (y0 * this.sinRotation); + float y = (x0 * this.sinRotation) + (y0 * this.cosRotation); + + float xSquared = x * x; + float ySquared = y * y; - var inBoundaryChecker = ((x * x) / (this.referenceRadius * this.referenceRadius)) - + ((y * y) / (this.secondRadius * this.secondRadius)); + var inBoundaryChecker = (xSquared / this.referenceRadiusSquared) + + (ySquared / this.secondRadiusSquared); return inBoundaryChecker; } From 75dab524dde05aabaa30a794b336a505594c2c7f Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 26 Apr 2018 19:39:01 +0200 Subject: [PATCH 23/43] improve performance on distance calculation --- .../Brushes/GradientBrushes/EllipticGradientBrush.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush.cs index 9be54358c..8986853e2 100644 --- a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush.cs +++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush.cs @@ -154,7 +154,12 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes PointF p1, PointF p2) { - return (float)Math.Sqrt(Math.Pow(p1.X - p2.X, 2) + Math.Pow(p1.Y - p2.Y, 2)); + float dX = p1.X - p2.X; + float dXsquared = dX * dX; + + float dY = p1.Y - p2.Y; + float dYsquared = dY * dY; + return (float)Math.Sqrt(dXsquared + dYsquared); } } } From d29b6017abf4a7fcc2395fc06230f90e3321ede7 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 27 Apr 2018 00:04:32 +0200 Subject: [PATCH 24/43] implement GradientRepetitionModes --- .../AbstractGradientBrush{TPixel}.cs | 52 +++++++++++++++-- .../GradientBrushes/EllipticGradientBrush.cs | 13 +++-- .../GradientBrushes/GradientRepetitionMode.cs | 35 ++++++++++++ .../LinearGradientBrush{TPixel}.cs | 17 ++++-- .../GradientBrushes/RadialGradientBrush.cs | 12 ++-- .../Drawing/FillEllipticGradientBrushTest.cs | 5 +- .../Drawing/FillLinearGradientBrushTests.cs | 56 +++++++++++++++++++ .../Drawing/FillRadialGradientBrushTests.cs | 2 + 8 files changed, 168 insertions(+), 24 deletions(-) create mode 100644 src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/GradientRepetitionMode.cs diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/AbstractGradientBrush{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/AbstractGradientBrush{TPixel}.cs index 2939aed7d..c963c9831 100644 --- a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/AbstractGradientBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/AbstractGradientBrush{TPixel}.cs @@ -1,4 +1,5 @@ -using System.Numerics; +using System; +using System.Numerics; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats.PixelBlenders; @@ -14,12 +15,21 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes where TPixel : struct, IPixel { /// + /// Defines how the colors are repeated beyond the interval [0..1] /// The gradient colors. - protected AbstractGradientBrush(params ColorStop[] colorStops) + protected AbstractGradientBrush( + GradientRepetitionMode repetitionMode, + params ColorStop[] colorStops) { + this.RepetitionMode = repetitionMode; this.ColorStops = colorStops; } + /// + /// Gets how the colors are repeated beyond the interval [0..1]. + /// + protected GradientRepetitionMode RepetitionMode { get; } + /// /// Gets the list of color stops for this gradient. /// @@ -38,21 +48,24 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes { private readonly ColorStop[] colorStops; + private readonly GradientRepetitionMode repetitionMode; + /// /// Initializes a new instance of the class. /// /// The target. /// The options. - /// an array of color stops sorted by their position. - /// TODO: use region, compare with other Brushes for reference + /// An array of color stops sorted by their position. + /// Defines if and how the gradient should be repeated. protected AbstractGradientBrushApplicator( ImageFrame target, GraphicsOptions options, ColorStop[] colorStops, - RectangleF region) + GradientRepetitionMode repetitionMode) : base(target, options) { this.colorStops = colorStops; // TODO: requires colorStops to be sorted by position - should that be checked? + this.repetitionMode = repetitionMode; } /// @@ -66,6 +79,35 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes get { float positionOnCompleteGradient = this.PositionOnGradient(x, y); + + switch (this.repetitionMode) + { + case GradientRepetitionMode.None: + // do nothing. The following could be done, but is not necessary: + // onLocalGradient = Math.Min(0, Math.Max(1, onLocalGradient)); + break; + case GradientRepetitionMode.Repeat: + positionOnCompleteGradient = positionOnCompleteGradient % 1; + break; + case GradientRepetitionMode.Reflect: + positionOnCompleteGradient = positionOnCompleteGradient % 2; + if (positionOnCompleteGradient > 1) + { + positionOnCompleteGradient = 2 - positionOnCompleteGradient; + } + + break; + case GradientRepetitionMode.DontFill: + if (positionOnCompleteGradient > 1 || positionOnCompleteGradient < 0) + { + return NamedColors.Transparent; + } + + break; + default: + throw new ArgumentOutOfRangeException(); + } + var (from, to) = this.GetGradientSegment(positionOnCompleteGradient); if (from.Color.Equals(to.Color)) diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush.cs index 8986853e2..471553318 100644 --- a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush.cs +++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush.cs @@ -29,13 +29,15 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes /// The second axis' is perpendicular to the reference axis and /// it's length is the reference axis' length multiplied by this factor. /// + /// Defines how the colors of the gradients are repeated. /// the color stops as defined in base class. public EllipticGradientBrush( Point center, Point referenceAxisEnd, float axisRatio, + GradientRepetitionMode repetitionMode, params ColorStop[] colorStops) - : base(colorStops) + : base(repetitionMode, colorStops) { this.center = center; this.referenceAxisEnd = referenceAxisEnd; @@ -54,7 +56,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes this.referenceAxisEnd, this.axisRatio, this.ColorStops, - region); + this.RepetitionMode); /// protected class RadialGradientBrushApplicator : AbstractGradientBrushApplicator @@ -90,7 +92,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes /// Ratio of the axis length's. Used to determine the length of the second axis, /// the first is defined by and . /// Definition of colors - /// TODO ! + /// Defines how the gradient colors are repeated. public RadialGradientBrushApplicator( ImageFrame target, GraphicsOptions options, @@ -98,8 +100,8 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes Point referenceAxisEnd, float axisRatio, ColorStop[] colorStops, - RectangleF region) - : base(target, options, colorStops, region) + GradientRepetitionMode repetitionMode) + : base(target, options, colorStops, repetitionMode) { this.center = center; this.referenceAxisEnd = referenceAxisEnd; @@ -116,7 +118,6 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes this.sinRotation = (float)Math.Sin(this.rotation); this.cosRotation = (float)Math.Cos(this.rotation); - } /// diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/GradientRepetitionMode.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/GradientRepetitionMode.cs new file mode 100644 index 000000000..2fdc7fca6 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/GradientRepetitionMode.cs @@ -0,0 +1,35 @@ +namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes +{ + /// + /// Modes to repeat a gradient. + /// + public enum GradientRepetitionMode + { + /// + /// don't repeat, keep the color of start and end beyond those points stable. + /// + None, + + /// + /// Repeat the gradient. + /// If it's a black-white gradient, with Repeat it will be Black->{gray}->White|Black->{gray}->White|... + /// + Repeat, + + /// + /// Reflect the gradient. + /// Similar to , but each other repetition uses inverse order of s. + /// Used on a Black-White gradient, Reflect leads to Black->{gray}->White->{gray}->White... + /// + Reflect, + + /// + /// With DontFill a gradient does not touch any pixel beyond it's borders. + /// For the this is beyond the orthogonal through start and end, + /// For the it's outside the polygon, + /// TODO For see cref="RadialGradientBrush{TPixel}"/> and it's beyond 1.0. + /// TODO: check documentation consistency according to 1.0 + /// + DontFill + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/LinearGradientBrush{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/LinearGradientBrush{TPixel}.cs index ba398995b..8dbc4df90 100644 --- a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/LinearGradientBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/LinearGradientBrush{TPixel}.cs @@ -23,9 +23,14 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes /// /// Start point /// End point + /// defines how colors are repeated. /// - public LinearGradientBrush(Point p1, Point p2, params ColorStop[] colorStops) - : base(colorStops) + public LinearGradientBrush( + Point p1, + Point p2, + GradientRepetitionMode repetitionMode, + params ColorStop[] colorStops) + : base(repetitionMode, colorStops) { this.p1 = p1; this.p2 = p2; @@ -33,7 +38,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes /// public override BrushApplicator CreateApplicator(ImageFrame source, RectangleF region, GraphicsOptions options) - => new LinearGradientBrushApplicator(source, this.p1, this.p2, this.ColorStops, region, options); + => new LinearGradientBrushApplicator(source, this.p1, this.p2, this.ColorStops, this.RepetitionMode, options); /// /// The linear gradient brush applicator. @@ -81,16 +86,16 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes /// start point of the gradient /// end point of the gradient /// tuple list of colors and their respective position between 0 and 1 on the line - /// the region, copied from SolidColorBrush, not sure if necessary! TODO + /// defines how the gradient colors are repeated. /// the graphics options public LinearGradientBrushApplicator( ImageFrame source, Point start, Point end, ColorStop[] colorStops, - RectangleF region, + GradientRepetitionMode repetitionMode, GraphicsOptions options) - : base(source, options, colorStops, region) + : base(source, options, colorStops, repetitionMode) { this.start = start; this.end = end; diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/RadialGradientBrush.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/RadialGradientBrush.cs index 60040ab3c..53b34e233 100644 --- a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/RadialGradientBrush.cs +++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/RadialGradientBrush.cs @@ -19,12 +19,14 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes /// /// The center of the circular gradient and 0 for the color stops. /// The radius of the circular gradient and 1 for the color stops. + /// Defines how the colors in the gradient are repeated. /// the color stops as defined in base class. public RadialGradientBrush( Point center, float radius, + GradientRepetitionMode repetitionMode, params ColorStop[] colorStops) - : base(colorStops) + : base(repetitionMode, colorStops) { this.center = center; this.radius = radius; @@ -41,7 +43,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes this.center, this.radius, this.ColorStops, - region); + this.RepetitionMode); /// protected class RadialGradientBrushApplicator : AbstractGradientBrushApplicator @@ -58,15 +60,15 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes /// Center point of the gradient. /// Radius of the gradient. /// Definition of colors. - /// TODO ! + /// How the colors are repeated beyond the first gradient. public RadialGradientBrushApplicator( ImageFrame target, GraphicsOptions options, Point center, float radius, ColorStop[] colorStops, - RectangleF region) - : base(target, options, colorStops, region) + GradientRepetitionMode repetitionMode) + : base(target, options, colorStops, repetitionMode) { this.center = center; this.radius = radius; diff --git a/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs b/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs index c789e3e46..1ec27d762 100644 --- a/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs +++ b/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs @@ -2,8 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Linq; -using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; @@ -27,6 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing new SixLabors.Primitives.Point(0, 0), new SixLabors.Primitives.Point(10, 0), 1.0f, + GradientRepetitionMode.None, new ColorStop(0, Rgba32.Red), new ColorStop(1, Rgba32.Red)); @@ -62,6 +61,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing new SixLabors.Primitives.Point(500, 500), new SixLabors.Primitives.Point(500, 750), ratio, + GradientRepetitionMode.None, new ColorStop(0, Rgba32.Yellow), new ColorStop(1, Rgba32.Red), new ColorStop(1, Rgba32.Black)); @@ -112,6 +112,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing center, new SixLabors.Primitives.Point(axisX, axisY), ratio, + GradientRepetitionMode.None, new ColorStop(0, Rgba32.Yellow), new ColorStop(1, Rgba32.Red), new ColorStop(1, Rgba32.Black)); diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs index b9d37d8e8..d080896a0 100644 --- a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs @@ -24,6 +24,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing new LinearGradientBrush( new SixLabors.Primitives.Point(0, 0), new SixLabors.Primitives.Point(10, 0), + GradientRepetitionMode.None, new ColorStop(0, Rgba32.Red), new ColorStop(1, Rgba32.Red)); @@ -54,6 +55,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing new LinearGradientBrush( new SixLabors.Primitives.Point(0, 0), new SixLabors.Primitives.Point(500, 0), + GradientRepetitionMode.None, new ColorStop(0, Rgba32.Red), new ColorStop(1, Rgba32.Yellow)); @@ -84,6 +86,56 @@ namespace SixLabors.ImageSharp.Tests.Drawing } } + [Theory] + [InlineData(GradientRepetitionMode.DontFill)] + [InlineData(GradientRepetitionMode.None)] + [InlineData(GradientRepetitionMode.Repeat)] + [InlineData(GradientRepetitionMode.Reflect)] + public void HorizontalLinearGradientBrushWithDifferentRepetitionModesCreatesCorrectImages( + GradientRepetitionMode repetitionMode) + { + int width = 500; + int height = 10; + int lastColumnIndex = width - 1; + + string path = TestEnvironment.CreateOutputDirectory("Fill", "LinearGradientBrush"); + using (var image = new Image(width, height)) + { + LinearGradientBrush unicolorLinearGradientBrush = + new LinearGradientBrush( + new SixLabors.Primitives.Point(0, 0), + new SixLabors.Primitives.Point(50, 0), + repetitionMode, + new ColorStop(0, Rgba32.Red), + new ColorStop(1, Rgba32.Yellow)); + + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + image.Save($"{path}/horizontalRedToYellow_{repetitionMode}.png"); + + using (PixelAccessor sourcePixels = image.Lock()) + { + Rgba32 columnColor0 = sourcePixels[0, 0]; + Rgba32 columnColor23 = sourcePixels[23, 0]; + Rgba32 columnColor42 = sourcePixels[42, 0]; + Rgba32 columnColor333 = sourcePixels[333, 0]; + + Rgba32 lastColumnColor = sourcePixels[lastColumnIndex, 0]; + + for (int i = 0; i < height; i++) + { + // check first and last column: + Assert.Equal(columnColor0, sourcePixels[0, i]); + Assert.Equal(lastColumnColor, sourcePixels[lastColumnIndex, i]); + + // check the random colors: + Assert.True(columnColor23 == sourcePixels[23, i], $"at {i}"); + Assert.Equal(columnColor42, sourcePixels[42, i]); + Assert.Equal(columnColor333, sourcePixels[333, i]); + } + } + } + } + [Theory] [InlineData(new[] { 0.5f })] [InlineData(new[] { 0.2f, 0.4f, 0.6f, 0.8f })] @@ -117,6 +169,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing new LinearGradientBrush( new SixLabors.Primitives.Point(0, 0), new SixLabors.Primitives.Point(width, 0), + GradientRepetitionMode.None, colorStops); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); @@ -146,6 +199,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing new LinearGradientBrush( new SixLabors.Primitives.Point(0, 0), new SixLabors.Primitives.Point(0, 500), + GradientRepetitionMode.None, new ColorStop(0, Rgba32.Red), new ColorStop(1, Rgba32.Yellow)); @@ -194,6 +248,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing new LinearGradientBrush( new SixLabors.Primitives.Point(startX, startY), new SixLabors.Primitives.Point(endX, endY), + GradientRepetitionMode.None, new ColorStop(0, Rgba32.Red), new ColorStop(1, Rgba32.Yellow)); @@ -251,6 +306,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing new LinearGradientBrush( new SixLabors.Primitives.Point(startX, startY), new SixLabors.Primitives.Point(endX, endY), + GradientRepetitionMode.None, colorStops); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); diff --git a/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs index 24a36a252..7229e7041 100644 --- a/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs @@ -19,6 +19,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing new RadialGradientBrush( new SixLabors.Primitives.Point(0, 0), 100, + GradientRepetitionMode.None, new ColorStop(0, Rgba32.Red), new ColorStop(1, Rgba32.Red)); @@ -54,6 +55,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing new RadialGradientBrush( new SixLabors.Primitives.Point(centerX, centerY), width / 2f, + GradientRepetitionMode.None, new ColorStop(0, Rgba32.Red), new ColorStop(1, Rgba32.Yellow)); From c8d0256e8553ecec17480f51ae0402ff0b47ffa4 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 27 Apr 2018 00:20:32 +0200 Subject: [PATCH 25/43] remove not implemented Polygon Brush from documentation for now. --- .../Brushes/GradientBrushes/GradientRepetitionMode.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/GradientRepetitionMode.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/GradientRepetitionMode.cs index 2fdc7fca6..adbc26ed4 100644 --- a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/GradientRepetitionMode.cs +++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/GradientRepetitionMode.cs @@ -26,9 +26,8 @@ /// /// With DontFill a gradient does not touch any pixel beyond it's borders. /// For the this is beyond the orthogonal through start and end, - /// For the it's outside the polygon, - /// TODO For see cref="RadialGradientBrush{TPixel}"/> and it's beyond 1.0. - /// TODO: check documentation consistency according to 1.0 + /// TODO For the cref="PolygonalGradientBrush" it's outside the polygon, + /// For and it's beyond 1.0. /// DontFill } From 9e9875274da29168104b933ddccd026dfd227b25 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 1 May 2018 22:35:15 +0200 Subject: [PATCH 26/43] #542: apply change requests made by @tocsoft in code review. --- ...sh.cs => EllipticGradientBrush{TPixel}.cs} | 4 +-- .../LinearGradientBrush{TPixel}.cs | 28 ++----------------- .../GradientBrushes/RadialGradientBrush.cs | 4 +-- 3 files changed, 6 insertions(+), 30 deletions(-) rename src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/{EllipticGradientBrush.cs => EllipticGradientBrush{TPixel}.cs} (97%) diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush{TPixel}.cs similarity index 97% rename from src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush.cs rename to src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush{TPixel}.cs index 471553318..74effa861 100644 --- a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush.cs +++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush{TPixel}.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes /// the ratio between longest and shortest extension. /// /// The Pixel format that is used. - public class EllipticGradientBrush : AbstractGradientBrush + public sealed class EllipticGradientBrush : AbstractGradientBrush where TPixel : struct, IPixel { private readonly Point center; @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes this.RepetitionMode); /// - protected class RadialGradientBrushApplicator : AbstractGradientBrushApplicator + private sealed class RadialGradientBrushApplicator : AbstractGradientBrushApplicator { private readonly Point center; diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/LinearGradientBrush{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/LinearGradientBrush{TPixel}.cs index 8dbc4df90..6cfa4651b 100644 --- a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/LinearGradientBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/LinearGradientBrush{TPixel}.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes /// - a set of colors in relative distances to each other. /// /// The pixel format - public class LinearGradientBrush : AbstractGradientBrush + public sealed class LinearGradientBrush : AbstractGradientBrush where TPixel : struct, IPixel { private readonly Point p1; @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes /// /// The linear gradient brush applicator. /// - private class LinearGradientBrushApplicator : AbstractGradientBrushApplicator + private sealed class LinearGradientBrushApplicator : AbstractGradientBrushApplicator { private readonly Point start; @@ -147,30 +147,6 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes public override void Dispose() { } - - internal override void Apply(Span scanline, int x, int y) - { - base.Apply(scanline, x, y); - - // TODO: we should at least(!) speed up the x=0 and y=0 special cases. - // But in fact that could be done by special case Applicators directly: - // - horizontal would apply a precalc. row independent of given row, - // - vertical would get the color of the row once and fill the whole line. - - // Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); - // MemoryManager memoryManager = this.Target.MemoryManager; - // using (IBuffer amountBuffer = memoryManager.Allocate(scanline.Length)) - // { - // Span amountSpan = amountBuffer.Span; - // - // for (int i = 0; i < scanline.Length; i++) - // { - // amountSpan[i] = scanline[i] * this.Options.BlendPercentage; - // } - // - // this.Blender.Blend(memoryManager, destinationRow, destinationRow, this.Colors.Span, amountSpan); - // } - } } } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/RadialGradientBrush.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/RadialGradientBrush.cs index 53b34e233..d1a99a015 100644 --- a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/RadialGradientBrush.cs +++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/RadialGradientBrush.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes /// A Circular Gradient Brush, defined by center point and radius. /// /// The pixel format. - public class RadialGradientBrush : AbstractGradientBrush + public sealed class RadialGradientBrush : AbstractGradientBrush where TPixel : struct, IPixel { private readonly Point center; @@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes this.RepetitionMode); /// - protected class RadialGradientBrushApplicator : AbstractGradientBrushApplicator + private sealed class RadialGradientBrushApplicator : AbstractGradientBrushApplicator { private readonly Point center; From b3976229445952a3ca3aaa9a65590a5790aa7b29 Mon Sep 17 00:00:00 2001 From: Peter Amrehn Date: Sun, 6 May 2018 16:15:07 +0200 Subject: [PATCH 27/43] #542: refactor tests to follow the recommended pattern for drawing tests, as @antonfirsov suggested. --- .../Drawing/FillEllipticGradientBrushTest.cs | 156 ++++---- .../Drawing/FillLinearGradientBrushTests.cs | 358 ++++++++++-------- .../Drawing/FillRadialGradientBrushTests.cs | 84 ++-- 3 files changed, 329 insertions(+), 269 deletions(-) diff --git a/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs b/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs index 1ec27d762..2d69e0ad5 100644 --- a/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs +++ b/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs @@ -12,113 +12,135 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Drawing { + [GroupOutput("Drawing/GradientBrushes")] public class FillEllipticGradientBrushTests : FileTestBase { - [Fact] - public void EllipticGradientBrushWithEqualColorsAndReturnsUnicolorImage() + [Theory] + [WithBlankImages(10, 10, PixelTypes.Rgba32)] + public void EllipticGradientBrushWithEqualColorsAndReturnsUnicolorImage( + TestImageProvider provider) + where TPixel : struct, IPixel { - string path = TestEnvironment.CreateOutputDirectory("Fill", "EllipticGradientBrush"); - using (var image = new Image(10, 10)) + TPixel red = NamedColors.Red; + + using (Image image = provider.GetImage()) { - EllipticGradientBrush unicolorLinearGradientBrush = - new EllipticGradientBrush( + EllipticGradientBrush unicolorLinearGradientBrush = + new EllipticGradientBrush( new SixLabors.Primitives.Point(0, 0), new SixLabors.Primitives.Point(10, 0), 1.0f, GradientRepetitionMode.None, - new ColorStop(0, Rgba32.Red), - new ColorStop(1, Rgba32.Red)); + new ColorStop(0, red), + new ColorStop(1, red)); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - image.Save($"{path}/UnicolorCircleGradient.png"); + image.DebugSave(provider); - using (PixelAccessor sourcePixels = image.Lock()) + using (PixelAccessor sourcePixels = image.Lock()) { - Assert.Equal(Rgba32.Red, sourcePixels[0, 0]); - Assert.Equal(Rgba32.Red, sourcePixels[9, 9]); - Assert.Equal(Rgba32.Red, sourcePixels[5, 5]); - Assert.Equal(Rgba32.Red, sourcePixels[3, 8]); + Assert.Equal(red, sourcePixels[0, 0]); + Assert.Equal(red, sourcePixels[9, 9]); + Assert.Equal(red, sourcePixels[5, 5]); + Assert.Equal(red, sourcePixels[3, 8]); } + + image.CompareToReferenceOutput(provider); } } [Theory] - [InlineData(0.1)] - [InlineData(0.4)] - [InlineData(0.8)] - [InlineData(1.0)] - [InlineData(1.2)] - [InlineData(1.6)] - [InlineData(2.0)] - public void EllipticGradientBrushProducesAxisParallelEllipsesWithDifferentRatio( + [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 0.1)] + [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 0.4)] + [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 0.8)] + [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 1.0)] + [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 1.2)] + [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 1.6)] + [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 2.0)] + public void EllipticGradientBrushProducesAxisParallelEllipsesWithDifferentRatio( + TestImageProvider provider, float ratio) + where TPixel : struct, IPixel { - string path = TestEnvironment.CreateOutputDirectory("Fill", "EllipticGradientBrush"); - using (var image = new Image(1000, 1000)) + TPixel yellow = NamedColors.Yellow; + TPixel red = NamedColors.Red; + TPixel black = NamedColors.Black; + + using (var image = provider.GetImage()) { - EllipticGradientBrush unicolorLinearGradientBrush = - new EllipticGradientBrush( - new SixLabors.Primitives.Point(500, 500), - new SixLabors.Primitives.Point(500, 750), + EllipticGradientBrush unicolorLinearGradientBrush = + new EllipticGradientBrush( + new SixLabors.Primitives.Point(image.Width / 2, image.Height / 2), + new SixLabors.Primitives.Point(image.Width / 2, (image.Width * 3) / 2), ratio, GradientRepetitionMode.None, - new ColorStop(0, Rgba32.Yellow), - new ColorStop(1, Rgba32.Red), - new ColorStop(1, Rgba32.Black)); + new ColorStop(0, yellow), + new ColorStop(1, red), + new ColorStop(1, black)); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - image.Save($"{path}/Ellipsis{ratio}.png"); + image.DebugSave(provider, ratio); + image.CompareToReferenceOutput(provider, ratio); } } [Theory] - [InlineData(0.1, 0)] - [InlineData(0.4, 0)] - [InlineData(0.8, 0)] - [InlineData(1.0, 0)] - - [InlineData(0.1, 45)] - [InlineData(0.4, 45)] - [InlineData(0.8, 45)] - [InlineData(1.0, 45)] - - [InlineData(0.1, 90)] - [InlineData(0.4, 90)] - [InlineData(0.8, 90)] - [InlineData(1.0, 90)] - - [InlineData(0.1, 30)] - [InlineData(0.4, 30)] - [InlineData(0.8, 30)] - [InlineData(1.0, 30)] - public void EllipticGradientBrushProducesRotatedEllipsesWithDifferentRatio( + [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 0.1, 0)] + [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 0.4, 0)] + [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 0.8, 0)] + [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 1.0, 0)] + + [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 0.1, 45)] + [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 0.4, 45)] + [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 0.8, 45)] + [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 1.0, 45)] + + [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 0.1, 90)] + [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 0.4, 90)] + [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 0.8, 90)] + [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 1.0, 90)] + + [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 0.1, 30)] + [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 0.4, 30)] + [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 0.8, 30)] + [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 1.0, 30)] + public void EllipticGradientBrushProducesRotatedEllipsesWithDifferentRatio( + TestImageProvider provider, float ratio, float rotationInDegree) + where TPixel: struct, IPixel { - var center = new SixLabors.Primitives.Point(500, 500); + string variant = $"{ratio}at{rotationInDegree}°"; + + using (var image = provider.GetImage()) + { + TPixel yellow = NamedColors.Yellow; + TPixel red = NamedColors.Red; + TPixel black = NamedColors.Black; - var rotation = (Math.PI * rotationInDegree) / 180.0; - var cos = Math.Cos(rotation); - var sin = Math.Sin(rotation); + var center = new SixLabors.Primitives.Point(image.Width / 2, image.Height / 2); - int axisX = (int)((center.X * cos) - (center.Y * sin)); - int axisY = (int)((center.X * sin) + (center.Y * cos)); + var rotation = (Math.PI * rotationInDegree) / 180.0; + var cos = Math.Cos(rotation); + var sin = Math.Sin(rotation); - string path = TestEnvironment.CreateOutputDirectory("Fill", "EllipticGradientBrush"); - using (var image = new Image(1000, 1000)) - { - EllipticGradientBrush unicolorLinearGradientBrush = - new EllipticGradientBrush( + int axisX = (int)((center.X * cos) - (center.Y * sin)); + int axisY = (int)((center.X * sin) + (center.Y * cos)); + + + EllipticGradientBrush unicolorLinearGradientBrush = + new EllipticGradientBrush( center, new SixLabors.Primitives.Point(axisX, axisY), ratio, GradientRepetitionMode.None, - new ColorStop(0, Rgba32.Yellow), - new ColorStop(1, Rgba32.Red), - new ColorStop(1, Rgba32.Black)); + new ColorStop(0, yellow), + new ColorStop(1, red), + new ColorStop(1, black)); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - image.Save($"{path}/Ellipsis{ratio}_rot{rotationInDegree}°.png"); + image.DebugSave(provider, variant); + image.CompareToReferenceOutput(provider, variant); } } } diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs index d080896a0..14ec69e82 100644 --- a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs @@ -1,7 +1,10 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Globalization; using System.Linq; +using System.Text; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; @@ -12,305 +15,364 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Drawing { + [GroupOutput("Drawing/GradientBrushes")] public class FillLinearGradientBrushTests : FileTestBase { - [Fact] - public void LinearGradientBrushWithEqualColorsReturnsUnicolorImage() + [Theory] + [WithBlankImages(10, 10, PixelTypes.Rgba32)] + public void WithEqualColorsReturnsUnicolorImage( + TestImageProvider provider) + where TPixel : struct, IPixel { - string path = TestEnvironment.CreateOutputDirectory("Fill", "LinearGradientBrush"); - using (var image = new Image(10, 10)) + TPixel red = NamedColors.Red; + using (var image = provider.GetImage()) { - LinearGradientBrush unicolorLinearGradientBrush = - new LinearGradientBrush( + LinearGradientBrush unicolorLinearGradientBrush = + new LinearGradientBrush( new SixLabors.Primitives.Point(0, 0), new SixLabors.Primitives.Point(10, 0), GradientRepetitionMode.None, - new ColorStop(0, Rgba32.Red), - new ColorStop(1, Rgba32.Red)); + new ColorStop(0, red), + new ColorStop(1, red)); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - image.Save($"{path}/UnicolorGradient.png"); - - using (PixelAccessor sourcePixels = image.Lock()) - { - Assert.Equal(Rgba32.Red, sourcePixels[0, 0]); - Assert.Equal(Rgba32.Red, sourcePixels[9, 9]); - Assert.Equal(Rgba32.Red, sourcePixels[5, 5]); - Assert.Equal(Rgba32.Red, sourcePixels[3, 8]); - } + image.DebugSave(provider); + image.CompareToReferenceOutput(provider); } } - [Fact] - public void HorizontalLinearGradientBrushReturnsUnicolorColumns() + [Theory] + [WithBlankImages(500, 10, PixelTypes.Rgba32)] + public void HorizontalReturnsUnicolorColumns( + TestImageProvider provider) + where TPixel : struct, IPixel { - int width = 500; - int height = 10; - int lastColumnIndex = width - 1; - - string path = TestEnvironment.CreateOutputDirectory("Fill", "LinearGradientBrush"); - using (var image = new Image(width, height)) + using (var image = provider.GetImage()) { - LinearGradientBrush unicolorLinearGradientBrush = - new LinearGradientBrush( + int lastColumnIndex = image.Width - 1; + TPixel red = NamedColors.Red; + TPixel yellow = NamedColors.Yellow; + + LinearGradientBrush unicolorLinearGradientBrush = + new LinearGradientBrush( new SixLabors.Primitives.Point(0, 0), - new SixLabors.Primitives.Point(500, 0), + new SixLabors.Primitives.Point(image.Width, 0), GradientRepetitionMode.None, - new ColorStop(0, Rgba32.Red), - new ColorStop(1, Rgba32.Yellow)); + new ColorStop(0, red), + new ColorStop(1, yellow)); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - image.Save($"{path}/horizontalRedToYellow.png"); + image.DebugSave(provider); - using (PixelAccessor sourcePixels = image.Lock()) + using (PixelAccessor sourcePixels = image.Lock()) { - Rgba32 columnColor0 = sourcePixels[0, 0]; - Rgba32 columnColor23 = sourcePixels[23, 0]; - Rgba32 columnColor42 = sourcePixels[42, 0]; - Rgba32 columnColor333 = sourcePixels[333, 0]; + TPixel columnColor0 = sourcePixels[0, 0]; + TPixel columnColor23 = sourcePixels[23, 0]; + TPixel columnColor42 = sourcePixels[42, 0]; + TPixel columnColor333 = sourcePixels[333, 0]; - Rgba32 lastColumnColor = sourcePixels[lastColumnIndex, 0]; + TPixel lastColumnColor = sourcePixels[lastColumnIndex, 0]; - for (int i = 0; i < height; i++) + for (int i = 0; i < image.Height; i++) { // check first and last column: Assert.Equal(columnColor0, sourcePixels[0, i]); Assert.Equal(lastColumnColor, sourcePixels[lastColumnIndex, i]); // check the random colors: - Assert.True(columnColor23 == sourcePixels[23, i], $"at {i}"); + Assert.True(columnColor23.Equals(sourcePixels[23, i]), $"at {i}"); Assert.Equal(columnColor42, sourcePixels[42, i]); Assert.Equal(columnColor333, sourcePixels[333, i]); } } + + image.CompareToReferenceOutput(provider); } } [Theory] - [InlineData(GradientRepetitionMode.DontFill)] - [InlineData(GradientRepetitionMode.None)] - [InlineData(GradientRepetitionMode.Repeat)] - [InlineData(GradientRepetitionMode.Reflect)] - public void HorizontalLinearGradientBrushWithDifferentRepetitionModesCreatesCorrectImages( + [WithBlankImages(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.DontFill)] + [WithBlankImages(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.None)] + [WithBlankImages(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.Repeat)] + [WithBlankImages(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.Reflect)] + public void HorizontalGradientWithRepMode( + TestImageProvider provider, GradientRepetitionMode repetitionMode) + where TPixel : struct, IPixel { - int width = 500; - int height = 10; - int lastColumnIndex = width - 1; - - string path = TestEnvironment.CreateOutputDirectory("Fill", "LinearGradientBrush"); - using (var image = new Image(width, height)) + using (var image = provider.GetImage()) { - LinearGradientBrush unicolorLinearGradientBrush = - new LinearGradientBrush( + int lastColumnIndex = image.Width - 1; + + TPixel red = NamedColors.Red; + TPixel yellow = NamedColors.Yellow; + + LinearGradientBrush unicolorLinearGradientBrush = + new LinearGradientBrush( new SixLabors.Primitives.Point(0, 0), - new SixLabors.Primitives.Point(50, 0), + new SixLabors.Primitives.Point(image.Width / 10, 0), repetitionMode, - new ColorStop(0, Rgba32.Red), - new ColorStop(1, Rgba32.Yellow)); + new ColorStop(0, red), + new ColorStop(1, yellow)); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - image.Save($"{path}/horizontalRedToYellow_{repetitionMode}.png"); + image.DebugSave(provider, repetitionMode); - using (PixelAccessor sourcePixels = image.Lock()) + using (PixelAccessor sourcePixels = image.Lock()) { - Rgba32 columnColor0 = sourcePixels[0, 0]; - Rgba32 columnColor23 = sourcePixels[23, 0]; - Rgba32 columnColor42 = sourcePixels[42, 0]; - Rgba32 columnColor333 = sourcePixels[333, 0]; + TPixel columnColor0 = sourcePixels[0, 0]; + TPixel columnColor23 = sourcePixels[23, 0]; + TPixel columnColor42 = sourcePixels[42, 0]; + TPixel columnColor333 = sourcePixels[333, 0]; - Rgba32 lastColumnColor = sourcePixels[lastColumnIndex, 0]; + TPixel lastColumnColor = sourcePixels[lastColumnIndex, 0]; - for (int i = 0; i < height; i++) + for (int i = 0; i < image.Height; i++) { // check first and last column: Assert.Equal(columnColor0, sourcePixels[0, i]); Assert.Equal(lastColumnColor, sourcePixels[lastColumnIndex, i]); // check the random colors: - Assert.True(columnColor23 == sourcePixels[23, i], $"at {i}"); + Assert.True(columnColor23.Equals(sourcePixels[23, i]), $"at {i}"); Assert.Equal(columnColor42, sourcePixels[42, i]); Assert.Equal(columnColor333, sourcePixels[333, i]); } } + + image.CompareToReferenceOutput(provider, repetitionMode); } } [Theory] - [InlineData(new[] { 0.5f })] - [InlineData(new[] { 0.2f, 0.4f, 0.6f, 0.8f })] - [InlineData(new[] { 0.1f, 0.3f, 0.6f })] - public void LinearGradientsWithDoubledStopsProduceDashedPatterns( + [WithBlankImages(200, 100, PixelTypes.Rgba32, new[] { 0.5f })] + [WithBlankImages(200, 100, PixelTypes.Rgba32, new[] { 0.2f, 0.4f, 0.6f, 0.8f })] + [WithBlankImages(200, 100, PixelTypes.Rgba32, new[] { 0.1f, 0.3f, 0.6f })] + public void WithDoubledStopsProduceDashedPatterns( + TestImageProvider provider, float[] pattern) + where TPixel : struct, IPixel { - int width = 200; - int height = 10; + string variant = string.Join(",", pattern.Select(i => i.ToString(CultureInfo.InvariantCulture))); // ensure the input data is valid Assert.True(pattern.Length > 0); + TPixel black = NamedColors.Black; + TPixel white = NamedColors.White; + // create the input pattern: 0, followed by each of the arguments twice, followed by 1.0 - toggling black and white. - ColorStop[] colorStops = - Enumerable.Repeat(new ColorStop(0, Rgba32.Black), 1) + ColorStop[] colorStops = + Enumerable.Repeat(new ColorStop(0, black), 1) .Concat( pattern .SelectMany((f, index) => new[] { - new ColorStop(f, index % 2 == 0 ? Rgba32.Black : Rgba32.White), - new ColorStop(f, index % 2 == 0 ? Rgba32.White : Rgba32.Black) + new ColorStop(f, index % 2 == 0 ? black : white), + new ColorStop(f, index % 2 == 0 ? white : black) })) - .Concat(Enumerable.Repeat(new ColorStop(1, pattern.Length % 2 == 0 ? Rgba32.Black : Rgba32.White), 1)) + .Concat(Enumerable.Repeat(new ColorStop(1, pattern.Length % 2 == 0 ? black : white), 1)) .ToArray(); - string path = TestEnvironment.CreateOutputDirectory("Fill", "LinearGradientBrush"); - using (var image = new Image(width, height)) + using (Image image = provider.GetImage()) { - LinearGradientBrush unicolorLinearGradientBrush = - new LinearGradientBrush( + LinearGradientBrush unicolorLinearGradientBrush = + new LinearGradientBrush( new SixLabors.Primitives.Point(0, 0), - new SixLabors.Primitives.Point(width, 0), + new SixLabors.Primitives.Point(image.Width, 0), GradientRepetitionMode.None, colorStops); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - image.Save($"{path}/blackAndWhite{pattern[0]}.png"); + image.DebugSave(provider, variant); - using (PixelAccessor sourcePixels = image.Lock()) + using (PixelAccessor sourcePixels = image.Lock()) { // the result must be a black and white pattern, no other color should occur: Assert.All( - Enumerable.Range(0, width).Select(i => sourcePixels[i, 0]), - color => Assert.True(color == Rgba32.Black || color == Rgba32.White)); + Enumerable.Range(0, image.Width).Select(i => sourcePixels[i, 0]), + color => Assert.True(color.Equals(black) || color.Equals(white))); } + + image.CompareToReferenceOutput(provider, variant); } } - [Fact] - public void VerticalLinearGradientBrushReturnsUnicolorColumns() + [Theory] + [WithBlankImages(10, 500, PixelTypes.Rgba32)] + public void VerticalReturnsUnicolorColumns( + TestImageProvider provider) + where TPixel : struct, IPixel { - int width = 10; - int height = 500; - int lastRowIndex = height - 1; - - string path = TestEnvironment.CreateOutputDirectory("Fill", "LinearGradientBrush"); - using (var image = new Image(width, height)) + using (var image = provider.GetImage()) { - LinearGradientBrush unicolorLinearGradientBrush = - new LinearGradientBrush( + int lastRowIndex = image.Height - 1; + + TPixel red = NamedColors.Red; + TPixel yellow = NamedColors.Yellow; + + LinearGradientBrush unicolorLinearGradientBrush = + new LinearGradientBrush( new SixLabors.Primitives.Point(0, 0), - new SixLabors.Primitives.Point(0, 500), + new SixLabors.Primitives.Point(0, image.Height), GradientRepetitionMode.None, - new ColorStop(0, Rgba32.Red), - new ColorStop(1, Rgba32.Yellow)); + new ColorStop(0, red), + new ColorStop(1, yellow)); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - image.Save($"{path}/verticalRedToYellow.png"); + image.DebugSave(provider); + + Random random = new Random(); - using (PixelAccessor sourcePixels = image.Lock()) + using (PixelAccessor sourcePixels = image.Lock()) { - Rgba32 firstRowColor = sourcePixels[0, 0]; + TPixel firstRowColor = sourcePixels[0, 0]; - Rgba32 columnColor23 = sourcePixels[0, 23]; - Rgba32 columnColor42 = sourcePixels[0, 42]; - Rgba32 columnColor333 = sourcePixels[0, 333]; + int columnA = random.Next(0, image.Height); + int columnB = random.Next(0, image.Height); + int columnC = random.Next(0, image.Height); + TPixel columnColorA = sourcePixels[0, columnA]; + TPixel columnColorB = sourcePixels[0, columnB]; + TPixel columnColorC = sourcePixels[0, columnC]; - Rgba32 lastRowColor = sourcePixels[0, lastRowIndex]; + TPixel lastRowColor = sourcePixels[0, lastRowIndex]; - for (int i = 0; i < width; i++) + for (int i = 0; i < image.Width; i++) { // check first and last column, these are known: Assert.Equal(firstRowColor, sourcePixels[i, 0]); Assert.Equal(lastRowColor, sourcePixels[i, lastRowIndex]); // check the random colors: - Assert.Equal(columnColor23, sourcePixels[i, 23]); - Assert.Equal(columnColor42, sourcePixels[i, 42]); - Assert.Equal(columnColor333, sourcePixels[i, 333]); + Assert.Equal(columnColorA, sourcePixels[i, columnA]); + Assert.Equal(columnColorB, sourcePixels[i, columnB]); + Assert.Equal(columnColorC, sourcePixels[i, columnC]); } } + + image.CompareToReferenceOutput(provider); } } - [Theory] - [InlineData(0, 0, 499, 499)] - [InlineData(0, 499, 499, 0)] - [InlineData(499, 499, 0, 0)] - [InlineData(499, 0, 0, 499)] - public void DiagonalLinearGradientBrushReturnsUnicolorColumns( - int startX, int startY, int endX, int endY) + public enum ImageCorner { - int size = 500; + TopLeft = 0, + TopRight = 1, + BottomLeft = 2, + BottomRight = 3 + } - string path = TestEnvironment.CreateOutputDirectory("Fill", "LinearGradientBrush"); - using (var image = new Image(size, size)) + [Theory] + [WithBlankImages(500, 500, PixelTypes.Rgba32, ImageCorner.TopLeft)] + [WithBlankImages(500, 500, PixelTypes.Rgba32, ImageCorner.TopRight)] + [WithBlankImages(500, 500, PixelTypes.Rgba32, ImageCorner.BottomLeft)] + [WithBlankImages(500, 500, PixelTypes.Rgba32, ImageCorner.BottomRight)] + public void DiagonalReturnsCorrectImages( + TestImageProvider provider, + ImageCorner startCorner) + where TPixel : struct, IPixel + { + using (var image = provider.GetImage()) { - LinearGradientBrush unicolorLinearGradientBrush = - new LinearGradientBrush( + Assert.True(image.Height == image.Width, "For the math check block at the end the image must be squared, but it is not."); + + int startX = (int)startCorner % 2 == 0 ? 0 : image.Width - 1; + int startY = startCorner > ImageCorner.TopRight ? 0 : image.Height - 1; + int endX = image.Height - startX - 1; + int endY = image.Width - startY - 1; + + TPixel red = NamedColors.Red; + TPixel yellow = NamedColors.Yellow; + + LinearGradientBrush unicolorLinearGradientBrush = + new LinearGradientBrush( new SixLabors.Primitives.Point(startX, startY), new SixLabors.Primitives.Point(endX, endY), GradientRepetitionMode.None, - new ColorStop(0, Rgba32.Red), - new ColorStop(1, Rgba32.Yellow)); + new ColorStop(0, red), + new ColorStop(1, yellow)); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - image.Save($"{path}/diagonalRedToYellowFrom{startX}_{startY}.png"); + image.DebugSave(provider, startCorner); + + int verticalSign = startY == 0 ? 1 : -1; + int horizontalSign = startX == 0 ? 1 : -1; - using (PixelAccessor sourcePixels = image.Lock()) + using (PixelAccessor sourcePixels = image.Lock()) { // check first and last pixel, these are known: - Assert.Equal(Rgba32.Red, sourcePixels[startX, startY]); - Assert.Equal(Rgba32.Yellow, sourcePixels[endX, endY]); + Assert.Equal(red, sourcePixels[startX, startY]); + Assert.Equal(yellow, sourcePixels[endX, endY]); - for (int i = 0; i < size; i++) + for (int i = 0; i < image.Height; i++) { // it's diagonal, so for any (a, a) on the gradient line, for all (a-x, b+x) - +/- depending on the diagonal direction - must be the same color) + TPixel colorOnDiagonal = sourcePixels[i, i]; + int orthoCount = 0; + for (int offset = -orthoCount; offset < orthoCount; offset++) + { + Assert.Equal(colorOnDiagonal, sourcePixels[i + horizontalSign * offset, i + verticalSign * offset]); + } } } + + image.CompareToReferenceOutput(provider, startCorner); } } [Theory] - [InlineData("a", 0, 0, 499, 499, new[] { 0f, .2f, .5f, .9f }, new[] { 0, 0, 1, 1 })] - [InlineData("b", 0, 499, 499, 0, new[] { 0f, 0.2f, 0.5f, 0.9f }, new[] { 0, 1, 2, 3 })] - [InlineData("c", 499, 499, 0, 0, new[] { 0f, 0.7f, 0.8f, 0.9f}, new[] { 0, 1, 2, 0 })] - [InlineData("d", 0, 0, 499, 499, new[] { 0f, .5f, 1f}, new[]{0, 1, 3})] - public void ArbitraryLinearGradientsProduceImagesVisualCheckOnly( - string filenameSuffix, + [WithBlankImages(500, 500, PixelTypes.Rgba32, 0, 0, 499, 499, new[] { 0f, .2f, .5f, .9f }, new[] { 0, 0, 1, 1 })] + [WithBlankImages(500, 500, PixelTypes.Rgba32, 0, 499, 499, 0, new[] { 0f, 0.2f, 0.5f, 0.9f }, new[] { 0, 1, 2, 3 })] + [WithBlankImages(500, 500, PixelTypes.Rgba32, 499, 499, 0, 0, new[] { 0f, 0.7f, 0.8f, 0.9f}, new[] { 0, 1, 2, 0 })] + [WithBlankImages(500, 500, PixelTypes.Rgba32, 0, 0, 499, 499, new[] { 0f, .5f, 1f}, new[]{0, 1, 3})] + // TODO: add some more tests with arbitrary gradient orders! + public void ArbitraryGradients( + TestImageProvider provider, int startX, int startY, int endX, int endY, float[] stopPositions, int[] stopColorCodes) + where TPixel : struct, IPixel { - var colors = new Rgba32[] + var colors = new [] { - Rgba32.Navy, - Rgba32.LightGreen, - Rgba32.Yellow, - Rgba32.Red + NamedColors.Navy, + NamedColors.LightGreen, + NamedColors.Yellow, + NamedColors.Red }; - var colorStops = new ColorStop[stopPositions.Length]; + StringBuilder coloringVariant = new StringBuilder(); + var colorStops = new ColorStop[stopPositions.Length]; for (int i = 0; i < stopPositions.Length; i++) { - colorStops[i] = new ColorStop( - stopPositions[i], - colors[stopColorCodes[i]]); + TPixel color = colors[stopColorCodes[i % colors.Length]]; + float position = stopPositions[i]; + + colorStops[i] = new ColorStop( + position, + color); + coloringVariant.AppendFormat( + CultureInfo.InvariantCulture, + "{0}@{1};", + color, + position); } - int size = 500; + string variant = $"{startX},{startY}to{endX},{endY};[{coloringVariant}]"; - string path = TestEnvironment.CreateOutputDirectory("Fill", "LinearGradientBrush"); - using (var image = new Image(size, size)) + using (var image = provider.GetImage()) { - LinearGradientBrush unicolorLinearGradientBrush = - new LinearGradientBrush( + LinearGradientBrush unicolorLinearGradientBrush = + new LinearGradientBrush( new SixLabors.Primitives.Point(startX, startY), new SixLabors.Primitives.Point(endX, endY), GradientRepetitionMode.None, colorStops); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - image.Save($"{path}/arbitraryGradient_{filenameSuffix}.png"); + image.DebugSave(provider, variant); + image.CompareToReferenceOutput(provider, variant); } } } diff --git a/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs index 7229e7041..9fa2c65fa 100644 --- a/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs @@ -9,80 +9,56 @@ namespace SixLabors.ImageSharp.Tests.Drawing { public class FillRadialGradientBrushTests : FileTestBase { - [Fact] - public void RadialGradientBrushWithEqualColorsReturnsUnicolorImage() + [Theory] + [WithBlankImages(200, 200, PixelTypes.Rgba32)] + public void RadialGradientBrushWithEqualColorsReturnsUnicolorImage( + TestImageProvider provider) + where TPixel : struct, IPixel { - string path = TestEnvironment.CreateOutputDirectory("Fill", "RadialGradientBrush"); - using (var image = new Image(200, 200)) + using (var image = provider.GetImage()) { - RadialGradientBrush unicolorRadialGradientBrush = - new RadialGradientBrush( + TPixel red = NamedColors.Red; + + RadialGradientBrush unicolorRadialGradientBrush = + new RadialGradientBrush( new SixLabors.Primitives.Point(0, 0), 100, GradientRepetitionMode.None, - new ColorStop(0, Rgba32.Red), - new ColorStop(1, Rgba32.Red)); + new ColorStop(0, red), + new ColorStop(1, red)); image.Mutate(x => x.Fill(unicolorRadialGradientBrush)); - image.Save($"{path}/UnicolorGradient.png"); + image.DebugSave(provider); - using (PixelAccessor sourcePixels = image.Lock()) - { - Assert.Equal(Rgba32.Red, sourcePixels[0, 0]); - Assert.Equal(Rgba32.Red, sourcePixels[9, 9]); - Assert.Equal(Rgba32.Red, sourcePixels[5, 5]); - Assert.Equal(Rgba32.Red, sourcePixels[3, 8]); - } + image.CompareToReferenceOutput(provider); } } [Theory] - [InlineData(250, 250)] - [InlineData(0, 0)] - [InlineData(250, 0)] - [InlineData(0, 250)] - [InlineData(-100, 250)] - public void RadialGradientBrushWithDifferentCentersReturnsImage( + [WithBlankImages(500, 500, PixelTypes.Rgba32, 250, 250)] + [WithBlankImages(500, 500, PixelTypes.Rgba32, 0, 0)] + [WithBlankImages(500, 500, PixelTypes.Rgba32, 250, 0)] + [WithBlankImages(500, 500, PixelTypes.Rgba32, 0, 250)] + [WithBlankImages(500, 500, PixelTypes.Rgba32, -100, 250)] + public void RadialGradientBrushWithDifferentCentersReturnsImage( + TestImageProvider provider, int centerX, int centerY) + where TPixel : struct, IPixel { - int width = 500; - - string path = TestEnvironment.CreateOutputDirectory("Fill", "RadialGradientBrush"); - using (var image = new Image(width, width)) + using (var image = provider.GetImage()) { - RadialGradientBrush brush = - new RadialGradientBrush( + RadialGradientBrush brush = + new RadialGradientBrush( new SixLabors.Primitives.Point(centerX, centerY), - width / 2f, + image.Width / 2f, GradientRepetitionMode.None, - new ColorStop(0, Rgba32.Red), - new ColorStop(1, Rgba32.Yellow)); + new ColorStop(0, NamedColors.Red), + new ColorStop(1, NamedColors.Yellow)); image.Mutate(x => x.Fill(brush)); - image.Save($"{path}/CenterAt{centerX}_{centerY}.png"); - - // using (PixelAccessor sourcePixels = image.Lock()) - // { - // Rgba32 columnColor0 = sourcePixels[0, 0]; - // Rgba32 columnColor23 = sourcePixels[23, 0]; - // Rgba32 columnColor42 = sourcePixels[42, 0]; - // Rgba32 columnColor333 = sourcePixels[333, 0]; - // - // Rgba32 lastColumnColor = sourcePixels[lastColumnIndex, 0]; - // - // for (int i = 0; i < width; i++) - // { - // // check first and last column: - // Assert.Equal(columnColor0, sourcePixels[0, i]); - // Assert.Equal(lastColumnColor, sourcePixels[lastColumnIndex, i]); - // - // // check the random colors: - // Assert.True(columnColor23 == sourcePixels[23, i], $"at {i}"); - // Assert.Equal(columnColor42, sourcePixels[42, i]); - // Assert.Equal(columnColor333, sourcePixels[333, i]); - // } - // } + image.DebugSave(provider); + image.CompareToReferenceOutput(provider); } } } From 2af95bef7ef57d403f88b3384c941c14c8249c85 Mon Sep 17 00:00:00 2001 From: Peter Amrehn Date: Sun, 6 May 2018 16:30:02 +0200 Subject: [PATCH 28/43] #542: reduce test image sizes to save submudule size --- .../Drawing/FillEllipticGradientBrushTest.cs | 54 +++++++++---------- .../Drawing/FillRadialGradientBrushTests.cs | 10 ++-- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs b/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs index 2d69e0ad5..72095d933 100644 --- a/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs +++ b/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs @@ -50,13 +50,13 @@ namespace SixLabors.ImageSharp.Tests.Drawing } [Theory] - [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 0.1)] - [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 0.4)] - [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 0.8)] - [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 1.0)] - [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 1.2)] - [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 1.6)] - [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 2.0)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.1)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 1.2)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 1.6)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 2.0)] public void EllipticGradientBrushProducesAxisParallelEllipsesWithDifferentRatio( TestImageProvider provider, float ratio) @@ -85,25 +85,25 @@ namespace SixLabors.ImageSharp.Tests.Drawing } [Theory] - [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 0.1, 0)] - [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 0.4, 0)] - [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 0.8, 0)] - [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 1.0, 0)] - - [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 0.1, 45)] - [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 0.4, 45)] - [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 0.8, 45)] - [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 1.0, 45)] - - [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 0.1, 90)] - [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 0.4, 90)] - [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 0.8, 90)] - [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 1.0, 90)] - - [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 0.1, 30)] - [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 0.4, 30)] - [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 0.8, 30)] - [WithBlankImages(1000, 1000, PixelTypes.Rgba32, 1.0, 30)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.1, 0)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4, 0)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8, 0)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0, 0)] + + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.1, 45)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4, 45)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8, 45)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0, 45)] + + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.1, 90)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4, 90)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8, 90)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0, 90)] + + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.1, 30)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4, 30)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8, 30)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0, 30)] public void EllipticGradientBrushProducesRotatedEllipsesWithDifferentRatio( TestImageProvider provider, float ratio, @@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing where TPixel: struct, IPixel { string variant = $"{ratio}at{rotationInDegree}°"; - + using (var image = provider.GetImage()) { TPixel yellow = NamedColors.Yellow; diff --git a/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs index 9fa2c65fa..60171e477 100644 --- a/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs @@ -35,11 +35,11 @@ namespace SixLabors.ImageSharp.Tests.Drawing } [Theory] - [WithBlankImages(500, 500, PixelTypes.Rgba32, 250, 250)] - [WithBlankImages(500, 500, PixelTypes.Rgba32, 0, 0)] - [WithBlankImages(500, 500, PixelTypes.Rgba32, 250, 0)] - [WithBlankImages(500, 500, PixelTypes.Rgba32, 0, 250)] - [WithBlankImages(500, 500, PixelTypes.Rgba32, -100, 250)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 100, 100)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0, 0)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 100, 0)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, 0, 100)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, -40, 100)] public void RadialGradientBrushWithDifferentCentersReturnsImage( TestImageProvider provider, int centerX, From 92b0130e5addcfd72ba84d8cfd60ca6506285335 Mon Sep 17 00:00:00 2001 From: Peter Amrehn Date: Sun, 6 May 2018 17:57:23 +0200 Subject: [PATCH 29/43] rename files to add {TPixel} generic parameter. --- .../GradientBrushes/{ColorStop.cs => ColorStop{TPixel}.cs} | 0 .../{RadialGradientBrush.cs => RadialGradientBrush{TPixel}.cs} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/{ColorStop.cs => ColorStop{TPixel}.cs} (100%) rename src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/{RadialGradientBrush.cs => RadialGradientBrush{TPixel}.cs} (100%) diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/ColorStop.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/ColorStop{TPixel}.cs similarity index 100% rename from src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/ColorStop.cs rename to src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/ColorStop{TPixel}.cs diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/RadialGradientBrush.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/RadialGradientBrush{TPixel}.cs similarity index 100% rename from src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/RadialGradientBrush.cs rename to src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/RadialGradientBrush{TPixel}.cs From 09f6a4f2b10599876ce4f5fcced7687b8c7609d9 Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 7 May 2018 20:47:47 +0200 Subject: [PATCH 30/43] #542: remove pixel checks and base class as requested by @antonfirsov. --- .../Drawing/FillLinearGradientBrushTests.cs | 25 +------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs index 14ec69e82..baf25a897 100644 --- a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs @@ -16,7 +16,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Drawing { [GroupOutput("Drawing/GradientBrushes")] - public class FillLinearGradientBrushTests : FileTestBase + public class FillLinearGradientBrushTests { [Theory] [WithBlankImages(10, 10, PixelTypes.Rgba32)] @@ -117,29 +117,6 @@ namespace SixLabors.ImageSharp.Tests.Drawing image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); image.DebugSave(provider, repetitionMode); - - using (PixelAccessor sourcePixels = image.Lock()) - { - TPixel columnColor0 = sourcePixels[0, 0]; - TPixel columnColor23 = sourcePixels[23, 0]; - TPixel columnColor42 = sourcePixels[42, 0]; - TPixel columnColor333 = sourcePixels[333, 0]; - - TPixel lastColumnColor = sourcePixels[lastColumnIndex, 0]; - - for (int i = 0; i < image.Height; i++) - { - // check first and last column: - Assert.Equal(columnColor0, sourcePixels[0, i]); - Assert.Equal(lastColumnColor, sourcePixels[lastColumnIndex, i]); - - // check the random colors: - Assert.True(columnColor23.Equals(sourcePixels[23, i]), $"at {i}"); - Assert.Equal(columnColor42, sourcePixels[42, i]); - Assert.Equal(columnColor333, sourcePixels[333, i]); - } - } - image.CompareToReferenceOutput(provider, repetitionMode); } } From 3052fe061a5e06d30480759ee2c88f09149022fe Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 7 May 2018 21:28:58 +0200 Subject: [PATCH 31/43] #542: improve tests for elliptic gradients changing parameters so that visual evaluation of correctness is easier. --- .../Drawing/FillEllipticGradientBrushTest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs b/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs index 72095d933..349fc90ba 100644 --- a/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs +++ b/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs @@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing EllipticGradientBrush unicolorLinearGradientBrush = new EllipticGradientBrush( new SixLabors.Primitives.Point(image.Width / 2, image.Height / 2), - new SixLabors.Primitives.Point(image.Width / 2, (image.Width * 3) / 2), + new SixLabors.Primitives.Point(image.Width / 2, (image.Width * 2) / 3), ratio, GradientRepetitionMode.None, new ColorStop(0, yellow), @@ -124,9 +124,9 @@ namespace SixLabors.ImageSharp.Tests.Drawing var cos = Math.Cos(rotation); var sin = Math.Sin(rotation); - int axisX = (int)((center.X * cos) - (center.Y * sin)); - int axisY = (int)((center.X * sin) + (center.Y * cos)); - + int offsetY = image.Height / 6; + int axisX = center.X + (int)-(offsetY * sin); + int axisY = center.Y + (int)(offsetY * cos); EllipticGradientBrush unicolorLinearGradientBrush = new EllipticGradientBrush( From f290896db177d78346c1bd3e2aa84fa73f10a522 Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 7 May 2018 21:31:29 +0200 Subject: [PATCH 32/43] #542: remove redundant Asserts, cleanup code --- .../Drawing/FillLinearGradientBrushTests.cs | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs index baf25a897..ba0c4d16f 100644 --- a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs @@ -63,29 +63,6 @@ namespace SixLabors.ImageSharp.Tests.Drawing image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); image.DebugSave(provider); - - using (PixelAccessor sourcePixels = image.Lock()) - { - TPixel columnColor0 = sourcePixels[0, 0]; - TPixel columnColor23 = sourcePixels[23, 0]; - TPixel columnColor42 = sourcePixels[42, 0]; - TPixel columnColor333 = sourcePixels[333, 0]; - - TPixel lastColumnColor = sourcePixels[lastColumnIndex, 0]; - - for (int i = 0; i < image.Height; i++) - { - // check first and last column: - Assert.Equal(columnColor0, sourcePixels[0, i]); - Assert.Equal(lastColumnColor, sourcePixels[lastColumnIndex, i]); - - // check the random colors: - Assert.True(columnColor23.Equals(sourcePixels[23, i]), $"at {i}"); - Assert.Equal(columnColor42, sourcePixels[42, i]); - Assert.Equal(columnColor333, sourcePixels[333, i]); - } - } - image.CompareToReferenceOutput(provider); } } @@ -102,8 +79,6 @@ namespace SixLabors.ImageSharp.Tests.Drawing { using (var image = provider.GetImage()) { - int lastColumnIndex = image.Width - 1; - TPixel red = NamedColors.Red; TPixel yellow = NamedColors.Yellow; @@ -302,7 +277,6 @@ namespace SixLabors.ImageSharp.Tests.Drawing [WithBlankImages(500, 500, PixelTypes.Rgba32, 0, 499, 499, 0, new[] { 0f, 0.2f, 0.5f, 0.9f }, new[] { 0, 1, 2, 3 })] [WithBlankImages(500, 500, PixelTypes.Rgba32, 499, 499, 0, 0, new[] { 0f, 0.7f, 0.8f, 0.9f}, new[] { 0, 1, 2, 0 })] [WithBlankImages(500, 500, PixelTypes.Rgba32, 0, 0, 499, 499, new[] { 0f, .5f, 1f}, new[]{0, 1, 3})] - // TODO: add some more tests with arbitrary gradient orders! public void ArbitraryGradients( TestImageProvider provider, int startX, int startY, From 46c8bd95a60cfaf0ae6542e605931aaeb39520c9 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 11 May 2018 21:37:20 +0200 Subject: [PATCH 33/43] #542: shorten test names, improve test image filenames --- .../Drawing/FillEllipticGradientBrushTest.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs b/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs index 349fc90ba..2311244fa 100644 --- a/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs +++ b/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing { [Theory] [WithBlankImages(10, 10, PixelTypes.Rgba32)] - public void EllipticGradientBrushWithEqualColorsAndReturnsUnicolorImage( + public void WithEqualColorsReturnsUnicolorImage( TestImageProvider provider) where TPixel : struct, IPixel { @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [WithBlankImages(200, 200, PixelTypes.Rgba32, 1.2)] [WithBlankImages(200, 200, PixelTypes.Rgba32, 1.6)] [WithBlankImages(200, 200, PixelTypes.Rgba32, 2.0)] - public void EllipticGradientBrushProducesAxisParallelEllipsesWithDifferentRatio( + public void AxisParallelEllipsesWithDifferentRatio( TestImageProvider provider, float ratio) where TPixel : struct, IPixel @@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing new ColorStop(1, black)); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - image.DebugSave(provider, ratio); + image.DebugSave(provider, ratio.ToString("F1")); image.CompareToReferenceOutput(provider, ratio); } } @@ -104,13 +104,13 @@ namespace SixLabors.ImageSharp.Tests.Drawing [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4, 30)] [WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8, 30)] [WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0, 30)] - public void EllipticGradientBrushProducesRotatedEllipsesWithDifferentRatio( + public void RotatedEllipsesWithDifferentRatio( TestImageProvider provider, float ratio, float rotationInDegree) where TPixel: struct, IPixel { - string variant = $"{ratio}at{rotationInDegree}°"; + string variant = $"{ratio:F2}at{rotationInDegree:00}°"; using (var image = provider.GetImage()) { From ea7cb83454aa84eb5790f1a656376dd635f15fe5 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 11 May 2018 21:39:00 +0200 Subject: [PATCH 34/43] #542: code cleanup --- tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs index ba0c4d16f..04a2417a1 100644 --- a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs @@ -49,7 +49,6 @@ namespace SixLabors.ImageSharp.Tests.Drawing { using (var image = provider.GetImage()) { - int lastColumnIndex = image.Width - 1; TPixel red = NamedColors.Red; TPixel yellow = NamedColors.Yellow; From 8f5cb5f1dd9f82688203b4f2148cb7c2f570cd2a Mon Sep 17 00:00:00 2001 From: Peter Amrehn Date: Fri, 11 May 2018 22:21:21 +0200 Subject: [PATCH 35/43] #542: cleanup test file names, fix naming for RadialGradientBrush test files --- .../Drawing/FillEllipticGradientBrushTest.cs | 2 +- .../Drawing/FillRadialGradientBrushTests.cs | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs b/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs index 2311244fa..7cf7775ce 100644 --- a/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs +++ b/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs @@ -13,7 +13,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Drawing { [GroupOutput("Drawing/GradientBrushes")] - public class FillEllipticGradientBrushTests : FileTestBase + public class FillEllipticGradientBrushTests { [Theory] [WithBlankImages(10, 10, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs index 60171e477..5388391cd 100644 --- a/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs @@ -7,11 +7,12 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Drawing { - public class FillRadialGradientBrushTests : FileTestBase + [GroupOutput("Drawing/GradientBrushes")] + public class FillRadialGradientBrushTests { [Theory] [WithBlankImages(200, 200, PixelTypes.Rgba32)] - public void RadialGradientBrushWithEqualColorsReturnsUnicolorImage( + public void WithEqualColorsReturnsUnicolorImage( TestImageProvider provider) where TPixel : struct, IPixel { @@ -40,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [WithBlankImages(200, 200, PixelTypes.Rgba32, 100, 0)] [WithBlankImages(200, 200, PixelTypes.Rgba32, 0, 100)] [WithBlankImages(200, 200, PixelTypes.Rgba32, -40, 100)] - public void RadialGradientBrushWithDifferentCentersReturnsImage( + public void WithDifferentCentersReturnsImage( TestImageProvider provider, int centerX, int centerY) @@ -57,7 +58,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing new ColorStop(1, NamedColors.Yellow)); image.Mutate(x => x.Fill(brush)); - image.DebugSave(provider); + image.DebugSave(provider, $"center{centerX:D3},{centerY:D3}"); image.CompareToReferenceOutput(provider); } } From e3889ab972c575fd0fbc76ff94ac5f27d0084160 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 11 May 2018 23:47:53 +0200 Subject: [PATCH 36/43] introduce TestImageExtensions.VerifyOperation(), simplify FillRadialGradientBrushTests and output file names --- .../Drawing/FillRadialGradientBrushTests.cs | 59 ++++++++------- .../TestUtilities/TestImageExtensions.cs | 73 ++++++++++++++++--- 2 files changed, 95 insertions(+), 37 deletions(-) diff --git a/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs index 5388391cd..b365156e0 100644 --- a/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs @@ -7,6 +7,8 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Drawing { + using System; + [GroupOutput("Drawing/GradientBrushes")] public class FillRadialGradientBrushTests { @@ -16,23 +18,23 @@ namespace SixLabors.ImageSharp.Tests.Drawing TestImageProvider provider) where TPixel : struct, IPixel { - using (var image = provider.GetImage()) - { - TPixel red = NamedColors.Red; - - RadialGradientBrush unicolorRadialGradientBrush = - new RadialGradientBrush( - new SixLabors.Primitives.Point(0, 0), - 100, - GradientRepetitionMode.None, - new ColorStop(0, red), - new ColorStop(1, red)); + provider.VerifyOperation( + image => + { + TPixel red = NamedColors.Red; - image.Mutate(x => x.Fill(unicolorRadialGradientBrush)); - image.DebugSave(provider); + var unicolorRadialGradientBrush = + new RadialGradientBrush( + new SixLabors.Primitives.Point(0, 0), + 100, + GradientRepetitionMode.None, + new ColorStop(0, red), + new ColorStop(1, red)); - image.CompareToReferenceOutput(provider); - } + image.Mutate(x => x.Fill(unicolorRadialGradientBrush)); + }, + false, + false); } [Theory] @@ -47,20 +49,21 @@ namespace SixLabors.ImageSharp.Tests.Drawing int centerY) where TPixel : struct, IPixel { - using (var image = provider.GetImage()) - { - RadialGradientBrush brush = - new RadialGradientBrush( - new SixLabors.Primitives.Point(centerX, centerY), - image.Width / 2f, - GradientRepetitionMode.None, - new ColorStop(0, NamedColors.Red), - new ColorStop(1, NamedColors.Yellow)); + provider.VerifyOperation( + image => + { + var brush = new RadialGradientBrush( + new SixLabors.Primitives.Point(centerX, centerY), + image.Width / 2f, + GradientRepetitionMode.None, + new ColorStop(0, NamedColors.Red), + new ColorStop(1, NamedColors.Yellow)); - image.Mutate(x => x.Fill(brush)); - image.DebugSave(provider, $"center{centerX:D3},{centerY:D3}"); - image.CompareToReferenceOutput(provider); - } + image.Mutate(x => x.Fill(brush)); + }, + $"center({centerX},{centerY})", + false, + false); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 34c93a7c1..c811b0241 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -173,7 +173,8 @@ namespace SixLabors.ImageSharp.Tests FormattableString testOutputDetails, string extension = "png", bool grayscale = false, - bool appendPixelTypeToFileName = true) + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) where TPixel : struct, IPixel { return image.CompareToReferenceOutput( @@ -181,7 +182,8 @@ namespace SixLabors.ImageSharp.Tests (object)testOutputDetails, extension, grayscale, - appendPixelTypeToFileName); + appendPixelTypeToFileName, + appendSourceFileOrDescription); } /// @@ -195,6 +197,7 @@ namespace SixLabors.ImageSharp.Tests /// The extension /// A boolean indicating whether we should debug save + compare against a grayscale image, smaller in size. /// A boolean indicating whether to append the pixel type to the output file name. + /// A boolean indicating whether to append to the test output file name. /// public static Image CompareToReferenceOutput( this Image image, @@ -202,7 +205,8 @@ namespace SixLabors.ImageSharp.Tests object testOutputDetails = null, string extension = "png", bool grayscale = false, - bool appendPixelTypeToFileName = true) + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) where TPixel : struct, IPixel { return CompareToReferenceOutput( @@ -212,7 +216,8 @@ namespace SixLabors.ImageSharp.Tests testOutputDetails, extension, grayscale, - appendPixelTypeToFileName); + appendPixelTypeToFileName, + appendSourceFileOrDescription); } public static Image CompareToReferenceOutput( @@ -246,6 +251,7 @@ namespace SixLabors.ImageSharp.Tests /// The extension /// A boolean indicating whether we should debug save + compare against a grayscale image, smaller in size. /// A boolean indicating whether to append the pixel type to the output file name. + /// A boolean indicating whether to append to the test output file name. /// public static Image CompareToReferenceOutput( this Image image, @@ -254,14 +260,16 @@ namespace SixLabors.ImageSharp.Tests object testOutputDetails = null, string extension = "png", bool grayscale = false, - bool appendPixelTypeToFileName = true) + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) where TPixel : struct, IPixel { using (Image referenceImage = GetReferenceOutputImage( provider, testOutputDetails, extension, - appendPixelTypeToFileName)) + appendPixelTypeToFileName, + appendSourceFileOrDescription)) { comparer.VerifySimilarity(referenceImage, image); } @@ -523,10 +531,57 @@ namespace SixLabors.ImageSharp.Tests } /// - /// Loads the expected image with a reference decoder + compares it to . - /// Also performs a debug save using . + /// Utility method for doing the following in one step: + /// 1. Executing an operation (taken as a delegate) + /// 2. Executing DebugSave() + /// 3. Executing CopareToReferenceOutput() + /// + internal static void VerifyOperation( + this TestImageProvider provider, + Action> operation, + FormattableString testOutputDetails, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + operation(image); + + image.DebugSave( + provider, + testOutputDetails, + appendPixelTypeToFileName: appendPixelTypeToFileName, + appendSourceFileOrDescription: appendSourceFileOrDescription); + + image.CompareToReferenceOutput(provider, + testOutputDetails, + appendPixelTypeToFileName: appendPixelTypeToFileName, + appendSourceFileOrDescription: appendSourceFileOrDescription); + } + } + + /// + /// Utility method for doing the following in one step: + /// 1. Executing an operation (taken as a delegate) + /// 2. Executing DebugSave() + /// 3. Executing CopareToReferenceOutput() /// - internal static void VerifyEncoder(this Image image, + internal static void VerifyOperation( + this TestImageProvider provider, + Action> operation, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : struct, IPixel + { + provider.VerifyOperation(operation, $"", appendPixelTypeToFileName, appendSourceFileOrDescription); + } + + /// + /// Loads the expected image with a reference decoder + compares it to . + /// Also performs a debug save using . + /// + internal static void VerifyEncoder(this Image image, ITestImageProvider provider, string extension, object testOutputDetails, From ce8ca9782aebe72e07f971800cf7c378a6df88d0 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 12 May 2018 00:01:49 +0200 Subject: [PATCH 37/43] FillLinearGradientBrushTests #1 --- .../Drawing/FillLinearGradientBrushTests.cs | 134 +++++++++--------- .../TestUtilities/ImagingTestCaseUtility.cs | 2 +- 2 files changed, 70 insertions(+), 66 deletions(-) diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs index 04a2417a1..9ea7ff08a 100644 --- a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs @@ -20,50 +20,48 @@ namespace SixLabors.ImageSharp.Tests.Drawing { [Theory] [WithBlankImages(10, 10, PixelTypes.Rgba32)] - public void WithEqualColorsReturnsUnicolorImage( - TestImageProvider provider) + public void WithEqualColorsReturnsUnicolorImage(TestImageProvider provider) where TPixel : struct, IPixel { - TPixel red = NamedColors.Red; - using (var image = provider.GetImage()) - { - LinearGradientBrush unicolorLinearGradientBrush = - new LinearGradientBrush( - new SixLabors.Primitives.Point(0, 0), - new SixLabors.Primitives.Point(10, 0), - GradientRepetitionMode.None, - new ColorStop(0, red), - new ColorStop(1, red)); - - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - image.DebugSave(provider); - image.CompareToReferenceOutput(provider); - } + provider.VerifyOperation( + image => + { + TPixel red = NamedColors.Red; + var unicolorLinearGradientBrush = new LinearGradientBrush( + new SixLabors.Primitives.Point(0, 0), + new SixLabors.Primitives.Point(10, 0), + GradientRepetitionMode.None, + new ColorStop(0, red), + new ColorStop(1, red)); + + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + }, + false, + false); } [Theory] [WithBlankImages(500, 10, PixelTypes.Rgba32)] - public void HorizontalReturnsUnicolorColumns( - TestImageProvider provider) + public void HorizontalReturnsUnicolorColumns(TestImageProvider provider) where TPixel : struct, IPixel { - using (var image = provider.GetImage()) - { - TPixel red = NamedColors.Red; - TPixel yellow = NamedColors.Yellow; - - LinearGradientBrush unicolorLinearGradientBrush = - new LinearGradientBrush( - new SixLabors.Primitives.Point(0, 0), - new SixLabors.Primitives.Point(image.Width, 0), - GradientRepetitionMode.None, - new ColorStop(0, red), - new ColorStop(1, yellow)); - - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - image.DebugSave(provider); - image.CompareToReferenceOutput(provider); - } + provider.VerifyOperation( + image => + { + TPixel red = NamedColors.Red; + TPixel yellow = NamedColors.Yellow; + + LinearGradientBrush unicolorLinearGradientBrush = new LinearGradientBrush( + new SixLabors.Primitives.Point(0, 0), + new SixLabors.Primitives.Point(image.Width, 0), + GradientRepetitionMode.None, + new ColorStop(0, red), + new ColorStop(1, yellow)); + + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + }, + false, + false); } [Theory] @@ -76,23 +74,23 @@ namespace SixLabors.ImageSharp.Tests.Drawing GradientRepetitionMode repetitionMode) where TPixel : struct, IPixel { - using (var image = provider.GetImage()) - { - TPixel red = NamedColors.Red; - TPixel yellow = NamedColors.Yellow; - - LinearGradientBrush unicolorLinearGradientBrush = - new LinearGradientBrush( - new SixLabors.Primitives.Point(0, 0), - new SixLabors.Primitives.Point(image.Width / 10, 0), - repetitionMode, - new ColorStop(0, red), - new ColorStop(1, yellow)); - - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - image.DebugSave(provider, repetitionMode); - image.CompareToReferenceOutput(provider, repetitionMode); - } + provider.VerifyOperation( + image => + { + TPixel red = NamedColors.Red; + TPixel yellow = NamedColors.Yellow; + + var unicolorLinearGradientBrush = new LinearGradientBrush( + new SixLabors.Primitives.Point(0, 0), + new SixLabors.Primitives.Point(image.Width / 10, 0), + repetitionMode, + new ColorStop(0, red), + new ColorStop(1, yellow)); + + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + }, + false, + false); } [Theory] @@ -104,7 +102,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing float[] pattern) where TPixel : struct, IPixel { - string variant = string.Join(",", pattern.Select(i => i.ToString(CultureInfo.InvariantCulture))); + string variant = string.Join("_", pattern.Select(i => i.ToString(CultureInfo.InvariantCulture))); // ensure the input data is valid Assert.True(pattern.Length > 0); @@ -127,7 +125,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing using (Image image = provider.GetImage()) { - LinearGradientBrush unicolorLinearGradientBrush = + var unicolorLinearGradientBrush = new LinearGradientBrush( new SixLabors.Primitives.Point(0, 0), new SixLabors.Primitives.Point(image.Width, 0), @@ -135,17 +133,23 @@ namespace SixLabors.ImageSharp.Tests.Drawing colorStops); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - image.DebugSave(provider, variant); - - using (PixelAccessor sourcePixels = image.Lock()) - { - // the result must be a black and white pattern, no other color should occur: - Assert.All( - Enumerable.Range(0, image.Width).Select(i => sourcePixels[i, 0]), - color => Assert.True(color.Equals(black) || color.Equals(white))); - } - image.CompareToReferenceOutput(provider, variant); + image.DebugSave( + provider, + variant, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + + // the result must be a black and white pattern, no other color should occur: + Assert.All( + Enumerable.Range(0, image.Width).Select(i => image[i, 0]), + color => Assert.True(color.Equals(black) || color.Equals(white))); + + image.CompareToReferenceOutput( + provider, + variant, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs index 2177551af..bfd120fff 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -92,7 +92,7 @@ namespace SixLabors.ImageSharp.Tests details = '_' + details; } - return $"{this.GetTestOutputDir()}/{this.TestName}{pixName}{fn}{details}{extension}"; + return TestUtils.AsInvariantString($"{this.GetTestOutputDir()}/{this.TestName}{pixName}{fn}{details}{extension}"); } /// From e818a5474cb055dfe9588f8034a2780c6fea356f Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 12 May 2018 00:11:13 +0200 Subject: [PATCH 38/43] VerticalReturnsUnicolorColumns -> VerticalBrushReturnsUnicolorRows (+ simplify test code) --- .../Drawing/FillLinearGradientBrushTests.cs | 69 ++++++++----------- 1 file changed, 28 insertions(+), 41 deletions(-) diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs index 9ea7ff08a..07bdbc74e 100644 --- a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs @@ -12,9 +12,12 @@ using SixLabors.ImageSharp.Processing.Drawing; using SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes; using Xunit; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Drawing { + using SixLabors.ImageSharp.Advanced; + [GroupOutput("Drawing/GradientBrushes")] public class FillLinearGradientBrushTests { @@ -51,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing TPixel red = NamedColors.Red; TPixel yellow = NamedColors.Yellow; - LinearGradientBrush unicolorLinearGradientBrush = new LinearGradientBrush( + var unicolorLinearGradientBrush = new LinearGradientBrush( new SixLabors.Primitives.Point(0, 0), new SixLabors.Primitives.Point(image.Width, 0), GradientRepetitionMode.None, @@ -155,57 +158,41 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Theory] [WithBlankImages(10, 500, PixelTypes.Rgba32)] - public void VerticalReturnsUnicolorColumns( + public void VerticalBrushReturnsUnicolorRows( TestImageProvider provider) where TPixel : struct, IPixel { - using (var image = provider.GetImage()) - { - int lastRowIndex = image.Height - 1; - - TPixel red = NamedColors.Red; - TPixel yellow = NamedColors.Yellow; + provider.VerifyOperation( + image => + { + TPixel red = NamedColors.Red; + TPixel yellow = NamedColors.Yellow; - LinearGradientBrush unicolorLinearGradientBrush = - new LinearGradientBrush( - new SixLabors.Primitives.Point(0, 0), - new SixLabors.Primitives.Point(0, image.Height), - GradientRepetitionMode.None, - new ColorStop(0, red), - new ColorStop(1, yellow)); + var unicolorLinearGradientBrush = new LinearGradientBrush( + new SixLabors.Primitives.Point(0, 0), + new SixLabors.Primitives.Point(0, image.Height), + GradientRepetitionMode.None, + new ColorStop(0, red), + new ColorStop(1, yellow)); - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - image.DebugSave(provider); + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - Random random = new Random(); + VerifyAllRowsAreUnicolor(image); + }, + false, + false); - using (PixelAccessor sourcePixels = image.Lock()) + void VerifyAllRowsAreUnicolor(Image image) + { + for (int y = 0; y < image.Height; y++) { - TPixel firstRowColor = sourcePixels[0, 0]; - - int columnA = random.Next(0, image.Height); - int columnB = random.Next(0, image.Height); - int columnC = random.Next(0, image.Height); - TPixel columnColorA = sourcePixels[0, columnA]; - TPixel columnColorB = sourcePixels[0, columnB]; - TPixel columnColorC = sourcePixels[0, columnC]; - - TPixel lastRowColor = sourcePixels[0, lastRowIndex]; - - for (int i = 0; i < image.Width; i++) + Span row = image.GetPixelRowSpan(y); + TPixel firstColorOfRow = row[0]; + foreach (TPixel p in row) { - // check first and last column, these are known: - Assert.Equal(firstRowColor, sourcePixels[i, 0]); - Assert.Equal(lastRowColor, sourcePixels[i, lastRowIndex]); - - // check the random colors: - Assert.Equal(columnColorA, sourcePixels[i, columnA]); - Assert.Equal(columnColorB, sourcePixels[i, columnB]); - Assert.Equal(columnColorC, sourcePixels[i, columnC]); + Assert.Equal(firstColorOfRow, p); } } - - image.CompareToReferenceOutput(provider); } } From d53e015be454fcf1b656226a4d60738c8ee28077 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 12 May 2018 00:29:51 +0200 Subject: [PATCH 39/43] finish refactoring FillLinearGradientBrushTests --- .../Drawing/FillLinearGradientBrushTests.cs | 118 ++++++++++-------- 1 file changed, 69 insertions(+), 49 deletions(-) diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs index 07bdbc74e..955209f9c 100644 --- a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs @@ -43,6 +43,28 @@ namespace SixLabors.ImageSharp.Tests.Drawing false); } + [Theory] + [WithBlankImages(20, 10, PixelTypes.Rgba32)] + [WithBlankImages(20, 10, PixelTypes.Argb32)] + [WithBlankImages(20, 10, PixelTypes.Rgb24)] + public void DoesNotDependOnSinglePixelType(TestImageProvider provider) + where TPixel : struct, IPixel + { + provider.VerifyOperation( + image => + { + var unicolorLinearGradientBrush = new LinearGradientBrush( + new SixLabors.Primitives.Point(0, 0), + new SixLabors.Primitives.Point(image.Width, 0), + GradientRepetitionMode.None, + new ColorStop(0, NamedColors.Blue), + new ColorStop(1, NamedColors.Yellow)); + + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + }, + appendSourceFileOrDescription: false); + } + [Theory] [WithBlankImages(500, 10, PixelTypes.Rgba32)] public void HorizontalReturnsUnicolorColumns(TestImageProvider provider) @@ -92,6 +114,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); }, + $"{repetitionMode}", false, false); } @@ -205,16 +228,16 @@ namespace SixLabors.ImageSharp.Tests.Drawing } [Theory] - [WithBlankImages(500, 500, PixelTypes.Rgba32, ImageCorner.TopLeft)] - [WithBlankImages(500, 500, PixelTypes.Rgba32, ImageCorner.TopRight)] - [WithBlankImages(500, 500, PixelTypes.Rgba32, ImageCorner.BottomLeft)] - [WithBlankImages(500, 500, PixelTypes.Rgba32, ImageCorner.BottomRight)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, ImageCorner.TopLeft)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, ImageCorner.TopRight)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, ImageCorner.BottomLeft)] + [WithBlankImages(200, 200, PixelTypes.Rgba32, ImageCorner.BottomRight)] public void DiagonalReturnsCorrectImages( TestImageProvider provider, ImageCorner startCorner) where TPixel : struct, IPixel { - using (var image = provider.GetImage()) + using (Image image = provider.GetImage()) { Assert.True(image.Height == image.Width, "For the math check block at the end the image must be squared, but it is not."); @@ -226,7 +249,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing TPixel red = NamedColors.Red; TPixel yellow = NamedColors.Yellow; - LinearGradientBrush unicolorLinearGradientBrush = + var unicolorLinearGradientBrush = new LinearGradientBrush( new SixLabors.Primitives.Point(startX, startY), new SixLabors.Primitives.Point(endX, endY), @@ -235,30 +258,35 @@ namespace SixLabors.ImageSharp.Tests.Drawing new ColorStop(1, yellow)); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - image.DebugSave(provider, startCorner); + image.DebugSave( + provider, + startCorner, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); int verticalSign = startY == 0 ? 1 : -1; int horizontalSign = startX == 0 ? 1 : -1; - using (PixelAccessor sourcePixels = image.Lock()) - { - // check first and last pixel, these are known: - Assert.Equal(red, sourcePixels[startX, startY]); - Assert.Equal(yellow, sourcePixels[endX, endY]); + // check first and last pixel, these are known: + Assert.Equal(red, image[startX, startY]); + Assert.Equal(yellow, image[endX, endY]); - for (int i = 0; i < image.Height; i++) + for (int i = 0; i < image.Height; i++) + { + // it's diagonal, so for any (a, a) on the gradient line, for all (a-x, b+x) - +/- depending on the diagonal direction - must be the same color) + TPixel colorOnDiagonal = image[i, i]; + int orthoCount = 0; + for (int offset = -orthoCount; offset < orthoCount; offset++) { - // it's diagonal, so for any (a, a) on the gradient line, for all (a-x, b+x) - +/- depending on the diagonal direction - must be the same color) - TPixel colorOnDiagonal = sourcePixels[i, i]; - int orthoCount = 0; - for (int offset = -orthoCount; offset < orthoCount; offset++) - { - Assert.Equal(colorOnDiagonal, sourcePixels[i + horizontalSign * offset, i + verticalSign * offset]); - } + Assert.Equal(colorOnDiagonal, image[i + horizontalSign * offset, i + verticalSign * offset]); } } - image.CompareToReferenceOutput(provider, startCorner); + image.CompareToReferenceOutput( + provider, + startCorner, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); } } @@ -275,46 +303,38 @@ namespace SixLabors.ImageSharp.Tests.Drawing int[] stopColorCodes) where TPixel : struct, IPixel { - var colors = new [] - { - NamedColors.Navy, - NamedColors.LightGreen, - NamedColors.Yellow, + TPixel[] colors = { + NamedColors.Navy, NamedColors.LightGreen, NamedColors.Yellow, NamedColors.Red }; - StringBuilder coloringVariant = new StringBuilder(); - var colorStops = new ColorStop[stopPositions.Length]; + var coloringVariant = new StringBuilder(); + ColorStop[] colorStops = new ColorStop[stopPositions.Length]; for (int i = 0; i < stopPositions.Length; i++) { TPixel color = colors[stopColorCodes[i % colors.Length]]; float position = stopPositions[i]; - colorStops[i] = new ColorStop( - position, - color); - coloringVariant.AppendFormat( - CultureInfo.InvariantCulture, - "{0}@{1};", - color, - position); + colorStops[i] = new ColorStop(position, color); + coloringVariant.AppendFormat(CultureInfo.InvariantCulture, "{0}@{1};", color, position); } - string variant = $"{startX},{startY}to{endX},{endY};[{coloringVariant}]"; + FormattableString variant = $"({startX},{startY})_TO_({endX},{endY})__[{coloringVariant}]"; - using (var image = provider.GetImage()) - { - LinearGradientBrush unicolorLinearGradientBrush = - new LinearGradientBrush( - new SixLabors.Primitives.Point(startX, startY), - new SixLabors.Primitives.Point(endX, endY), - GradientRepetitionMode.None, - colorStops); + provider.VerifyOperation( + image => + { + var unicolorLinearGradientBrush = new LinearGradientBrush( + new SixLabors.Primitives.Point(startX, startY), + new SixLabors.Primitives.Point(endX, endY), + GradientRepetitionMode.None, + colorStops); - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - image.DebugSave(provider, variant); - image.CompareToReferenceOutput(provider, variant); - } + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + }, + variant, + false, + false); } } } \ No newline at end of file From a217e42a481862eb1b665529441286b131d4cc23 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 13 May 2018 11:07:46 +0200 Subject: [PATCH 40/43] FillEllipticGradientBrushTests --- .../Drawing/FillEllipticGradientBrushTest.cs | 112 +++++++++--------- .../Drawing/FillLinearGradientBrushTests.cs | 31 ++--- .../Drawing/FillRadialGradientBrushTests.cs | 32 ++--- .../TestUtilities/TestImageExtensions.cs | 8 +- 4 files changed, 95 insertions(+), 88 deletions(-) diff --git a/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs b/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs index 7cf7775ce..5801e7276 100644 --- a/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs +++ b/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing using (Image image = provider.GetImage()) { - EllipticGradientBrush unicolorLinearGradientBrush = + var unicolorLinearGradientBrush = new EllipticGradientBrush( new SixLabors.Primitives.Point(0, 0), new SixLabors.Primitives.Point(10, 0), @@ -35,17 +35,11 @@ namespace SixLabors.ImageSharp.Tests.Drawing new ColorStop(1, red)); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - image.DebugSave(provider); - using (PixelAccessor sourcePixels = image.Lock()) - { - Assert.Equal(red, sourcePixels[0, 0]); - Assert.Equal(red, sourcePixels[9, 9]); - Assert.Equal(red, sourcePixels[5, 5]); - Assert.Equal(red, sourcePixels[3, 8]); - } + image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); - image.CompareToReferenceOutput(provider); + // no need for reference image in this test: + image.ComparePixelBufferTo(red); } } @@ -66,22 +60,23 @@ namespace SixLabors.ImageSharp.Tests.Drawing TPixel red = NamedColors.Red; TPixel black = NamedColors.Black; - using (var image = provider.GetImage()) - { - EllipticGradientBrush unicolorLinearGradientBrush = - new EllipticGradientBrush( - new SixLabors.Primitives.Point(image.Width / 2, image.Height / 2), - new SixLabors.Primitives.Point(image.Width / 2, (image.Width * 2) / 3), - ratio, - GradientRepetitionMode.None, - new ColorStop(0, yellow), - new ColorStop(1, red), - new ColorStop(1, black)); - - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - image.DebugSave(provider, ratio.ToString("F1")); - image.CompareToReferenceOutput(provider, ratio); - } + provider.VerifyOperation( + image => + { + var unicolorLinearGradientBrush = new EllipticGradientBrush( + new SixLabors.Primitives.Point(image.Width / 2, image.Height / 2), + new SixLabors.Primitives.Point(image.Width / 2, (image.Width * 2) / 3), + ratio, + GradientRepetitionMode.None, + new ColorStop(0, yellow), + new ColorStop(1, red), + new ColorStop(1, black)); + + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + }, + $"{ratio:F2}", + false, + false); } [Theory] @@ -110,38 +105,39 @@ namespace SixLabors.ImageSharp.Tests.Drawing float rotationInDegree) where TPixel: struct, IPixel { - string variant = $"{ratio:F2}at{rotationInDegree:00}°"; - - using (var image = provider.GetImage()) - { - TPixel yellow = NamedColors.Yellow; - TPixel red = NamedColors.Red; - TPixel black = NamedColors.Black; - - var center = new SixLabors.Primitives.Point(image.Width / 2, image.Height / 2); - - var rotation = (Math.PI * rotationInDegree) / 180.0; - var cos = Math.Cos(rotation); - var sin = Math.Sin(rotation); - - int offsetY = image.Height / 6; - int axisX = center.X + (int)-(offsetY * sin); - int axisY = center.Y + (int)(offsetY * cos); - - EllipticGradientBrush unicolorLinearGradientBrush = - new EllipticGradientBrush( - center, - new SixLabors.Primitives.Point(axisX, axisY), - ratio, - GradientRepetitionMode.None, - new ColorStop(0, yellow), - new ColorStop(1, red), - new ColorStop(1, black)); - - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - image.DebugSave(provider, variant); - image.CompareToReferenceOutput(provider, variant); - } + FormattableString variant = $"{ratio:F2}_AT_{rotationInDegree:00}deg"; + + provider.VerifyOperation( + image => + { + TPixel yellow = NamedColors.Yellow; + TPixel red = NamedColors.Red; + TPixel black = NamedColors.Black; + + var center = new SixLabors.Primitives.Point(image.Width / 2, image.Height / 2); + + double rotation = (Math.PI * rotationInDegree) / 180.0; + double cos = Math.Cos(rotation); + double sin = Math.Sin(rotation); + + int offsetY = image.Height / 6; + int axisX = center.X + (int)-(offsetY * sin); + int axisY = center.Y + (int)(offsetY * cos); + + var unicolorLinearGradientBrush = new EllipticGradientBrush( + center, + new SixLabors.Primitives.Point(axisX, axisY), + ratio, + GradientRepetitionMode.None, + new ColorStop(0, yellow), + new ColorStop(1, red), + new ColorStop(1, black)); + + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + }, + variant, + false, + false); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs index 955209f9c..a9f08eb44 100644 --- a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs @@ -26,21 +26,24 @@ namespace SixLabors.ImageSharp.Tests.Drawing public void WithEqualColorsReturnsUnicolorImage(TestImageProvider provider) where TPixel : struct, IPixel { - provider.VerifyOperation( - image => - { - TPixel red = NamedColors.Red; - var unicolorLinearGradientBrush = new LinearGradientBrush( - new SixLabors.Primitives.Point(0, 0), - new SixLabors.Primitives.Point(10, 0), - GradientRepetitionMode.None, - new ColorStop(0, red), - new ColorStop(1, red)); + using (Image image = provider.GetImage()) + { + TPixel red = NamedColors.Red; - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - }, - false, - false); + var unicolorLinearGradientBrush = new LinearGradientBrush( + new SixLabors.Primitives.Point(0, 0), + new SixLabors.Primitives.Point(10, 0), + GradientRepetitionMode.None, + new ColorStop(0, red), + new ColorStop(1, red)); + + image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); + + image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); + + // no need for reference image in this test: + image.ComparePixelBufferTo(red); + } } [Theory] diff --git a/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs index b365156e0..98004326e 100644 --- a/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs @@ -18,23 +18,25 @@ namespace SixLabors.ImageSharp.Tests.Drawing TestImageProvider provider) where TPixel : struct, IPixel { - provider.VerifyOperation( - image => - { - TPixel red = NamedColors.Red; + using (Image image = provider.GetImage()) + { + TPixel red = NamedColors.Red; - var unicolorRadialGradientBrush = - new RadialGradientBrush( - new SixLabors.Primitives.Point(0, 0), - 100, - GradientRepetitionMode.None, - new ColorStop(0, red), - new ColorStop(1, red)); + var unicolorRadialGradientBrush = + new RadialGradientBrush( + new SixLabors.Primitives.Point(0, 0), + 100, + GradientRepetitionMode.None, + new ColorStop(0, red), + new ColorStop(1, red)); - image.Mutate(x => x.Fill(unicolorRadialGradientBrush)); - }, - false, - false); + image.Mutate(x => x.Fill(unicolorRadialGradientBrush)); + + image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); + + // no need for reference image in this test: + image.ComparePixelBufferTo(red); + } } [Theory] diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index c811b0241..f4c7869e2 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -450,6 +450,9 @@ namespace SixLabors.ImageSharp.Tests return image; } + /// + /// All pixels in all frames should be exactly equal to 'expectedPixel'. + /// public static Image ComparePixelBufferTo(this Image image, TPixel expectedPixel) where TPixel : struct, IPixel { @@ -461,6 +464,9 @@ namespace SixLabors.ImageSharp.Tests return image; } + /// + /// All pixels in the frame should be exactly equal to 'expectedPixel'. + /// public static ImageFrame ComparePixelBufferTo(this ImageFrame imageFrame, TPixel expectedPixel) where TPixel : struct, IPixel { @@ -473,7 +479,7 @@ namespace SixLabors.ImageSharp.Tests return imageFrame; } - + public static ImageFrame ComparePixelBufferTo( this ImageFrame image, Span expectedPixels) From 5158f0bf5ed2b16152fa143e7a678c84c36486ea Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 13 May 2018 11:12:12 +0200 Subject: [PATCH 41/43] update submodule --- tests/Images/External | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Images/External b/tests/Images/External index f641620eb..8ab54f800 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit f641620eb5378db49d6153bbf1443ad13bda2379 +Subproject commit 8ab54f8003aff94b3a9662b0be46b0062cad6b74 From 9fa1e3e4df6a92808f39f99dcede0d8c01652e10 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 13 May 2018 11:42:37 +0200 Subject: [PATCH 42/43] add tolerance to comparison in tests --- .../Drawing/FillEllipticGradientBrushTest.cs | 6 ++ .../Drawing/FillLinearGradientBrushTests.cs | 8 ++ .../Drawing/FillRadialGradientBrushTests.cs | 5 ++ .../TestUtilities/TestImageExtensions.cs | 81 +++++++++++++++---- 4 files changed, 85 insertions(+), 15 deletions(-) diff --git a/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs b/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs index 5801e7276..7c9fa2088 100644 --- a/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs +++ b/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs @@ -12,9 +12,13 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Drawing { + using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + [GroupOutput("Drawing/GradientBrushes")] public class FillEllipticGradientBrushTests { + public static ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.01f); + [Theory] [WithBlankImages(10, 10, PixelTypes.Rgba32)] public void WithEqualColorsReturnsUnicolorImage( @@ -61,6 +65,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing TPixel black = NamedColors.Black; provider.VerifyOperation( + TolerantComparer, image => { var unicolorLinearGradientBrush = new EllipticGradientBrush( @@ -108,6 +113,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing FormattableString variant = $"{ratio:F2}_AT_{rotationInDegree:00}deg"; provider.VerifyOperation( + TolerantComparer, image => { TPixel yellow = NamedColors.Yellow; diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs index a9f08eb44..78b7d11e0 100644 --- a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs @@ -17,10 +17,13 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Drawing { using SixLabors.ImageSharp.Advanced; + using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; [GroupOutput("Drawing/GradientBrushes")] public class FillLinearGradientBrushTests { + public static ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.01f); + [Theory] [WithBlankImages(10, 10, PixelTypes.Rgba32)] public void WithEqualColorsReturnsUnicolorImage(TestImageProvider provider) @@ -54,6 +57,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing where TPixel : struct, IPixel { provider.VerifyOperation( + TolerantComparer, image => { var unicolorLinearGradientBrush = new LinearGradientBrush( @@ -74,6 +78,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing where TPixel : struct, IPixel { provider.VerifyOperation( + TolerantComparer, image => { TPixel red = NamedColors.Red; @@ -103,6 +108,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing where TPixel : struct, IPixel { provider.VerifyOperation( + TolerantComparer, image => { TPixel red = NamedColors.Red; @@ -175,6 +181,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing color => Assert.True(color.Equals(black) || color.Equals(white))); image.CompareToReferenceOutput( + TolerantComparer, provider, variant, appendPixelTypeToFileName: false, @@ -286,6 +293,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing } image.CompareToReferenceOutput( + TolerantComparer, provider, startCorner, appendPixelTypeToFileName: false, diff --git a/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs index 98004326e..eafbf3df1 100644 --- a/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs @@ -9,9 +9,13 @@ namespace SixLabors.ImageSharp.Tests.Drawing { using System; + using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + [GroupOutput("Drawing/GradientBrushes")] public class FillRadialGradientBrushTests { + public static ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.01f); + [Theory] [WithBlankImages(200, 200, PixelTypes.Rgba32)] public void WithEqualColorsReturnsUnicolorImage( @@ -52,6 +56,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing where TPixel : struct, IPixel { provider.VerifyOperation( + TolerantComparer, image => { var brush = new RadialGradientBrush( diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index f4c7869e2..b8c0489c8 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -544,6 +544,7 @@ namespace SixLabors.ImageSharp.Tests /// internal static void VerifyOperation( this TestImageProvider provider, + ImageComparer comparer, Action> operation, FormattableString testOutputDetails, bool appendPixelTypeToFileName = true, @@ -560,13 +561,58 @@ namespace SixLabors.ImageSharp.Tests appendPixelTypeToFileName: appendPixelTypeToFileName, appendSourceFileOrDescription: appendSourceFileOrDescription); - image.CompareToReferenceOutput(provider, + image.CompareToReferenceOutput(comparer, + provider, testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName, appendSourceFileOrDescription: appendSourceFileOrDescription); } } + /// + /// Utility method for doing the following in one step: + /// 1. Executing an operation (taken as a delegate) + /// 2. Executing DebugSave() + /// 3. Executing CopareToReferenceOutput() + /// + internal static void VerifyOperation( + this TestImageProvider provider, + Action> operation, + FormattableString testOutputDetails, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : struct, IPixel + { + provider.VerifyOperation( + ImageComparer.Tolerant(), + operation, + testOutputDetails, + appendPixelTypeToFileName, + appendSourceFileOrDescription); + } + + /// + /// Utility method for doing the following in one step: + /// 1. Executing an operation (taken as a delegate) + /// 2. Executing DebugSave() + /// 3. Executing CopareToReferenceOutput() + /// + internal static void VerifyOperation( + this TestImageProvider provider, + ImageComparer comparer, + Action> operation, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : struct, IPixel + { + provider.VerifyOperation( + comparer, + operation, + $"", + appendPixelTypeToFileName, + appendSourceFileOrDescription); + } + /// /// Utility method for doing the following in one step: /// 1. Executing an operation (taken as a delegate) @@ -582,23 +628,28 @@ namespace SixLabors.ImageSharp.Tests { provider.VerifyOperation(operation, $"", appendPixelTypeToFileName, appendSourceFileOrDescription); } - + /// - /// Loads the expected image with a reference decoder + compares it to . - /// Also performs a debug save using . - /// - internal static void VerifyEncoder(this Image image, - ITestImageProvider provider, - string extension, - object testOutputDetails, - IImageEncoder encoder, - ImageComparer customComparer = null, - bool appendPixelTypeToFileName = true, - string referenceImageExtension = null - ) + /// Loads the expected image with a reference decoder + compares it to . + /// Also performs a debug save using . + /// + internal static void VerifyEncoder( + this Image image, + ITestImageProvider provider, + string extension, + object testOutputDetails, + IImageEncoder encoder, + ImageComparer customComparer = null, + bool appendPixelTypeToFileName = true, + string referenceImageExtension = null) where TPixel : struct, IPixel { - string actualOutputFile = provider.Utility.SaveTestOutputFile(image, extension, encoder, testOutputDetails, appendPixelTypeToFileName); + string actualOutputFile = provider.Utility.SaveTestOutputFile( + image, + extension, + encoder, + testOutputDetails, + appendPixelTypeToFileName); IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); using (var actualImage = Image.Load(actualOutputFile, referenceDecoder)) From 5d3daaacbe23c914f9633b264cc30ee6f3ec850a Mon Sep 17 00:00:00 2001 From: Peter Amrehn Date: Mon, 14 May 2018 18:52:02 +0200 Subject: [PATCH 43/43] #542: apply naming scheme for abstract classes --- .../GradientBrushes/EllipticGradientBrush{TPixel}.cs | 6 +++--- ...ntBrush{TPixel}.cs => GradientBrushBase{TPixel}.cs} | 10 +++++----- .../GradientBrushes/LinearGradientBrush{TPixel}.cs | 4 ++-- .../GradientBrushes/RadialGradientBrush{TPixel}.cs | 6 +++--- .../Drawing/FillLinearGradientBrushTests.cs | 4 ++-- 5 files changed, 15 insertions(+), 15 deletions(-) rename src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/{AbstractGradientBrush{TPixel}.cs => GradientBrushBase{TPixel}.cs} (95%) diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush{TPixel}.cs index 74effa861..43f7fe04e 100644 --- a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush{TPixel}.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes /// the ratio between longest and shortest extension. /// /// The Pixel format that is used. - public sealed class EllipticGradientBrush : AbstractGradientBrush + public sealed class EllipticGradientBrush : GradientBrushBase where TPixel : struct, IPixel { private readonly Point center; @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes private readonly float axisRatio; - /// + /// /// The center of the elliptical gradient and 0 for the color stops. /// The end point of the reference axis of the ellipse. /// @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes this.RepetitionMode); /// - private sealed class RadialGradientBrushApplicator : AbstractGradientBrushApplicator + private sealed class RadialGradientBrushApplicator : GradientBrushApplicatorBase { private readonly Point center; diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/AbstractGradientBrush{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/GradientBrushBase{TPixel}.cs similarity index 95% rename from src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/AbstractGradientBrush{TPixel}.cs rename to src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/GradientBrushBase{TPixel}.cs index c963c9831..d0a1ef1c2 100644 --- a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/AbstractGradientBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/GradientBrushBase{TPixel}.cs @@ -11,13 +11,13 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes /// Base class for Gradient brushes /// /// The pixel format - public abstract class AbstractGradientBrush : IBrush + public abstract class GradientBrushBase : IBrush where TPixel : struct, IPixel { /// /// Defines how the colors are repeated beyond the interval [0..1] /// The gradient colors. - protected AbstractGradientBrush( + protected GradientBrushBase( GradientRepetitionMode repetitionMode, params ColorStop[] colorStops) { @@ -44,20 +44,20 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes /// /// Base class for gradient brush applicators /// - protected abstract class AbstractGradientBrushApplicator : BrushApplicator + protected abstract class GradientBrushApplicatorBase : BrushApplicator { private readonly ColorStop[] colorStops; private readonly GradientRepetitionMode repetitionMode; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The target. /// The options. /// An array of color stops sorted by their position. /// Defines if and how the gradient should be repeated. - protected AbstractGradientBrushApplicator( + protected GradientBrushApplicatorBase( ImageFrame target, GraphicsOptions options, ColorStop[] colorStops, diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/LinearGradientBrush{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/LinearGradientBrush{TPixel}.cs index 6cfa4651b..09f816dd9 100644 --- a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/LinearGradientBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/LinearGradientBrush{TPixel}.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes /// - a set of colors in relative distances to each other. /// /// The pixel format - public sealed class LinearGradientBrush : AbstractGradientBrush + public sealed class LinearGradientBrush : GradientBrushBase where TPixel : struct, IPixel { private readonly Point p1; @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes /// /// The linear gradient brush applicator. /// - private sealed class LinearGradientBrushApplicator : AbstractGradientBrushApplicator + private sealed class LinearGradientBrushApplicator : GradientBrushApplicatorBase { private readonly Point start; diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/RadialGradientBrush{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/RadialGradientBrush{TPixel}.cs index d1a99a015..5c0d8051c 100644 --- a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/RadialGradientBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/RadialGradientBrush{TPixel}.cs @@ -9,14 +9,14 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes /// A Circular Gradient Brush, defined by center point and radius. /// /// The pixel format. - public sealed class RadialGradientBrush : AbstractGradientBrush + public sealed class RadialGradientBrush : GradientBrushBase where TPixel : struct, IPixel { private readonly Point center; private readonly float radius; - /// + /// /// The center of the circular gradient and 0 for the color stops. /// The radius of the circular gradient and 1 for the color stops. /// Defines how the colors in the gradient are repeated. @@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes this.RepetitionMode); /// - private sealed class RadialGradientBrushApplicator : AbstractGradientBrushApplicator + private sealed class RadialGradientBrushApplicator : GradientBrushApplicatorBase { private readonly Point center; diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs index 78b7d11e0..9e7af1e57 100644 --- a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs @@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing new ColorStop(1, red)); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - + image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); // no need for reference image in this test: @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing new ColorStop(1, NamedColors.Yellow)); image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - }, + }, appendSourceFileOrDescription: false); }