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];
}
diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/ColorStop{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/ColorStop{TPixel}.cs
new file mode 100644
index 000000000..298af5cb5
--- /dev/null
+++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/ColorStop{TPixel}.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/GradientBrushes/EllipticGradientBrush{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush{TPixel}.cs
new file mode 100644
index 000000000..43f7fe04e
--- /dev/null
+++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush{TPixel}.cs
@@ -0,0 +1,167 @@
+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 sealed class EllipticGradientBrush : GradientBrushBase
+ 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.
+ ///
+ /// 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(repetitionMode, 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,
+ this.RepetitionMode);
+
+ ///
+ private sealed class RadialGradientBrushApplicator : GradientBrushApplicatorBase
+ {
+ private readonly Point center;
+
+ private readonly Point referenceAxisEnd;
+
+ private readonly float axisRatio;
+
+ private readonly double rotation;
+
+ private readonly float referenceRadius;
+
+ 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.
+ ///
+ /// 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
+ /// Defines how the gradient colors are repeated.
+ public RadialGradientBrushApplicator(
+ ImageFrame target,
+ GraphicsOptions options,
+ Point center,
+ Point referenceAxisEnd,
+ float axisRatio,
+ ColorStop[] colorStops,
+ GradientRepetitionMode repetitionMode)
+ : base(target, options, colorStops, repetitionMode)
+ {
+ 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;
+
+ 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);
+ }
+
+ ///
+ public override void Dispose()
+ {
+ }
+
+ ///
+ protected override float PositionOnGradient(int xt, int yt)
+ {
+ float x0 = xt - this.center.X;
+ float y0 = yt - this.center.Y;
+
+ 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 = (xSquared / this.referenceRadiusSquared)
+ + (ySquared / this.secondRadiusSquared);
+
+ 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)
+ {
+ 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);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/GradientBrushBase{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/GradientBrushBase{TPixel}.cs
new file mode 100644
index 000000000..d0a1ef1c2
--- /dev/null
+++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/GradientBrushBase{TPixel}.cs
@@ -0,0 +1,174 @@
+using System;
+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 GradientBrushBase : IBrush
+ where TPixel : struct, IPixel
+ {
+ ///
+ /// Defines how the colors are repeated beyond the interval [0..1]
+ /// The gradient colors.
+ protected GradientBrushBase(
+ 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.
+ ///
+ protected ColorStop[] ColorStops { get; }
+
+ ///
+ public abstract BrushApplicator CreateApplicator(
+ ImageFrame source,
+ RectangleF region,
+ GraphicsOptions options);
+
+ ///
+ /// Base class for gradient brush applicators
+ ///
+ protected abstract class GradientBrushApplicatorBase : BrushApplicator
+ {
+ 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.
+ /// Defines if and how the gradient should be repeated.
+ protected GradientBrushApplicatorBase(
+ ImageFrame target,
+ GraphicsOptions options,
+ ColorStop[] colorStops,
+ GradientRepetitionMode repetitionMode)
+ : base(target, options)
+ {
+ this.colorStops = colorStops; // TODO: requires colorStops to be sorted by position - should that be checked?
+ this.repetitionMode = repetitionMode;
+ }
+
+ ///
+ /// 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);
+
+ 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))
+ {
+ 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/GradientRepetitionMode.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/GradientRepetitionMode.cs
new file mode 100644
index 000000000..adbc26ed4
--- /dev/null
+++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/GradientRepetitionMode.cs
@@ -0,0 +1,34 @@
+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,
+ /// TODO For the cref="PolygonalGradientBrush" it's outside the polygon,
+ /// For and it's beyond 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
new file mode 100644
index 000000000..09f816dd9
--- /dev/null
+++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/LinearGradientBrush{TPixel}.cs
@@ -0,0 +1,152 @@
+using System;
+
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes
+{
+ ///
+ /// 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.
+ ///
+ /// The pixel format
+ public sealed class LinearGradientBrush : GradientBrushBase
+ where TPixel : struct, IPixel
+ {
+ private readonly Point p1;
+
+ private readonly Point p2;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Start point
+ /// End point
+ /// defines how colors are repeated.
+ ///
+ public LinearGradientBrush(
+ Point p1,
+ Point p2,
+ GradientRepetitionMode repetitionMode,
+ params ColorStop[] colorStops)
+ : base(repetitionMode, colorStops)
+ {
+ this.p1 = p1;
+ this.p2 = p2;
+ }
+
+ ///
+ public override BrushApplicator CreateApplicator(ImageFrame source, RectangleF region, GraphicsOptions options)
+ => new LinearGradientBrushApplicator(source, this.p1, this.p2, this.ColorStops, this.RepetitionMode, options);
+
+ ///
+ /// The linear gradient brush applicator.
+ ///
+ private sealed class LinearGradientBrushApplicator : GradientBrushApplicatorBase
+ {
+ private readonly Point start;
+
+ private readonly Point end;
+
+ ///
+ /// 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;
+
+ ///
+ /// the result of ^2 + ^2
+ ///
+ private readonly float alongsSquared;
+
+ ///
+ /// the length of the defined gradient (between source and end)
+ ///
+ private readonly float length;
+
+ ///
+ /// 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
+ /// defines how the gradient colors are repeated.
+ /// the graphics options
+ public LinearGradientBrushApplicator(
+ ImageFrame source,
+ Point start,
+ Point end,
+ ColorStop[] colorStops,
+ GradientRepetitionMode repetitionMode,
+ GraphicsOptions options)
+ : base(source, options, colorStops, repetitionMode)
+ {
+ this.start = start;
+ this.end = end;
+
+ // the along vector:
+ 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.alongsSquared = (this.alongX * this.alongX) + (this.alongY * this.alongY);
+ this.length = (float)Math.Sqrt(this.alongsSquared);
+ }
+
+ protected override float PositionOnGradient(int x, int y)
+ {
+ 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;
+ }
+ }
+
+ public override void Dispose()
+ {
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/RadialGradientBrush{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/RadialGradientBrush{TPixel}.cs
new file mode 100644
index 000000000..5c0d8051c
--- /dev/null
+++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/RadialGradientBrush{TPixel}.cs
@@ -0,0 +1,102 @@
+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 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.
+ /// the color stops as defined in base class.
+ public RadialGradientBrush(
+ Point center,
+ float radius,
+ GradientRepetitionMode repetitionMode,
+ params ColorStop[] colorStops)
+ : base(repetitionMode, 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,
+ this.RepetitionMode);
+
+ ///
+ private sealed class RadialGradientBrushApplicator : GradientBrushApplicatorBase
+ {
+ 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.
+ /// How the colors are repeated beyond the first gradient.
+ public RadialGradientBrushApplicator(
+ ImageFrame target,
+ GraphicsOptions options,
+ Point center,
+ float radius,
+ ColorStop[] colorStops,
+ GradientRepetitionMode repetitionMode)
+ : base(target, options, colorStops, repetitionMode)
+ {
+ 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/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.
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/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs b/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs
new file mode 100644
index 000000000..7c9fa2088
--- /dev/null
+++ b/tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs
@@ -0,0 +1,149 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+
+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
+{
+ 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(
+ TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ TPixel red = NamedColors.Red;
+
+ using (Image image = provider.GetImage())
+ {
+ var unicolorLinearGradientBrush =
+ new EllipticGradientBrush(
+ new SixLabors.Primitives.Point(0, 0),
+ new SixLabors.Primitives.Point(10, 0),
+ 1.0f,
+ 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]
+ [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 AxisParallelEllipsesWithDifferentRatio(
+ TestImageProvider provider,
+ float ratio)
+ where TPixel : struct, IPixel
+ {
+ TPixel yellow = NamedColors.Yellow;
+ TPixel red = NamedColors.Red;
+ TPixel black = NamedColors.Black;
+
+ provider.VerifyOperation(
+ TolerantComparer,
+ 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]
+ [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 RotatedEllipsesWithDifferentRatio(
+ TestImageProvider provider,
+ float ratio,
+ float rotationInDegree)
+ where TPixel: struct, IPixel
+ {
+ FormattableString variant = $"{ratio:F2}_AT_{rotationInDegree:00}deg";
+
+ provider.VerifyOperation(
+ TolerantComparer,
+ 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
new file mode 100644
index 000000000..9e7af1e57
--- /dev/null
+++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs
@@ -0,0 +1,351 @@
+// 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;
+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;
+ 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)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage())
+ {
+ 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));
+
+ image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false);
+
+ // no need for reference image in this test:
+ image.ComparePixelBufferTo(red);
+ }
+ }
+
+ [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(
+ TolerantComparer,
+ 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)
+ where TPixel : struct, IPixel
+ {
+ provider.VerifyOperation(
+ TolerantComparer,
+ 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, 0),
+ GradientRepetitionMode.None,
+ new ColorStop(0, red),
+ new ColorStop(1, yellow));
+
+ image.Mutate(x => x.Fill(unicolorLinearGradientBrush));
+ },
+ false,
+ false);
+ }
+
+ [Theory]
+ [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
+ {
+ provider.VerifyOperation(
+ TolerantComparer,
+ 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));
+ },
+ $"{repetitionMode}",
+ false,
+ false);
+ }
+
+ [Theory]
+ [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
+ {
+ 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, black), 1)
+ .Concat(
+ pattern
+ .SelectMany((f, index) => new[]
+ {
+ 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 ? black : white), 1))
+ .ToArray();
+
+ using (Image image = provider.GetImage())
+ {
+ var unicolorLinearGradientBrush =
+ new LinearGradientBrush(
+ new SixLabors.Primitives.Point(0, 0),
+ new SixLabors.Primitives.Point(image.Width, 0),
+ GradientRepetitionMode.None,
+ colorStops);
+
+ image.Mutate(x => x.Fill(unicolorLinearGradientBrush));
+
+ 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(
+ TolerantComparer,
+ provider,
+ variant,
+ appendPixelTypeToFileName: false,
+ appendSourceFileOrDescription: false);
+ }
+ }
+
+ [Theory]
+ [WithBlankImages(10, 500, PixelTypes.Rgba32)]
+ public void VerticalBrushReturnsUnicolorRows(
+ TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ 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(0, image.Height),
+ GradientRepetitionMode.None,
+ new ColorStop(0, red),
+ new ColorStop(1, yellow));
+
+ image.Mutate(x => x.Fill(unicolorLinearGradientBrush));
+
+ VerifyAllRowsAreUnicolor(image);
+ },
+ false,
+ false);
+
+ void VerifyAllRowsAreUnicolor(Image image)
+ {
+ for (int y = 0; y < image.Height; y++)
+ {
+ Span row = image.GetPixelRowSpan(y);
+ TPixel firstColorOfRow = row[0];
+ foreach (TPixel p in row)
+ {
+ Assert.Equal(firstColorOfRow, p);
+ }
+ }
+ }
+ }
+
+ public enum ImageCorner
+ {
+ TopLeft = 0,
+ TopRight = 1,
+ BottomLeft = 2,
+ BottomRight = 3
+ }
+
+ [Theory]
+ [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 (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.");
+
+ 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;
+
+ var unicolorLinearGradientBrush =
+ new LinearGradientBrush(
+ new SixLabors.Primitives.Point(startX, startY),
+ new SixLabors.Primitives.Point(endX, endY),
+ GradientRepetitionMode.None,
+ new ColorStop(0, red),
+ new ColorStop(1, yellow));
+
+ image.Mutate(x => x.Fill(unicolorLinearGradientBrush));
+ image.DebugSave(
+ provider,
+ startCorner,
+ appendPixelTypeToFileName: false,
+ appendSourceFileOrDescription: false);
+
+ int verticalSign = startY == 0 ? 1 : -1;
+ int horizontalSign = startX == 0 ? 1 : -1;
+
+ // 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++)
+ {
+ // 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++)
+ {
+ Assert.Equal(colorOnDiagonal, image[i + horizontalSign * offset, i + verticalSign * offset]);
+ }
+ }
+
+ image.CompareToReferenceOutput(
+ TolerantComparer,
+ provider,
+ startCorner,
+ appendPixelTypeToFileName: false,
+ appendSourceFileOrDescription: false);
+ }
+ }
+
+ [Theory]
+ [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})]
+ public void ArbitraryGradients(
+ TestImageProvider provider,
+ int startX, int startY,
+ int endX, int endY,
+ float[] stopPositions,
+ int[] stopColorCodes)
+ where TPixel : struct, IPixel
+ {
+ TPixel[] colors = {
+ NamedColors.Navy, NamedColors.LightGreen, NamedColors.Yellow,
+ NamedColors.Red
+ };
+
+ 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);
+ }
+
+ FormattableString variant = $"({startX},{startY})_TO_({endX},{endY})__[{coloringVariant}]";
+
+ 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));
+ },
+ variant,
+ false,
+ false);
+ }
+ }
+}
\ 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..eafbf3df1
--- /dev/null
+++ b/tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs
@@ -0,0 +1,76 @@
+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
+{
+ 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(
+ TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ 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));
+
+ 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]
+ [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 WithDifferentCentersReturnsImage(
+ TestImageProvider provider,
+ int centerX,
+ int centerY)
+ where TPixel : struct, IPixel
+ {
+ provider.VerifyOperation(
+ TolerantComparer,
+ 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));
+ },
+ $"center({centerX},{centerY})",
+ false,
+ false);
+ }
+ }
+}
\ No newline at end of file
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}");
}
///
diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs
index 34c93a7c1..b8c0489c8 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);
}
@@ -442,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
{
@@ -453,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
{
@@ -465,7 +479,7 @@ namespace SixLabors.ImageSharp.Tests
return imageFrame;
}
-
+
public static ImageFrame ComparePixelBufferTo(
this ImageFrame image,
Span expectedPixels)
@@ -522,22 +536,120 @@ namespace SixLabors.ImageSharp.Tests
return image;
}
+ ///
+ /// 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,
+ 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(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)
+ /// 2. Executing DebugSave()
+ /// 3. Executing CopareToReferenceOutput()
+ ///
+ 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,
- IImageEncoder encoder,
- ImageComparer customComparer = null,
- bool appendPixelTypeToFileName = true,
- string referenceImageExtension = null
- )
+ 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))
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