Skip to content

Commit 758b7b4

Browse files
Ensure colorstop order in applicator.
1 parent 060d03e commit 758b7b4

File tree

1 file changed

+33
-7
lines changed

1 file changed

+33
-7
lines changed

src/ImageSharp.Drawing/Processing/GradientBrush.cs

+33-7
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ internal abstract class GradientBrushApplicator<TPixel> : BrushApplicator<TPixel
6262

6363
private readonly MemoryAllocator allocator;
6464

65-
private readonly int scalineWidth;
65+
private readonly int scanlineWidth;
6666

6767
private readonly ThreadLocalBlenderBuffers<TPixel> blenderBuffers;
6868

@@ -84,13 +84,14 @@ protected GradientBrushApplicator(
8484
GradientRepetitionMode repetitionMode)
8585
: base(configuration, options, target)
8686
{
87-
// TODO: requires colorStops to be sorted by position.
88-
// Use Array.Sort with a custom comparer.
8987
this.colorStops = colorStops;
88+
89+
// Ensure the color-stop order is correct.
90+
InsertionSort(this.colorStops, (x, y) => x.Ratio.CompareTo(y.Ratio));
9091
this.repetitionMode = repetitionMode;
91-
this.scalineWidth = target.Width;
92+
this.scanlineWidth = target.Width;
9293
this.allocator = configuration.MemoryAllocator;
93-
this.blenderBuffers = new ThreadLocalBlenderBuffers<TPixel>(this.allocator, this.scalineWidth);
94+
this.blenderBuffers = new ThreadLocalBlenderBuffers<TPixel>(this.allocator, this.scanlineWidth);
9495
}
9596

9697
internal TPixel this[int x, int y]
@@ -135,15 +136,16 @@ protected GradientBrushApplicator(
135136

136137
float onLocalGradient = (positionOnCompleteGradient - from.Ratio) / (to.Ratio - from.Ratio);
137138

139+
// TODO: This should use premultiplied vectors to avoid bad blends e.g. red -> brown <- green.
138140
return new Color(Vector4.Lerp((Vector4)from.Color, (Vector4)to.Color, onLocalGradient)).ToPixel<TPixel>();
139141
}
140142
}
141143

142144
/// <inheritdoc />
143145
public override void Apply(Span<float> scanline, int x, int y)
144146
{
145-
Span<float> amounts = this.blenderBuffers.AmountSpan.Slice(0, scanline.Length);
146-
Span<TPixel> overlays = this.blenderBuffers.OverlaySpan.Slice(0, scanline.Length);
147+
Span<float> amounts = this.blenderBuffers.AmountSpan[..scanline.Length];
148+
Span<TPixel> overlays = this.blenderBuffers.OverlaySpan[..scanline.Length];
147149
float blendPercentage = this.Options.BlendPercentage;
148150

149151
// TODO: Remove bounds checks.
@@ -221,5 +223,29 @@ protected override void Dispose(bool disposing)
221223

222224
return (localGradientFrom, localGradientTo);
223225
}
226+
227+
/// <summary>
228+
/// Provides a stable sorting algorithm for the given array.
229+
/// <see cref="Array.Sort(Array, System.Collections.IComparer?)"/> is not stable.
230+
/// </summary>
231+
/// <typeparam name="T">The type of element to sort.</typeparam>
232+
/// <param name="collection">The array to sort.</param>
233+
/// <param name="comparison">The comparison delegate.</param>
234+
private static void InsertionSort<T>(T[] collection, Comparison<T> comparison)
235+
{
236+
int count = collection.Length;
237+
for (int j = 1; j < count; j++)
238+
{
239+
T key = collection[j];
240+
241+
int i = j - 1;
242+
for (; i >= 0 && comparison(collection[i], key) > 0; i--)
243+
{
244+
collection[i + 1] = collection[i];
245+
}
246+
247+
collection[i + 1] = key;
248+
}
249+
}
224250
}
225251
}

0 commit comments

Comments
 (0)