Skip to content

Commit fa58c22

Browse files
Make PointAlongPath internal
1 parent 0b42626 commit fa58c22

9 files changed

Lines changed: 178 additions & 180 deletions

File tree

src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,17 @@ int IPathInternals.FindIntersections(
190190
totalAdded += position;
191191
}
192192

193-
Span<float> distances = stackalloc float[totalAdded];
193+
// Avoid pool overhead for short runs.
194+
// This method can be called in high volume.
195+
const int MaxStackSize = 1024 / sizeof(float);
196+
float[] rentedFromPool = null;
197+
Span<float> buffer =
198+
totalAdded > MaxStackSize
199+
? (rentedFromPool = ArrayPool<float>.Shared.Rent(totalAdded))
200+
: stackalloc float[MaxStackSize];
201+
202+
Span<float> distances = buffer.Slice(0, totalAdded);
203+
194204
for (int i = 0; i < totalAdded; i++)
195205
{
196206
distances[i] = Vector2.DistanceSquared(start, intersections[i]);
@@ -205,6 +215,11 @@ int IPathInternals.FindIntersections(
205215
totalAdded = InternalPath.ApplyNonZeroIntersectionRules(activeIntersections, activeOrientations);
206216
}
207217

218+
if (rentedFromPool != null)
219+
{
220+
ArrayPool<float>.Shared.Return(rentedFromPool);
221+
}
222+
208223
return totalAdded;
209224
}
210225

@@ -248,7 +263,7 @@ public IPath Transform(Matrix3x2 matrix)
248263
int i = 0;
249264
foreach (IPath s in this.Paths)
250265
{
251-
shapes[i++] = (IPath)s.Transform(matrix);
266+
shapes[i++] = s.Transform(matrix);
252267
}
253268

254269
return new ComplexPolygon(shapes);
@@ -288,7 +303,7 @@ public IPath AsClosedPath()
288303
var paths = new IPath[this.paths.Length];
289304
for (int i = 0; i < this.paths.Length; i++)
290305
{
291-
paths[i] = (IPath)this.paths[i].AsClosedPath();
306+
paths[i] = this.paths[i].AsClosedPath();
292307
}
293308

