Skip to content

Commit d5f29cc

Browse files
Use ThreadLocal for all applicator allocations
1 parent 11ed8b1 commit d5f29cc

6 files changed

Lines changed: 315 additions & 130 deletions

File tree

src/ImageSharp.Drawing/Processing/EllipticGradientBrush.cs

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Licensed under the Apache License, Version 2.0.
33

44
using System;
5-
5+
using System.Numerics;
66
using SixLabors.ImageSharp.PixelFormats;
77

88
namespace SixLabors.ImageSharp.Drawing.Processing
@@ -126,16 +126,16 @@ public RadialGradientBrushApplicator(
126126
}
127127

128128
/// <inheritdoc />
129-
protected override float PositionOnGradient(float xt, float yt)
129+
protected override float PositionOnGradient(float x, float y)
130130
{
131-
float x0 = xt - this.center.X;
132-
float y0 = yt - this.center.Y;
131+
float x0 = x - this.center.X;
132+
float y0 = y - this.center.Y;
133133

134-
float x = (x0 * this.cosRotation) - (y0 * this.sinRotation);
135-
float y = (x0 * this.sinRotation) + (y0 * this.cosRotation);
134+
float xR = (x0 * this.cosRotation) - (y0 * this.sinRotation);
135+
float yR = (x0 * this.sinRotation) + (y0 * this.cosRotation);
136136

137-
float xSquared = x * x;
138-
float ySquared = y * y;
137+
float xSquared = xR * xR;
138+
float ySquared = yR * yR;
139139

140140
return (xSquared / this.referenceRadiusSquared) + (ySquared / this.secondRadiusSquared);
141141
}
@@ -147,18 +147,7 @@ private float AngleBetween(PointF junction, PointF a, PointF b)
147147
return MathF.Atan2(vB.Y, vB.X) - MathF.Atan2(vA.Y, vA.X);
148148
}
149149

150-
private float DistanceBetween(
151-
PointF p1,
152-
PointF p2)
153-
{
154-
// TODO: Can we not just use Vector2 distance here?
155-
float dX = p1.X - p2.X;
156-
float dXsquared = dX * dX;
157-
158-
float dY = p1.Y - p2.Y;
159-
float dYsquared = dY * dY;
160-
return MathF.Sqrt(dXsquared + dYsquared);
161-
}
150+
private float DistanceBetween(PointF p1, PointF p2) => Vector2.Distance(p1, p2);
162151
}
163152
}
164153
}

src/ImageSharp.Drawing/Processing/GradientBrush.cs

Lines changed: 73 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Buffers;
66
using System.Numerics;
7+
using System.Threading;
78
using SixLabors.ImageSharp.Memory;
89
using SixLabors.ImageSharp.PixelFormats;
910

@@ -54,6 +55,14 @@ internal abstract class GradientBrushApplicator<TPixel> : BrushApplicator<TPixel
5455

5556
private readonly GradientRepetitionMode repetitionMode;
5657

58+
private readonly MemoryAllocator allocator;
59+
60+
private readonly int scalineWidth;
61+
62+
private readonly ThreadLocal<ThreadContextData> threadContextData;
63+
64+
private bool isDisposed;
65+
5766
/// <summary>
5867
/// Initializes a new instance of the <see cref="GradientBrushApplicator{TPixel}"/> class.
5968
/// </summary>
@@ -74,6 +83,11 @@ protected GradientBrushApplicator(
7483
// Use Array.Sort with a custom comparer.
7584
this.colorStops = colorStops;
7685
this.repetitionMode = repetitionMode;
86+
this.scalineWidth = target.Width;
87+
this.allocator = configuration.MemoryAllocator;
88+
this.threadContextData = new ThreadLocal<ThreadContextData>(
89+
() => new ThreadContextData(this.allocator, this.scalineWidth),
90+
true);
7791
}
7892

