Skip to content

Commit 9b6e55f

Browse files
committed
add robustness to intersection code + PathGradientBrush bencmharks
1 parent 5a67308 commit 9b6e55f

5 files changed

Lines changed: 82 additions & 13 deletions

File tree

src/ImageSharp.Drawing/Utilities/Intersect.cs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,28 @@ namespace SixLabors.ImageSharp.Drawing.Utilities
55
{
66
internal static class Intersect
77
{
8-
public static bool LineSegmentToLineSegment(PointF a0, PointF a1, PointF b0, PointF b1, ref PointF intersectionPoint)
8+
private const float Eps = 1e-3f;
9+
private const float MinusEps = -Eps;
10+
private const float OnePlusEps = 1 + Eps;
11+
12+
public static bool LineSegmentToLineSegmentIgnoreCollinear(PointF a0, PointF a1, PointF b0, PointF b1, ref PointF intersectionPoint)
913
{
1014
float dax = a1.X - a0.X;
1115
float day = a1.Y - a0.Y;
1216
float dbx = b1.X - b0.X;
1317
float dby = b1.Y - b0.Y;
1418

15-
float s = ((-day * (a0.X - b0.X)) + (dax * (a0.Y - b0.Y))) / ((-dbx * day) + (dax * dby));
16-
float t = ((dbx * (a0.Y - b0.Y)) - (dby * (a0.X - b0.X))) / ((-dbx * day) + (dax * dby));
19+
float crossD = (-dbx * day) + (dax * dby);
20+
21+
if (crossD > MinusEps && crossD < Eps)
22+
{
23+
return false;
24+
}
25+
26+
float s = ((-day * (a0.X - b0.X)) + (dax * (a0.Y - b0.Y))) / crossD;
27+
float t = ((dbx * (a0.Y - b0.Y)) - (dby * (a0.X - b0.X))) / crossD;
1728

18-
if (s >= 0 && s <= 1 && t >= 0 && t <= 1)
29+
if (s > MinusEps && s < OnePlusEps && t > MinusEps && t < OnePlusEps)
1930
{
2031
intersectionPoint.X = a0.X + (t * dax);
2132
intersectionPoint.Y = a0.Y + (t * day);
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using BenchmarkDotNet.Attributes;
5+
using SixLabors.ImageSharp.Drawing.Processing;
6+
using SixLabors.ImageSharp.PixelFormats;
7+
using SixLabors.ImageSharp.Processing;
8+
9+
namespace SixLabors.ImageSharp.Drawing.Benchmarks
10+
{
11+
public class FillPathGradientBrush
12+
{
13+
private Image<Rgba32> image;
14+
15+
[GlobalSetup]
16+
public void Setup() => this.image = new Image<Rgba32>(100, 100);
17+
18+
[GlobalCleanup]
19+
public void Cleanup() => this.image.Dispose();
20+
21+
[Benchmark]
22+
public void FillGradientBrush_ImageSharp()
23+
{
24+
var star = new Star(50, 50, 5, 20, 45);
25+
PointF[] points = star.Points.ToArray();
26+
Color[] colors = { Color.Red, Color.Yellow, Color.Green, Color.Blue, Color.Purple,
27+
Color.Red, Color.Yellow, Color.Green, Color.Blue, Color.Purple };
28+
29+
var brush = new PathGradientBrush(points, colors, Color.White);
30+
31+
this.image.Mutate(x => x.Fill(brush));
32+
}
33+
}
34+
}

tests/ImageSharp.Drawing.Tests/TestUtilities/ApproximateFloatComparer.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ namespace SixLabors.ImageSharp.Drawing.Tests
1212
internal readonly struct ApproximateFloatComparer :
1313
IEqualityComparer<float>,
1414
IEqualityComparer<Vector2>,
15+
IEqualityComparer<PointF>,
1516
IEqualityComparer<Vector4>,
1617
IEqualityComparer<ColorMatrix>
1718
{
@@ -35,13 +36,19 @@ public bool Equals(float x, float y)
3536
public int GetHashCode(float obj) => obj.GetHashCode();
3637

3738
/// <inheritdoc/>
38-
public bool Equals(Vector2 x, Vector2 y) => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y);
39+
public bool Equals(Vector2 a, Vector2 b) => this.Equals(a.X, b.X) && this.Equals(a.Y, b.Y);
40+
41+
/// <inheritdoc/>
42+
public bool Equals(PointF a, PointF b) => this.Equals(a.X, b.X) && this.Equals(a.Y, b.Y);
3943

4044
/// <inheritdoc/>
4145
public int GetHashCode(Vector2 obj) => obj.GetHashCode();
4246

4347
/// <inheritdoc/>
44-
public bool Equals(Vector4 x, Vector4 y) => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y) && this.Equals(x.Z, y.Z) && this.Equals(x.W, y.W);
48+
public int GetHashCode(PointF obj) => obj.GetHashCode();
49+
50+
/// <inheritdoc/>
51+
public bool Equals(Vector4 a, Vector4 b) => this.Equals(a.X, b.X) && this.Equals(a.Y, b.Y) && this.Equals(a.Z, b.Z) && this.Equals(a.W, b.W);
4552

4653
/// <inheritdoc/>
4754
public int GetHashCode(Vector4 obj) => obj.GetHashCode();

tests/ImageSharp.Drawing.Tests/Utilities/IntersectTests.cs

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,39 @@ namespace SixLabors.ImageSharp.Drawing.Tests.Drawing.Utils
88
{
99
public class IntersectTests
1010
{
11-
public static TheoryData<PointF, PointF, PointF, PointF, PointF?> LineSegmentToLineSegment_Data =
12-
new TheoryData<PointF, PointF, PointF, PointF, PointF?>()
11+
public static TheoryData<(float x, float y), (float x, float y), (float x, float y), (float x, float y), (float x, float y)?> LineSegmentToLineSegment_Data =
12+
new ()
1313
{
14-
{ new PointF(0, 0), new PointF(2, 3), new PointF(1, 3), new PointF(1, 0), new PointF(1, 1.5f) },
15-
{ new PointF(0, 0), new PointF(2, 3), new PointF(1, 3), new PointF(1, 2), null },
14+
{ (0, 0), (2, 3), (1, 3), (1, 0), (1, 1.5f) },
15+
{ (3, 1), (3, 3), (3, 2), (4, 2), (3, 2)},
16+
{ (1, -3), (3, -1), (3, -4), (2, -2), (2, -2)},
17+
{ (0, 0), (2, 1), (2, 1.0001f), (5, 2), (2, 1)}, // Robust to inaccuracies
18+
{ (0, 0), (2, 3), (1, 3), (1, 2), null },
19+
{ (-3, 3), (-1, 3), (-3, 2), (-1, 2), null},
20+
{ (-4, 3), (-4, 1), (-5, 3), (-5, 1), null},
21+
{ (0, 0), (4, 1), (4, 1), (8, 2), null}, // Collinear intersections are ignored
22+
{ (0, 0), (4, 1), (4, 1.0001f), (8, 2), null}, // Collinear intersections are ignored
1623
};
1724

1825
[Theory]
1926
[MemberData(nameof(LineSegmentToLineSegment_Data))]
20-
public void LineSegmentToLineSegment(PointF a0, PointF a1, PointF b0, PointF b1, PointF? expected)
27+
public void LineSegmentToLineSegmentNoCollinear(
28+
(float x, float y) a0,
29+
(float x, float y) a1,
30+
(float x, float y) b0,
31+
(float x, float y) b1,
32+
(float x, float y)? expected)
2133
{
2234
PointF ip = default;
2335

24-
bool result = Intersect.LineSegmentToLineSegment(a0, a1, b0, b1, ref ip);
36+
bool result = Intersect.LineSegmentToLineSegmentIgnoreCollinear(P(a0), P(a1), P(b0), P(b1), ref ip);
2537
Assert.Equal(result, expected.HasValue);
2638
if (expected.HasValue)
2739
{
28-
Assert.Equal(expected.Value, ip);
40+
Assert.Equal(P(expected.Value), ip, new ApproximateFloatComparer(1e-3f));
2941
}
42+
43+
static PointF P((float x, float y) p) => new PointF(p.x, p.y);
3044
}
3145
}
3246
}
Lines changed: 3 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)