Skip to content

Commit 2d8cf99

Browse files
Remove per pixel allocation in PathGradientBrushApplicator
1 parent 00b3414 commit 2d8cf99

16 files changed

Lines changed: 321 additions & 383 deletions

src/ImageSharp.Drawing/Processing/GradientBrush.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ public override void Apply(Span<float> scanline, int x, int y)
129129
MemoryAllocator memoryAllocator = this.Configuration.MemoryAllocator;
130130
using IMemoryOwner<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length);
131131
using IMemoryOwner<TPixel> overlay = memoryAllocator.Allocate<TPixel>(scanline.Length);
132+
132133
Span<float> amountSpan = amountBuffer.Memory.Span;
133134
Span<TPixel> overlaySpan = overlay.Memory.Span;
134135
float blendPercentage = this.Options.BlendPercentage;

src/ImageSharp.Drawing/Processing/PathGradientBrush.cs

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,17 @@ public Edge(Path path, Color startColor, Color endColor)
160160

161161
public Vector4 EndColor { get; }
162162

163-
public Intersection? FindIntersection(PointF start, PointF end, Span<PointF> intersections)
163+
public Intersection? FindIntersection(
164+
PointF start,
165+
PointF end,
166+
Span<PointF> intersections,
167+
Span<PointOrientation> orientations)
164168
{
165-
int pathIntersections = this.Path.FindIntersections(start, end, intersections.Slice(0, this.Path.MaxIntersections));
169+
int pathIntersections = this.Path.FindIntersections(
170+
start,
171+
end,
172+
intersections.Slice(0, this.Path.MaxIntersections),
173+
orientations.Slice(0, this.Path.MaxIntersections));
166174

167175
if (pathIntersections == 0)
168176
{
@@ -211,14 +219,14 @@ private class PathGradientBrushApplicator<TPixel> : BrushApplicator<TPixel>
211219

212220
private readonly float maxDistance;
213221

222+
private readonly int maxIntersections;
223+
214224
private readonly IList<Edge> edges;
215225

216226
private readonly TPixel centerPixel;
217227

218228
private readonly TPixel transparentPixel;
219229

220-
private readonly int maxIntersections;
221-
222230
/// <summary>
223231
/// Initializes a new instance of the <see cref="PathGradientBrushApplicator{TPixel}"/> class.
224232
/// </summary>
@@ -249,7 +257,7 @@ public PathGradientBrushApplicator(
249257
this.transparentPixel = Color.Transparent.ToPixel<TPixel>();
250258
}
251259

252-
internal TPixel this[int x, int y, Span<PointF> intersections]
260+
internal TPixel this[int x, int y, Span<PointF> intersections, Span<PointOrientation> orientations]
253261
{
254262
get
255263
{
@@ -285,7 +293,7 @@ public PathGradientBrushApplicator(
285293
var direction = Vector2.Normalize(point - this.center);
286294
PointF end = point + (PointF)(direction * this.maxDistance);
287295

288-
(Edge edge, Intersection? info) = this.FindIntersection(point, end, intersections);
296+
(Edge edge, Intersection? info) = this.FindIntersection(point, end, intersections, orientations);
289297

290298
if (!info.HasValue)
291299
{
@@ -311,12 +319,14 @@ public override void Apply(Span<float> scanline, int x, int y)
311319
{
312320
MemoryAllocator memoryAllocator = this.Configuration.MemoryAllocator;
313321
using IMemoryOwner<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length);
314-
using IMemoryOwner<TPixel> overlay = memoryAllocator.Allocate<TPixel>(scanline.Length);
322+
using IMemoryOwner<TPixel> overlayBuffer = memoryAllocator.Allocate<TPixel>(scanline.Length);
315323
using IMemoryOwner<PointF> intersectionsBuffer = memoryAllocator.Allocate<PointF>(this.maxIntersections);
324+
using IMemoryOwner<PointOrientation> orientationsBuffer = memoryAllocator.Allocate<PointOrientation>(this.maxIntersections);
316325

317326
Span<float> amountSpan = amountBuffer.Memory.Span;
318-
Span<TPixel> overlaySpan = overlay.Memory.Span;
327+
Span<TPixel> overlaySpan = overlayBuffer.Memory.Span;
319328
Span<PointF> intersectionsSpan = intersectionsBuffer.Memory.Span;
329+
Span<PointOrientation> orientationsSpan = orientationsBuffer.Memory.Span;
320330
float blendPercentage = this.Options.BlendPercentage;
321331

322332
// TODO: Remove bounds checks.
@@ -325,29 +335,33 @@ public override void Apply(Span<float> scanline, int x, int y)
325335
for (int i = 0; i < scanline.Length; i++)
326336
{
327337
amountSpan[i] = scanline[i] * blendPercentage;
328-
overlaySpan[i] = this[x + i, y, intersectionsSpan];
338+
overlaySpan[i] = this[x + i, y, intersectionsSpan, orientationsSpan];
329339
}
330340
}
331341
else
332342
{
333343
for (int i = 0; i < scanline.Length; i++)
334344
{
335345
amountSpan[i] = scanline[i];
336-
overlaySpan[i] = this[x + i, y, intersectionsSpan];
346+
overlaySpan[i] = this[x + i, y, intersectionsSpan, orientationsSpan];
337347
}
338348
}
339349

340350
Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
341351
this.Blender.Blend(this.Configuration, destinationRow, destinationRow, overlaySpan, amountSpan);
342352
}
343353

344-
private (Edge edge, Intersection? info) FindIntersection(PointF start, PointF end, Span<PointF> intersections)
354+
private (Edge edge, Intersection? info) FindIntersection(
355+
PointF start,
356+
PointF end,
357+
Span<PointF> intersections,
358+
Span<PointOrientation> orientations)
345359
{
346360
(Edge edge, Intersection? info) closest = default;
347361

348362
foreach (Edge edge in this.edges)
349363
{
350-
Intersection? intersection = edge.FindIntersection(start, end, intersections);
364+
Intersection? intersection = edge.FindIntersection(start, end, intersections, orientations);
351365

352366
if (!intersection.HasValue)
353367
{

src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs

Lines changed: 28 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ public ComplexPolygon(params IPath[] paths)
120120
public int MaxIntersections { get; }
121121

122122
/// <summary>
123-
/// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds
123+
/// The distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds
124124
/// </summary>
125125
/// <param name="point">The point.</param>
126126
/// <returns>
@@ -162,81 +162,43 @@ public PointInfo Distance(PointF point)
162162
return pointInfo;
163163
}
164164

165-
/// <summary>
166-
/// Based on a line described by <paramref name="start"/> and <paramref name="end"/>
167-
/// populate a buffer for all points on all the polygons, that make up this complex shape,
168-
/// that the line intersects.
169-
/// </summary>
170-
/// <param name="start">The start point of the line.</param>
171-
/// <param name="end">The end point of the line.</param>
172-
/// <param name="buffer">The buffer that will be populated with intersections.</param>
173-
/// <param name="offset">The offset within the buffer</param>
174-
/// <returns>
175-
/// The number of intersections populated into the buffer.
176-
/// </returns>
177-
public int FindIntersections(PointF start, PointF end, PointF[] buffer, int offset)
178-
=> this.FindIntersections(start, end, buffer, offset, IntersectionRule.OddEven);
179-
180165
/// <inheritdoc />
181-
public int FindIntersections(PointF start, PointF end, Span<PointF> buffer)
182-
=> this.FindIntersections(start, end, buffer, IntersectionRule.OddEven);
183-
184-
/// <summary>
185-
/// Based on a line described by <paramref name="start"/> and <paramref name="end"/>
186-
/// populate a buffer for all points on all the polygons, that make up this complex shape,
187-
/// that the line intersects.
188-
/// </summary>
189-
/// <param name="start">The start point of the line.</param>
190-
/// <param name="end">The end point of the line.</param>
191-
/// <param name="buffer">The buffer that will be populated with intersections.</param>
192-
/// <param name="offset">The offset within the buffer</param>
193-
/// <param name="intersectionRule">The intersection rule to use</param>
194-
/// <returns>
195-
/// The number of intersections populated into the buffer.
196-
/// </returns>
197-
public int FindIntersections(PointF start, PointF end, PointF[] buffer, int offset, IntersectionRule intersectionRule)
198-
{
199-
Span<PointF> subBuffer = buffer.AsSpan(offset);
200-
return this.FindIntersections(start, end, subBuffer, intersectionRule);
201-
}
166+
public int FindIntersections(PointF start, PointF end, Span<PointF> intersections, Span<PointOrientation> orientations)
167+
=> this.FindIntersections(start, end, intersections, orientations, IntersectionRule.OddEven);
202168

203169
/// <inheritdoc />
204-
public int FindIntersections(PointF start, PointF end, Span<PointF> buffer, IntersectionRule intersectionRule)
170+
public int FindIntersections(
171+
PointF start,
172+
PointF end,
173+
Span<PointF> intersections,
174+
Span<PointOrientation> orientations,
175+
IntersectionRule intersectionRule)
205176
{
206177
this.EnsureInternalPathsInitalized();
207178

208179
int totalAdded = 0;
209-
InternalPath.PointOrientation[] orientations = ArrayPool<InternalPath.PointOrientation>.Shared.Rent(buffer.Length); // the largest number of intersections of any sub path of the set is the max size with need for this buffer.
210-
Span<InternalPath.PointOrientation> orientationsSpan = orientations;
211-
try
180+
foreach (InternalPath ip in this.internalPaths)
212181
{
213-
foreach (var ip in this.internalPaths)
214-
{
215-
Span<PointF> subBuffer = buffer.Slice(totalAdded);
216-
Span<InternalPath.PointOrientation> subOrientationsSpan = orientationsSpan.Slice(totalAdded);
182+
Span<PointF> subBuffer = intersections.Slice(totalAdded);
183+
Span<PointOrientation> subOrientationsSpan = orientations.Slice(totalAdded);
217184

218-
var position = ip.FindIntersectionsWithOrientation(start, end, subBuffer, subOrientationsSpan);
219-
totalAdded += position;
220-
}
185+
int position = ip.FindIntersectionsWithOrientation(start, end, subBuffer, subOrientationsSpan);
186+
totalAdded += position;
187+
}
221188

222-
Span<float> distances = stackalloc float[totalAdded];
223-
for (int i = 0; i < totalAdded; i++)
224-
{
225-
distances[i] = Vector2.DistanceSquared(start, buffer[i]);
226-
}
189+
Span<float> distances = stackalloc float[totalAdded];
190+
for (int i = 0; i < totalAdded; i++)
191+
{
192+
distances[i] = Vector2.DistanceSquared(start, intersections[i]);
193+
}
227194

228-
var activeBuffer = buffer.Slice(0, totalAdded);
229-
var activeOrientationsSpan = orientationsSpan.Slice(0, totalAdded);
230-
SortUtility.Sort(distances, activeBuffer, activeOrientationsSpan);
195+
Span<PointF> activeIntersections = intersections.Slice(0, totalAdded);
196+
Span<PointOrientation> activeOrientations = orientations.Slice(0, totalAdded);
197+
SortUtility.Sort(distances, activeIntersections, activeOrientations);
231198

232-
if (intersectionRule == IntersectionRule.Nonzero)
233-
{
234-
totalAdded = InternalPath.ApplyNonZeroIntersectionRules(activeBuffer, activeOrientationsSpan);
235-
}
236-
}
237-
finally
199+
if (intersectionRule == IntersectionRule.Nonzero)
238200
{
239-
ArrayPool<InternalPath.PointOrientation>.Shared.Return(orientations);
201+
totalAdded = InternalPath.ApplyNonZeroIntersectionRules(activeIntersections, activeOrientations);
240202
}
241203

242204
return totalAdded;
@@ -252,9 +214,9 @@ private void EnsureInternalPathsInitalized()
252214
{
253215
this.internalPaths = new List<InternalPath>(this.paths.Length);
254216

255-
foreach (var p in this.paths)
217+
foreach (IPath p in this.paths)
256218
{
257-
foreach (var s in p.Flatten())
219+
foreach (ISimplePath s in p.Flatten())
258220
{
259221
var ip = new InternalPath(s.Points, s.IsClosed);
260222
this.internalPaths.Add(ip);
@@ -361,7 +323,7 @@ public IPath AsClosedPath()
361323
/// </returns>
362324
public SegmentInfo PointAlongPath(float distanceAlongPath)
363325
{
364-
distanceAlongPath = distanceAlongPath % this.Length;
326+
distanceAlongPath %= this.Length;
365327

366328
foreach (IPath p in this.Paths)
367329
{

src/ImageSharp.Drawing/Shapes/EllipsePolygon.cs

Lines changed: 17 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
namespace SixLabors.ImageSharp.Drawing
99
{
1010
/// <summary>
11-
/// A shape made up of a single path made up of one of more <see cref="ILineSegment"/>s
11+
/// An elliptical shape made up of a single path made up of one of more <see cref="ILineSegment"/>s.
1212
/// </summary>
1313
public class EllipsePolygon : IPath, ISimplePath, IInternalPathOwner
1414
{
@@ -110,12 +110,9 @@ public PointInfo Distance(PointF point)
110110
/// <returns>
111111
/// A new path with the matrix applied to it.
112112
/// </returns>
113-
public EllipsePolygon Transform(Matrix3x2 matrix)
114-
{
115-
return matrix.IsIdentity
113+
public EllipsePolygon Transform(Matrix3x2 matrix) => matrix.IsIdentity
116114
? this
117115
: new EllipsePolygon(this.segment.Transform(matrix));
118-
}
119116

120117
/// <summary>
121118
/// Transforms the path using the specified matrix.
@@ -133,7 +130,7 @@ public EllipsePolygon Transform(Matrix3x2 matrix)
133130
IPath IPath.AsClosedPath() => this;
134131

135132
/// <summary>
136-
/// Converts the <see cref="IPath" /> into a simple linear path..
133+
/// Converts the <see cref="IPath" /> into a simple linear path.
137134
/// </summary>
138135
/// <returns>
139136
/// Returns the current <see cref="IPath" /> as simple linear path.
@@ -144,30 +141,17 @@ public IEnumerable<ISimplePath> Flatten()
144141
}
145142

146143
/// <inheritdoc/>
147-
int IPath.FindIntersections(PointF start, PointF end, PointF[] buffer, int offset, IntersectionRule intersectionRule)
148-
{
149-
Span<PointF> subBuffer = buffer.AsSpan(offset);
150-
return this.innerPath.FindIntersections(start, end, subBuffer, intersectionRule);
151-
}
152-
153-
/// <inheritdoc/>
154-
int IPath.FindIntersections(PointF start, PointF end, Span<PointF> buffer, IntersectionRule intersectionRule)
155-
{
156-
return this.innerPath.FindIntersections(start, end, buffer, intersectionRule);
157-
}
144+
int IPath.FindIntersections(PointF start, PointF end, Span<PointF> intersections, Span<PointOrientation> orientations)
145+
=> this.innerPath.FindIntersections(start, end, intersections, orientations, IntersectionRule.OddEven);
158146

159147
/// <inheritdoc/>
160-
int IPath.FindIntersections(PointF start, PointF end, PointF[] buffer, int offset)
161-
{
162-
Span<PointF> subBuffer = buffer.AsSpan(offset);
163-
return this.innerPath.FindIntersections(start, end, subBuffer, IntersectionRule.OddEven);
164-
}
165-
166-
/// <inheritdoc/>
167-
int IPath.FindIntersections(PointF start, PointF end, Span<PointF> buffer)
168-
{
169-
return this.innerPath.FindIntersections(start, end, buffer, IntersectionRule.OddEven);
170-
}
148+
int IPath.FindIntersections(
149+
PointF start,
150+
PointF end,
151+
Span<PointF> intersections,
152+
Span<PointOrientation> orientations,
153+
IntersectionRule intersectionRule)
154+
=> this.innerPath.FindIntersections(start, end, intersections, orientations, intersectionRule);
171155

172156
/// <summary>
173157
/// Determines whether the <see cref="IPath" /> contains the specified point
@@ -176,17 +160,13 @@ int IPath.FindIntersections(PointF start, PointF end, Span<PointF> buffer)
176160
/// <returns>
177161
/// <c>true</c> if the <see cref="IPath" /> contains the specified point; otherwise, <c>false</c>.
178162
/// </returns>
179-
public bool Contains(PointF point)
180-
{
181-
return this.innerPath.PointInPolygon(point);
182-
}
163+
public bool Contains(PointF point) => this.innerPath.PointInPolygon(point);
183164

184165
/// <inheritdoc />
185-
public SegmentInfo PointAlongPath(float distanceAlongPath)
186-
{
187-
// TODO switch this out to a calculated algorithum
188-
return this.innerPath.PointAlongPath(distanceAlongPath);
189-
}
166+
public SegmentInfo PointAlongPath(float distanceAlongPath) =>
167+
168+
// TODO switch this out to a calculated algorithm
169+
this.innerPath.PointAlongPath(distanceAlongPath);
190170

191171
private static CubicBezierLineSegment CreateSegment(Vector2 location, SizeF size)
192172
{

0 commit comments

Comments
 (0)