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 0000000000..bfbeded698
--- /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 0000000000..3d613adc05
--- /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]);
+ }
+ }
+ }
+ }
+}