7993
internal TPixel this[int x, int y]
@@ -126,38 +140,35 @@ protected GradientBrushApplicator(
126140
/// <inheritdoc />
127141
public override void Apply(Span<float> scanline, int x, int y)
128142
{
129-
MemoryAllocator memoryAllocator = this.Configuration.MemoryAllocator;
130-
using IMemoryOwner<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length);
131-
using IMemoryOwner<TPixel> overlay = memoryAllocator.Allocate<TPixel>(scanline.Length);
132-
133-
Span<float> amountSpan = amountBuffer.Memory.Span;
134-
Span<TPixel> overlaySpan = overlay.Memory.Span;
143+
ThreadContextData contextData = this.threadContextData.Value;
144+
Span<float> amounts = contextData.AmountSpan.Slice(0, scanline.Length);
145+
Span<TPixel> overlays = contextData.OverlaySpan.Slice(0, scanline.Length);
135146
float blendPercentage = this.Options.BlendPercentage;
136147

137148
// TODO: Remove bounds checks.
138149
if (blendPercentage < 1)
139150
{
140151
for (int i = 0; i < scanline.Length; i++)
141152
{
142-
amountSpan[i] = scanline[i] * blendPercentage;
143-
overlaySpan[i] = this[x + i, y];
153+
amounts[i] = scanline[i] * blendPercentage;
154+
overlays[i] = this[x + i, y];
144155
}
145156
}
146157
else
147158
{
148159
for (int i = 0; i < scanline.Length; i++)
149160
{
150-
amountSpan[i] = scanline[i];
151-
overlaySpan[i] = this[x + i, y];
161+
amounts[i] = scanline[i];
162+
overlays[i] = this[x + i, y];
152163
}
153164
}
154165

155166
Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
156-
this.Blender.Blend(this.Configuration, destinationRow, destinationRow, overlaySpan, amountSpan);
167+
this.Blender.Blend(this.Configuration, destinationRow, destinationRow, overlays, amounts);
157168
}
158169

159170
/// <summary>
160-
/// calculates the position on the gradient for a given point.
171+
/// Calculates the position on the gradient for a given point.
161172
/// This method is abstract as it's content depends on the shape of the gradient.
162173
/// </summary>
163174
/// <param name="x">The x-coordinate of the point.</param>
@@ -170,6 +181,29 @@ public override void Apply(Span<float> scanline, int x, int y)
170181
/// </returns>
171182
protected abstract float PositionOnGradient(float x, float y);
172183

184+
/// <inheritdoc/>
185+
protected override void Dispose(bool disposing)
186+
{
187+
if (this.isDisposed)
188+
{
189+
return;
190+
}
191+
192+
base.Dispose(disposing);
193+
194+
if (disposing)
195+
{
196+
foreach (ThreadContextData data in this.threadContextData.Values)
197+
{
198+
data.Dispose();
199+
}
200+
201+
this.threadContextData.Dispose();
202+
}
203+
204+
this.isDisposed = true;
205+
}
206+
173207
private (ColorStop from, ColorStop to) GetGradientSegment(float positionOnCompleteGradient)
174208
{
175209
ColorStop localGradientFrom = this.colorStops[0];
@@ -191,6 +225,33 @@ public override void Apply(Span<float> scanline, int x, int y)
191225

192226
return (localGradientFrom, localGradientTo);
193227
}
228+
229+
private sealed class ThreadContextData : IDisposable
230+
{
231+
private bool isDisposed;
232+
private readonly IMemoryOwner<float> amountBuffer;
233+
private readonly IMemoryOwner<TPixel> overlayBuffer;
234+
235+
public ThreadContextData(MemoryAllocator allocator, int scanlineLength)
236+
{
237+
this.amountBuffer = allocator.Allocate<float>(scanlineLength);
238+
this.overlayBuffer = allocator.Allocate<TPixel>(scanlineLength);
239+
}
240+
241+
public Span<float> AmountSpan => this.amountBuffer.Memory.Span;
242+
243+
public Span<TPixel> OverlaySpan => this.overlayBuffer.Memory.Span;
244+
245+
public void Dispose()
246+
{
247+
if (!this.isDisposed)
248+
{
249+
this.isDisposed = true;
250+
this.amountBuffer.Dispose();
251+
this.overlayBuffer.Dispose();
252+
}
253+
}
254+
}
194255
}
195256
}
196257
}

