Skip to content

Commit 6f7715e

Browse files
committed
SixLabors#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.
1 parent 1736bbb commit 6f7715e

File tree

4 files changed

+204
-131
lines changed

4 files changed

+204
-131
lines changed
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
using System.Numerics;
2+
3+
using SixLabors.ImageSharp.PixelFormats;
4+
using SixLabors.ImageSharp.PixelFormats.PixelBlenders;
5+
using SixLabors.Primitives;
6+
7+
namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes
8+
{
9+
/// <summary>
10+
/// Base class for Gradient brushes
11+
/// </summary>
12+
/// <typeparam name="TPixel">The pixel format</typeparam>
13+
public abstract class AbstractGradientBrush<TPixel> : IBrush<TPixel>
14+
where TPixel : struct, IPixel<TPixel>
15+
{
16+
/// <inheritdoc cref="IBrush{TPixel}"/>
17+
/// <param name="colorStops">The gradient colors.</param>
18+
protected AbstractGradientBrush(params ColorStop<TPixel>[] colorStops)
19+
{
20+
this.ColorStops = colorStops;
21+
}
22+
23+
/// <summary>
24+
/// Gets the list of color stops for this gradient.
25+
/// </summary>
26+
protected ColorStop<TPixel>[] ColorStops { get; }
27+
28+
/// <inheritdoc cref="IBrush{TPixel}" />
29+
public abstract BrushApplicator<TPixel> CreateApplicator(
30+
ImageFrame<TPixel> source,
31+
RectangleF region,
32+
GraphicsOptions options);
33+
34+
/// <summary>
35+
/// Base class for gradient brush applicators
36+
/// </summary>
37+
protected abstract class AbstractGradientBrushApplicator : BrushApplicator<TPixel>
38+
{
39+
private readonly ColorStop<TPixel>[] colorStops;
40+
41+
/// <summary>
42+
/// Initializes a new instance of the <see cref="AbstractGradientBrushApplicator"/> class.
43+
/// </summary>
44+
/// <param name="target">The target.</param>
45+
/// <param name="options">The options.</param>
46+
/// <param name="colorStops">an array of color stops sorted by their position.</param>
47+
/// <param name="region">TODO: use region, compare with other Brushes for reference</param>
48+
protected AbstractGradientBrushApplicator(
49+
ImageFrame<TPixel> target,
50+
GraphicsOptions options,
51+
ColorStop<TPixel>[] colorStops,
52+
RectangleF region)
53+
: base(target, options)
54+
{
55+
this.colorStops = colorStops; // TODO: requires colorStops to be sorted by position - should that be checked?
56+
}
57+
58+
/// <summary>
59+
/// Base implementation of the indexer for gradients
60+
/// (follows the facade pattern, using abstract methods)
61+
/// </summary>
62+
/// <param name="x">X coordinate of the Pixel.</param>
63+
/// <param name="y">Y coordinate of the Pixel.</param>
64+
internal override TPixel this[int x, int y]
65+
{
66+
get
67+
{
68+
float positionOnCompleteGradient = this.PositionOnGradient(x, y);
69+
var (from, to) = this.GetGradientSegment(positionOnCompleteGradient);
70+
71+
if (from.Color.Equals(to.Color))
72+
{
73+
return from.Color;
74+
}
75+
else
76+
{
77+
var fromAsVector = from.Color.ToVector4();
78+
var toAsVector = to.Color.ToVector4();
79+
float onLocalGradient = (positionOnCompleteGradient - from.Ratio) / to.Ratio;
80+
81+
// TODO: this should be changeble for different gradienting functions
82+
Vector4 result = PorterDuffFunctions.Normal(
83+
fromAsVector,
84+
toAsVector,
85+
onLocalGradient);
86+
87+
TPixel resultColor = default;
88+
resultColor.PackFromVector4(result);
89+
return resultColor;
90+
}
91+
}
92+
}
93+
94+
/// <summary>
95+
/// calculates the position on the gradient for a given pixel.
96+
/// This method is abstract as it's content depends on the shape of the gradient.
97+
/// </summary>
98+
/// <param name="x">The x coordinate of the pixel</param>
99+
/// <param name="y">The y coordinate of the pixel</param>
100+
/// <returns>
101+
/// The position the given pixel has on the gradient.
102+
/// The position is not bound to the [0..1] interval.
103+
/// Values outside of that interval may be treated differently,
104+
/// e.g. for the <see cref="GradientRepetitionMode" /> enum.
105+
/// </returns>
106+
protected abstract float PositionOnGradient(int x, int y);
107+
108+
private (ColorStop<TPixel> from, ColorStop<TPixel> to) GetGradientSegment(
109+
float positionOnCompleteGradient)
110+
{
111+
var localGradientFrom = this.colorStops[0];
112+
ColorStop<TPixel> localGradientTo = default;
113+
114+
// TODO: ensure colorStops has at least 2 items (technically 1 would be okay, but that's no gradient)
115+
foreach (var colorStop in this.colorStops)
116+
{
117+
localGradientTo = colorStop;
118+
119+
if (colorStop.Ratio > positionOnCompleteGradient)
120+
{
121+
// we're done here, so break it!
122+
break;
123+
}
124+
125+
localGradientFrom = localGradientTo;
126+
}
127+
128+
return (localGradientFrom, localGradientTo);
129+
}
130+
}
131+
}
132+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System.Diagnostics;
2+
3+
using SixLabors.ImageSharp.PixelFormats;
4+
5+
namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes
6+
{
7+
/// <summary>
8+
/// A struct that defines a single color stop.
9+
/// </summary>
10+
/// <typeparam name="TPixel">The pixel format.</typeparam>
11+
[DebuggerDisplay("ColorStop({Ratio} -> {Color}")]
12+
public struct ColorStop<TPixel>
13+
where TPixel : struct, IPixel<TPixel>
14+
{
15+
/// <summary>
16+
/// Initializes a new instance of the <see cref="ColorStop{TPixel}" /> struct.
17+
/// </summary>
18+
/// <param name="ratio">Where should it be? 0 is at the start, 1 at the end of the Gradient.</param>
19+
/// <param name="color">What color should be used at that point?</param>
20+
public ColorStop(float ratio, TPixel color)
21+
{
22+
this.Ratio = ratio;
23+
this.Color = color;
24+
}
25+
26+
/// <summary>
27+
/// Gets the point along the defined gradient axis.
28+
/// </summary>
29+
public float Ratio { get; }
30+
31+
/// <summary>
32+
/// Gets the color to be used.
33+
/// </summary>
34+
public TPixel Color { get; }
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,49 @@
11
using System;
2-
using System.Diagnostics;
3-
using System.Numerics;
42

53
using SixLabors.ImageSharp.PixelFormats;
6-
using SixLabors.ImageSharp.PixelFormats.PixelBlenders;
74
using SixLabors.Primitives;
85

9-
namespace SixLabors.ImageSharp.Processing.Drawing.Brushes
6+
namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes
107
{
118
/// <summary>
12-
/// Provides an implementation of a brush for painting gradients within areas.
9+
/// Provides an implementation of a brush for painting linear gradients within areas.
1310
/// Supported right now:
1411
/// - a set of colors in relative distances to each other.
15-
/// - two points to gradient along.
1612
/// </summary>
1713
/// <typeparam name="TPixel">The pixel format</typeparam>
18-
public class LinearGradientBrush<TPixel> : IBrush<TPixel>
14+
public class LinearGradientBrush<TPixel> : AbstractGradientBrush<TPixel>
1915
where TPixel : struct, IPixel<TPixel>
2016
{
2117
private readonly Point p1;
2218

2319
private readonly Point p2;
2420

25-
private readonly ColorStop[] colorStops;
26-
2721
/// <summary>
2822
/// Initializes a new instance of the <see cref="LinearGradientBrush{TPixel}"/> class.
2923
/// </summary>
3024
/// <param name="p1">Start point</param>
3125
/// <param name="p2">End point</param>
32-
/// <param name="colorStops">
33-
/// A set of color keys and where they are.
34-
/// The double should be in range [0..1] and is relative between p1 and p2.
35-
/// 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.
36-
/// </param>
37-
public LinearGradientBrush(Point p1, Point p2, params ColorStop[] colorStops)
26+
/// <param name="colorStops"><inheritdoc /></param>
27+
public LinearGradientBrush(Point p1, Point p2, params ColorStop<TPixel>[] colorStops)
28+
: base(colorStops)
3829
{
3930
this.p1 = p1;
4031
this.p2 = p2;
41-
this.colorStops = colorStops;
4232
}
4333

4434
/// <inheritdoc />
45-
public BrushApplicator<TPixel> CreateApplicator(ImageFrame<TPixel> source, RectangleF region, GraphicsOptions options)
46-
=> new LinearGradientBrushApplicator(source, this.p1, this.p2, this.colorStops, region, options);
47-
48-
/// <summary>
49-
/// A struct that defines a single color stop.
50-
/// </summary>
51-
[DebuggerDisplay("ColorStop({Ratio} -> {Color}")]
52-
public struct ColorStop
53-
{
54-
/// <summary>
55-
/// Initializes a new instance of the <see cref="ColorStop" /> struct.
56-
/// </summary>
57-
/// <param name="ratio">Where should it be? 0 is at the start, 1 at the end of the <see cref="LinearGradientBrush{TPixel}"/>.</param>
58-
/// <param name="color">What color should be used at that point?</param>
59-
public ColorStop(float ratio, TPixel color)
60-
{
61-
this.Ratio = ratio;
62-
this.Color = color;
63-
}
64-
65-
/// <summary>
66-
/// Gets the point along the defined <see cref="LinearGradientBrush{TPixel}" /> gradient axis.
67-
/// </summary>
68-
public float Ratio { get; }
69-
70-
/// <summary>
71-
/// Gets the color to be used.
72-
/// </summary>
73-
public TPixel Color { get; }
74-
}
35+
public override BrushApplicator<TPixel> CreateApplicator(ImageFrame<TPixel> source, RectangleF region, GraphicsOptions options)
36+
=> new LinearGradientBrushApplicator(source, this.p1, this.p2, this.ColorStops, region, options);
7537

7638
/// <summary>
7739
/// The linear gradient brush applicator.
7840
/// </summary>
79-
private class LinearGradientBrushApplicator : BrushApplicator<TPixel>
41+
private class LinearGradientBrushApplicator : AbstractGradientBrushApplicator
8042
{
8143
private readonly Point start;
8244

8345
private readonly Point end;
8446

85-
private readonly ColorStop[] colorStops;
86-
8747
/// <summary>
8848
/// the vector along the gradient, x component
8949
/// </summary>
@@ -127,14 +87,13 @@ public LinearGradientBrushApplicator(
12787
ImageFrame<TPixel> source,
12888
Point start,
12989
Point end,
130-
ColorStop[] colorStops,
131-
RectangleF region, // TODO: use region, compare with other Brushes for reference.
90+
ColorStop<TPixel>[] colorStops,
91+
RectangleF region,
13292
GraphicsOptions options)
133-
: base(source, options)
93+
: base(source, options, colorStops, region)
13494
{
13595
this.start = start;
13696
this.end = end;
137-
this.colorStops = colorStops; // TODO: requires colorStops to be sorted by Item1!
13897

13998
// the along vector:
14099
this.alongX = this.end.X - this.start.X;
@@ -149,61 +108,7 @@ public LinearGradientBrushApplicator(
149108
this.length = (float)Math.Sqrt(this.alongsSquared);
150109
}
151110

152-
/// <summary>
153-
/// Gets the color for a single pixel
154-
/// </summary>
155-
/// <param name="x">The x coordinate.</param>
156-
/// <param name="y">The y coordinate.</param>
157-
internal override TPixel this[int x, int y]
158-
{
159-
get
160-
{
161-
// the following formula is the result of the linear equation system that forms the vector.
162-
// TODO: this formula should be abstracted as it's the only difference between linear and radial gradient!
163-
float onCompleteGradient = this.RatioOnGradient(x, y);
164-
165-
var localGradientFrom = this.colorStops[0];
166-
ColorStop localGradientTo = default;
167-
168-
// TODO: ensure colorStops has at least 2 items (technically 1 would be okay, but that's no gradient)
169-
foreach (var colorStop in this.colorStops)
170-
{
171-
localGradientTo = colorStop;
172-
173-
if (colorStop.Ratio > onCompleteGradient)
174-
{
175-
// we're done here, so break it!
176-
break;
177-
}
178-
179-
localGradientFrom = localGradientTo;
180-
}
181-
182-
TPixel resultColor = default;
183-
if (localGradientFrom.Color.Equals(localGradientTo.Color))
184-
{
185-
resultColor = localGradientFrom.Color;
186-
}
187-
else
188-
{
189-
var fromAsVector = localGradientFrom.Color.ToVector4();
190-
var toAsVector = localGradientTo.Color.ToVector4();
191-
float onLocalGradient = (onCompleteGradient - localGradientFrom.Ratio) / localGradientTo.Ratio; // TODO:
192-
193-
Vector4 result = PorterDuffFunctions.Normal(
194-
fromAsVector,
195-
toAsVector,
196-
onLocalGradient);
197-
198-
// TODO: when resultColor is a struct, what does PackFromVector4 do here?
199-
resultColor.PackFromVector4(result);
200-
}
201-
202-
return resultColor;
203-
}
204-
}
205-
206-
private float RatioOnGradient(int x, int y)
111+
protected override float PositionOnGradient(int x, int y)
207112
{
208113
if (this.acrossX == 0)
209114
{
@@ -234,6 +139,10 @@ private float RatioOnGradient(int x, int y)
234139
}
235140
}
236141

142+
public override void Dispose()
143+
{
144+
}
145+
237146
internal override void Apply(Span<float> scanline, int x, int y)
238147
{
239148
base.Apply(scanline, x, y);
@@ -257,11 +166,6 @@ internal override void Apply(Span<float> scanline, int x, int y)
257166
// this.Blender.Blend(memoryManager, destinationRow, destinationRow, this.Colors.Span, amountSpan);
258167
// }
259168
}
260-
261-
/// <inheritdoc />
262-
public override void Dispose()
263-
{
264-
}
265169
}
266170
}
267171
}

0 commit comments

Comments
 (0)