294309
return new ComplexPolygon(paths);
@@ -298,23 +313,22 @@ public IPath AsClosedPath()
298313
/// <summary>
299314
/// Calculates the point a certain distance a path.
300315
/// </summary>
301-
/// <param name="distanceAlongPath">The distance along the path to find details of.</param>
316+
/// <param name="distance">The distance along the path to find details of.</param>
302317
/// <returns>
303318
/// Returns details about a point along a path.
304319
/// </returns>
305-
public SegmentInfo PointAlongPath(float distanceAlongPath)
320+
SegmentInfo IPathInternals.PointAlongPath(float distance)
306321
{
307-
distanceAlongPath %= this.Length;
308-
309-
foreach (IPath p in this.Paths)
322+
distance %= this.Length;
323+
foreach (InternalPath p in this.internalPaths)
310324
{
311-
if (p.Length >= distanceAlongPath)
325+
if (p.Length >= distance)
312326
{
313-
return p.PointAlongPath(distanceAlongPath);
327+
return p.PointAlongPath(distance);
314328
}
315329

316-
// reduce it before trying the next path
317-
distanceAlongPath -= p.Length;
330+
// Reduce it before trying the next path
331+
distance -= p.Length;
318332
}
319333

320334
// TODO: Perf. Throwhelper

src/ImageSharp.Drawing/Shapes/EllipsePolygon.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,10 +163,9 @@ int IPathInternals.FindIntersections(
163163
public bool Contains(PointF point) => this.innerPath.PointInPolygon(point);
164164

165165
/// <inheritdoc />
166-
public SegmentInfo PointAlongPath(float distanceAlongPath) =>
167-
168-
// TODO switch this out to a calculated algorithm
169-
this.innerPath.PointAlongPath(distanceAlongPath);
166+
// TODO switch this out to a calculated algorithm
167+
SegmentInfo IPathInternals.PointAlongPath(float distance)
168+
=> this.innerPath.PointAlongPath(distance);
170169

171170
private static CubicBezierLineSegment CreateSegment(Vector2 location, SizeF size)
172171
{

src/ImageSharp.Drawing/Shapes/IPath.cs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,6 @@ public interface IPath
2626
/// </summary>
2727
float Length { get; }
2828

29-
/// <summary>
30-
/// Calculates the point a certain distance along a path.
31-
/// </summary>
32-
/// <param name="distanceAlongPath">The distance along the path to find details of.</param>
33-
/// <returns>
34-
/// Returns details about a point along a path.
35-
/// </returns>
36-
SegmentInfo PointAlongPath(float distanceAlongPath);
37-
3829
/// <summary>
3930
/// Calculates the distance along and away from the path for a specified point.
4031
/// </summary>

src/ImageSharp.Drawing/Shapes/IPathInternals.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,14 @@ int FindIntersections(
5252
Span<PointF> intersections,
5353
Span<PointOrientation> orientations,
5454
IntersectionRule intersectionRule);
55+
56+
/// <summary>
57+
/// Returns information about a point at a given distance along a path.
58+
/// </summary>
59+
/// <param name="distance">The distance along the path to return details for.</param>
60+
/// <returns>
61+
/// The segment information.
62+
/// </returns>
63+
SegmentInfo PointAlongPath(float distance);
5564
}
5665
}

src/ImageSharp.Drawing/Shapes/InternalPath.cs

Lines changed: 109 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -245,164 +245,151 @@ public int FindIntersectionsWithOrientation(
245245
int position = 0;
246246
Vector2 lastPoint = MaxVector;
247247

248-
int max = this.points.Length;
249-
Span<PassPointData> precaclulateSpan;
250-
PassPointData[] precaclulate = null;
251-
252-
// Avoid pool overhead for short numbers.
248+
// Avoid pool overhead for short runs.
253249
// This method can be called in high volume.
254-
unsafe
255-
{
256-
if (max < (1024 / sizeof(PassPointData)))
250+
int pointsLength = this.points.Length;
251+
int maxStackSize = 1024 / Unsafe.SizeOf<PassPointData>();
252+
PassPointData[] rentedFromPool = null;
253+
Span<PassPointData> buffer =
254+
pointsLength > maxStackSize
255+
? (rentedFromPool = ArrayPool<PassPointData>.Shared.Rent(pointsLength))
256+
: stackalloc PassPointData[maxStackSize];
257+
258+
Span<PassPointData> precalculate = buffer.Slice(0, pointsLength);
259+
260+
// Pre calculate relative orientations X places ahead and behind
261+
Vector2 startToEnd = end - start;
262+
PointOrientation prevOrientation = CalulateOrientation(startToEnd, this.points[polyCorners - 1].Point - end);
263+
PointOrientation nextOrientation = CalulateOrientation(startToEnd, this.points[0].Point - end);
264+
PointOrientation nextPlus1Orientation = CalulateOrientation(startToEnd, this.points[1].Point - end);
265+
266+
// iterate over all points and precalculate data about each, pre cacluating it relative orientation
267+
for (int i = 0; i < polyCorners && count > 0; i++)
268+
{
269+
ref Segment edge = ref this.points[i].Segment;
270+
271+
// shift all orientations along but one place and fill in the last one
272+
PointOrientation pointOrientation = nextOrientation;
273+
nextOrientation = nextPlus1Orientation;
274+
nextPlus1Orientation = CalulateOrientation(startToEnd, this.points[WrapArrayIndex(i + 2, this.points.Length)].Point - end);
275+
276+
// should this point cause the last matched point to be excluded
277+
bool removeLastIntersection = nextOrientation == PointOrientation.Collinear &&
278+
pointOrientation == PointOrientation.Collinear &&
279+
nextPlus1Orientation != prevOrientation &&
280+
(this.closedPath || i > 0) &&
281+
(IsOnSegment(target, edge.Start) || IsOnSegment(target, edge.End));
282+
283+
// is there any chance the segments will intersection (do their bounding boxes touch)
284+
bool doIntersect = false;
285+
if (pointOrientation == PointOrientation.Collinear || pointOrientation != nextOrientation)
257286
{
258-
PassPointData* points = stackalloc PassPointData[max];
259-
precaclulateSpan = new Span<PassPointData>(points, max);
287+
doIntersect = (edge.Min.X - Epsilon) <= target.Max.X &&
288+
(edge.Max.X + Epsilon) >= target.Min.X &&
289+
(edge.Min.Y - Epsilon) <= target.Max.Y &&
290+
(edge.Max.Y + Epsilon) >= target.Min.Y;
260291
}
261-
else
292+
293+
precalculate[i] = new PassPointData
262294
{
263-
precaclulate = ArrayPool<PassPointData>.Shared.Rent(max);
264-
precaclulateSpan = precaclulate.AsSpan(0, this.points.Length);
265-
}
295+
RemoveLastIntersectionAndSkip = removeLastIntersection,
296+
RelativeOrientation = pointOrientation,
297+
DoIntersect = doIntersect
298+
};
299+
300+
prevOrientation = pointOrientation;
266301
}
267302

268-
try
303+
// seed the last point for deduping at begining of closed line
304+
if (this.closedPath)
269305
{
270-
// Pre calculate relative orientations X places ahead and behind
271-
Vector2 startToEnd = end - start;
272-
PointOrientation prevOrientation = CalulateOrientation(startToEnd, this.points[polyCorners - 1].Point - end);
273-
PointOrientation nextOrientation = CalulateOrientation(startToEnd, this.points[0].Point - end);
274-
PointOrientation nextPlus1Orientation = CalulateOrientation(startToEnd, this.points[1].Point - end);
306+
int prev = polyCorners - 1;
275307

276-
// iterate over all points and precalculate data about each, pre cacluating it relative orientation
277-
for (int i = 0; i < polyCorners && count > 0; i++)
308+
if (precalculate[prev].DoIntersect)
278309
{
279-
ref Segment edge = ref this.points[i].Segment;
280-
281-
// shift all orientations along but one place and fill in the last one
282-
PointOrientation pointOrientation = nextOrientation;
283-
nextOrientation = nextPlus1Orientation;
284-
nextPlus1Orientation = CalulateOrientation(startToEnd, this.points[WrapArrayIndex(i + 2, this.points.Length)].Point - end);
285-
286-
// should this point cause the last matched point to be excluded
287-
bool removeLastIntersection = nextOrientation == PointOrientation.Collinear &&
288-
pointOrientation == PointOrientation.Collinear &&
289-
nextPlus1Orientation != prevOrientation &&
290-
(this.closedPath || i > 0) &&
291-
(IsOnSegment(target, edge.Start) || IsOnSegment(target, edge.End));
292-
293-
// is there any chance the segments will intersection (do their bounding boxes touch)
294-
bool doIntersect = false;
295-
if (pointOrientation == PointOrientation.Collinear || pointOrientation != nextOrientation)
296-
{
297-
doIntersect = (edge.Min.X - Epsilon) <= target.Max.X &&
298-
(edge.Max.X + Epsilon) >= target.Min.X &&
299-
(edge.Min.Y - Epsilon) <= target.Max.Y &&
300-
(edge.Max.Y + Epsilon) >= target.Min.Y;
301-
}
302-
303-
precaclulateSpan[i] = new PassPointData
304-
{
305-
RemoveLastIntersectionAndSkip = removeLastIntersection,
306-
RelativeOrientation = pointOrientation,
307-
DoIntersect = doIntersect
308-
};
309-
310-
prevOrientation = pointOrientation;
310+
lastPoint = FindIntersection(this.points[prev].Segment, target);
311311
}
312+
}
312313

313-
// seed the last point for deduping at begining of closed line
314-
if (this.closedPath)
315-
{
316-
int prev = polyCorners - 1;
314+
for (int i = 0; i < polyCorners && count > 0; i++)
315+
{
316+
int next = WrapArrayIndex(i + 1, this.points.Length);
317317

318-
if (precaclulateSpan[prev].DoIntersect)
318+
if (precalculate[i].RemoveLastIntersectionAndSkip)
319+
{
320+
if (position > 0)
319321
{
320-
lastPoint = FindIntersection(this.points[prev].Segment, target);
322+
position--;
323+
count++;
321324
}
325+
326+
continue;
322327
}
323328

324-
for (int i = 0; i < polyCorners && count > 0; i++)
329+
if (precalculate[i].DoIntersect)
325330
{
326-
int next = WrapArrayIndex(i + 1, this.points.Length);
327-
328-
if (precaclulateSpan[i].RemoveLastIntersectionAndSkip)
331+
Vector2 point = FindIntersection(this.points[i].Segment, target);
332+
if (point != MaxVector)
329333
{
330-
if (position > 0)
334+
if (lastPoint.Equivalent(point, Epsilon2))
331335
{
332-
position--;
333-
count++;
334-
}
336+
lastPoint = MaxVector;
335337

336-
continue;
337-
}
338+
int last = WrapArrayIndex(i - 1 + polyCorners, polyCorners);
338339

339-
if (precaclulateSpan[i].DoIntersect)
340-
{
341-
Vector2 point = FindIntersection(this.points[i].Segment, target);
342-
if (point != MaxVector)
343-
{
344-
if (lastPoint.Equivalent(point, Epsilon2))
340+
// hit the same point a second time do we need to remove the old one if just clipping
341+
if (this.points[next].Point.Equivalent(point, Epsilon))
345342
{
346-
lastPoint = MaxVector;
347-
348-
int last = WrapArrayIndex(i - 1 + polyCorners, polyCorners);
349-
350-
// hit the same point a second time do we need to remove the old one if just clipping
351-
if (this.points[next].Point.Equivalent(point, Epsilon))
352-
{
353-
next = i;
354-
}
343+
next = i;
344+
}
355345

356-
if (this.points[last].Point.Equivalent(point, Epsilon))
357-
{
358-
last = i;
359-
}
346+
if (this.points[last].Point.Equivalent(point, Epsilon))
347+
{
348+
last = i;
349+
}
360350

361-
PointOrientation side = precaclulateSpan[next].RelativeOrientation;
362-
PointOrientation side2 = precaclulateSpan[last].RelativeOrientation;
351+
PointOrientation side = precalculate[next].RelativeOrientation;
352+
PointOrientation side2 = precalculate[last].RelativeOrientation;
363353

364-
if (side != side2)
365-
{
366-
// differnet side we skip adding as we are passing through it
367-
continue;
368-
}
354+
if (side != side2)
355+
{
356+
// differnet side we skip adding as we are passing through it
357+
continue;
369358
}
370-
371-
// only need to track this during odd non zero rulings
372-
orientations[position] = precaclulateSpan[i].RelativeOrientation;
373-
intersections[position] = point;
374-
position++;
375-
count--;
376359
}
377360

378-
lastPoint = point;
379-
}
380-
else
381-
{
382-
lastPoint = MaxVector;
361+
// only need to track this during odd non zero rulings
362+
orientations[position] = precalculate[i].RelativeOrientation;
363+
intersections[position] = point;
364+
position++;
365+
count--;
383366
}
384-
}
385367

386-
Vector2 startVector = start;
387-
Span<float> distances = stackalloc float[position];
388-
for (int i = 0; i < position; i++)
368+
lastPoint = point;
369+
}
370+
else
389371
{
390-
distances[i] = Vector2.DistanceSquared(startVector, intersections[i]);
372+
lastPoint = MaxVector;
391373
}
374+
}
392375

393-
Span<PointF> activeBuffer = intersections.Slice(0, position);
394-
Span<PointOrientation> activeOrientationsSpan = orientations.Slice(0, position);
395-
SortUtility.Sort(distances, activeBuffer, activeOrientationsSpan);
396-
397-
return position;
376+
Vector2 startVector = start;
377+
Span<float> distances = stackalloc float[position];
378+
for (int i = 0; i < distances.Length; i++)
379+
{
380+
distances[i] = Vector2.DistanceSquared(startVector, intersections[i]);
398381
}
399-
finally
382+
383+
Span<PointF> activeBuffer = intersections.Slice(0, position);
384+
Span<PointOrientation> activeOrientationsSpan = orientations.Slice(0, position);
385+
SortUtility.Sort(distances, activeBuffer, activeOrientationsSpan);
386+
387+
if (rentedFromPool != null)
400388
{
401-
if (precaclulate != null)
402-
{
403-
ArrayPool<PassPointData>.Shared.Return(precaclulate);
404-
}
389+
ArrayPool<PassPointData>.Shared.Return(rentedFromPool);
405390
}
391+
392+
return position;
406393
}
407394

408395
internal static int ApplyNonZeroIntersectionRules(Span<PointF> intersections, Span<PointOrientation> orientations)

0 commit comments

Comments
 (0)