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]]);
}