src/ImageSharp.Drawing/Processing/PathGradientBrush.cs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ public Vector4 ColorAt(float distance)
210210
/// The path gradient brush applicator.
211211
/// </summary>
212212
/// <typeparam name="TPixel">The pixel format.</typeparam>
213-
private class PathGradientBrushApplicator<TPixel> : BrushApplicator<TPixel>
213+
private sealed class PathGradientBrushApplicator<TPixel> : BrushApplicator<TPixel>
214214
where TPixel : unmanaged, IPixel<TPixel>
215215
{
216216
private readonly PointF center;
@@ -333,11 +333,11 @@ public PathGradientBrushApplicator(
333333
/// <inheritdoc />
334334
public override void Apply(Span<float> scanline, int x, int y)
335335
{
336-
ThreadContextData values = this.threadContextData.Value;
337-
Span<float> amounts = values.AmountSpan.Slice(0, scanline.Length);
338-
Span<TPixel> overlays = values.OverlaySpan.Slice(0, scanline.Length);
339-
Span<PointF> intersections = values.IntersectionsSpan;
340-
Span<PointOrientation> orientations = values.OrientationsSpan;
336+
ThreadContextData contextData = this.threadContextData.Value;
337+
Span<float> amounts = contextData.AmountSpan.Slice(0, scanline.Length);
338+
Span<TPixel> overlays = contextData.OverlaySpan.Slice(0, scanline.Length);
339+
Span<PointF> intersections = contextData.IntersectionsSpan;
340+
Span<PointOrientation> orientations = contextData.OrientationsSpan;
341341
float blendPercentage = this.Options.BlendPercentage;
342342

343343
// TODO: Remove bounds checks.
@@ -362,6 +362,7 @@ public override void Apply(Span<float> scanline, int x, int y)
362362
this.Blender.Blend(this.Configuration, destinationRow, destinationRow, overlays, amounts);
363363
}
364364

365+
/// <inheritdoc />
365366
protected override void Dispose(bool disposing)
366367
{
367368
if (this.isDisposed)
@@ -452,12 +453,12 @@ private sealed class ThreadContextData : IDisposable
452453
private readonly IMemoryOwner<PointF> intersectionsBuffer;
453454
private readonly IMemoryOwner<PointOrientation> orientationsBuffer;
454455

455-
public ThreadContextData(MemoryAllocator memoryAllocator, int scanlineLength, int maxIntersections)
456+
public ThreadContextData(MemoryAllocator allocator, int scanlineLength, int maxIntersections)
456457
{
457-
this.amountBuffer = memoryAllocator.Allocate<float>(scanlineLength);
458-
this.overlayBuffer = memoryAllocator.Allocate<TPixel>(scanlineLength);
459-
this.intersectionsBuffer = memoryAllocator.Allocate<PointF>(maxIntersections);
460-
this.orientationsBuffer = memoryAllocator.Allocate<PointOrientation>(maxIntersections);
458+
this.amountBuffer = allocator.Allocate<float>(scanlineLength);
459+
this.overlayBuffer = allocator.Allocate<TPixel>(scanlineLength);
460+
this.intersectionsBuffer = allocator.Allocate<PointF>(maxIntersections);
461+
this.orientationsBuffer = allocator.Allocate<PointOrientation>(maxIntersections);
461462
}
462463

463464
public Span<float> AmountSpan => this.amountBuffer.Memory.Span;

src/ImageSharp.Drawing/Processing/PatternBrush.cs

Lines changed: 79 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Buffers;
66
using System.Numerics;
7+
using System.Threading;
78
using SixLabors.ImageSharp.Drawing.Utilities;
89
using SixLabors.ImageSharp.Memory;
910
using SixLabors.ImageSharp.PixelFormats;
@@ -31,7 +32,7 @@ namespace SixLabors.ImageSharp.Drawing.Processing
3132
/// 0
3233
/// </para>
3334
/// </remarks>
34-
public class PatternBrush : IBrush
35+
public sealed class PatternBrush : IBrush
3536
{
3637
/// <summary>
3738
/// The pattern.
@@ -103,13 +104,15 @@ public BrushApplicator<TPixel> CreateApplicator<TPixel>(
103104
/// <summary>
104105
/// The pattern brush applicator.
105106
/// </summary>
106-
private class PatternBrushApplicator<TPixel> : BrushApplicator<TPixel>
107+
/// <typeparam name="TPixel">The pixel format.</typeparam>
108+
private sealed class PatternBrushApplicator<TPixel> : BrushApplicator<TPixel>
107109
where TPixel : unmanaged, IPixel<TPixel>
108110
{
109-
/// <summary>
110-
/// The pattern.
111-
/// </summary>
112111
private readonly DenseMatrix<TPixel> pattern;
112+
private readonly MemoryAllocator allocator;
113+
private readonly int scalineWidth;
114+
private readonly ThreadLocal<ThreadContextData> threadContextData;
115+
private bool isDisposed;
113116

114117
/// <summary>
115118
/// Initializes a new instance of the <see cref="PatternBrushApplicator{TPixel}" /> class.
@@ -123,7 +126,15 @@ public PatternBrushApplicator(
123126
GraphicsOptions options,
124127
ImageFrame<TPixel> source,
125128
in DenseMatrix<TPixel> pattern)
126-
: base(configuration, options, source) => this.pattern = pattern;
129+
: base(configuration, options, source)
130+
{
131+
this.pattern = pattern;
132+
this.scalineWidth = source.Width;
133+
this.allocator = configuration.MemoryAllocator;
134+
this.threadContextData = new ThreadLocal<ThreadContextData>(
135+
() => new ThreadContextData(this.allocator, this.scalineWidth),
136+
true);
137+
}
127138

128139
internal TPixel this[int x, int y]
129140
{
@@ -141,29 +152,74 @@ public PatternBrushApplicator(
141152
public override void Apply(Span<float> scanline, int x, int y)
142153
{
143154
int patternY = y % this.pattern.Rows;
144-
MemoryAllocator memoryAllocator = this.Configuration.MemoryAllocator;
155+
ThreadContextData contextData = this.threadContextData.Value;
156+
Span<float> amounts = contextData.AmountSpan.Slice(0, scanline.Length);
157+
Span<TPixel> overlays = contextData.OverlaySpan.Slice(0, scanline.Length);
145158

146-
using (IMemoryOwner<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length))
147-
using (IMemoryOwner<TPixel> overlay = memoryAllocator.Allocate<TPixel>(scanline.Length))
159+
for (int i = 0; i < scanline.Length; i++)
148160
{
149-
Span<float> amountSpan = amountBuffer.Memory.Span;
150-
Span<TPixel> overlaySpan = overlay.Memory.Span;
161+
amounts[i] = NumericUtilities.ClampFloat(scanline[i] * this.Options.BlendPercentage, 0, 1F);
151162

152-
for (int i = 0; i < scanline.Length; i++)
153-
{
154-
amountSpan[i] = NumericUtilities.ClampFloat(scanline[i] * this.Options.BlendPercentage, 0, 1F);
163+
int patternX = (x + i) % this.pattern.Columns;
164+
overlays[i] = this.pattern[patternY, patternX];
165+
}
166+
167+
Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
168+
this.Blender.Blend(
169+
this.Configuration,
170+
destinationRow,
171+
destinationRow,
172+
overlays,
173+
amounts);
174+
}
155175

156-
int patternX = (x + i) % this.pattern.Columns;
157-
overlaySpan[i] = this.pattern[patternY, patternX];
176+
/// <inheritdoc/>
177+
protected override void Dispose(bool disposing)
178+
{
179+
if (this.isDisposed)
180+
{
181+
return;
182+
}
183+
184+
base.Dispose(disposing);
185+
186+
if (disposing)
187+
{
188+
foreach (ThreadContextData data in this.threadContextData.Values)
189+
{
190+
data.Dispose();
158191
}
159192

160-
Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
161-
this.Blender.Blend(
162-
this.Configuration,
163-
destinationRow,
164-
destinationRow,
165-
overlaySpan,
166-
amountSpan);
193+
this.threadContextData.Dispose();
194+
}
195+
196+
this.isDisposed = true;
197+
}
198+
199+
private sealed class ThreadContextData : IDisposable
200+
{
201+
private bool isDisposed;
202+
private readonly IMemoryOwner<float> amountBuffer;
203+
private readonly IMemoryOwner<TPixel> overlayBuffer;
204+
205+
public ThreadContextData(MemoryAllocator allocator, int scanlineLength)
206+
{
207+
this.amountBuffer = allocator.Allocate<float>(scanlineLength);
208+
this.overlayBuffer = allocator.Allocate<TPixel>(scanlineLength);
209+
}
210+
211+
public Span<float> AmountSpan => this.amountBuffer.Memory.Span;
212+
213+
public Span<TPixel> OverlaySpan => this.overlayBuffer.Memory.Span;
214+
215+
public void Dispose()
216+
{
217+
if (!this.isDisposed)
218+
{
219+
this.isDisposed = true;
220+
this.amountBuffer.Dispose();
221+
this.overlayBuffer.Dispose();
222+
}
167223
}
168224
}
169225
}

0 commit comments

Comments
 (0)