Skip to content

Commit 3d737b5

Browse files
authored
Merge pull request #96 from SixLabors/af/polygon-lab
Fast polygon scanning with active edge list
2 parents acf438b + 722ea9f commit 3d737b5

198 files changed

Lines changed: 4305 additions & 1252 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Directory.Build.targets

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
<!--Src Dependencies-->
2929
<PackageReference Update="SixLabors.Fonts" Version="1.0.0-beta0013" />
30-
<PackageReference Update="SixLabors.ImageSharp" Version="1.0.0-rc0003" />
30+
<PackageReference Update="SixLabors.ImageSharp" Version="1.0.2" />
3131
</ItemGroup>
3232

3333
</Project>

src/ImageSharp.Drawing/ImageSharp.Drawing.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<ItemGroup>
1616
<PackageReference Include="SixLabors.Fonts" />
1717
<PackageReference Include="SixLabors.ImageSharp" />
18-
<PackageReference Include="Microsoft.SourceLink.GitHub"/>
18+
<PackageReference Include="Microsoft.SourceLink.GitHub" />
1919
<PackageReference Include="MinVer" PrivateAssets="All" />
2020
</ItemGroup>
2121

src/ImageSharp.Drawing/Primitives/Region.cs

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,12 @@ namespace SixLabors.ImageSharp.Drawing
1010
/// </summary>
1111
public abstract class Region
1212
{
13-
/// <summary>
14-
/// Gets the maximum number of intersections to could be returned.
15-
/// </summary>
16-
public abstract int MaxIntersections { get; }
17-
1813
/// <summary>
1914
/// Gets the bounding box that entirely surrounds this region.
2015
/// </summary>
21-
/// <remarks>
22-
/// This should always contains all possible points returned from <see cref="Scan"/>.
23-
/// </remarks>
2416
public abstract Rectangle Bounds { get; }
2517

26-
/// <summary>
27-
/// Scans the X axis for intersections at the Y axis position.
28-
/// </summary>
29-
/// <param name="y">The position along the y axis to find intersections.</param>
30-
/// <param name="buffer">The buffer.</param>
31-
/// <param name="configuration">A <see cref="Configuration"/> instance in the context of the caller.</param>
32-
/// <param name="intersectionRule">How intersections are handled.</param>
33-
/// <returns>The number of intersections found.</returns>
34-
public abstract int Scan(float y, Span<float> buffer, Configuration configuration, IntersectionRule intersectionRule);
18+
// We should consider removing Region, so keeping this internal for now.
19+
internal abstract IPath Shape { get; }
3520
}
3621
}

src/ImageSharp.Drawing/Primitives/ShapeRegion.cs

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ internal class ShapeRegion : Region
1818
public ShapeRegion(IPath shape)
1919
{
2020
IPath closedPath = shape.AsClosedPath();
21-
this.MaxIntersections = closedPath.MaxIntersections;
2221
this.Shape = closedPath;
2322
int left = (int)MathF.Floor(shape.Bounds.Left);
2423
int top = (int)MathF.Floor(shape.Bounds.Top);
@@ -31,32 +30,9 @@ public ShapeRegion(IPath shape)
3130
/// <summary>
3231
/// Gets the fillable shape
3332
/// </summary>
34-
public IPath Shape { get; }
35-
36-
/// <inheritdoc/>
37-
public override int MaxIntersections { get; }
33+
internal override IPath Shape { get; }
3834

3935
/// <inheritdoc/>
4036
public override Rectangle Bounds { get; }
41-
42-
/// <inheritdoc/>
43-
public override int Scan(float y, Span<float> buffer, Configuration configuration, IntersectionRule intersectionRule)
44-
{
45-
var start = new PointF(this.Bounds.Left - 1, y);
46-
var end = new PointF(this.Bounds.Right + 1, y);
47-
48-
using (IMemoryOwner<PointF> tempBuffer = configuration.MemoryAllocator.Allocate<PointF>(buffer.Length))
49-
{
50-
Span<PointF> innerBuffer = tempBuffer.Memory.Span;
51-
int count = this.Shape.FindIntersections(start, end, innerBuffer, intersectionRule);
52-
53-
for (int i = 0; i < count; i++)
54-
{
55-
buffer[i] = innerBuffer[i].X;
56-
}
57-
58-
return count;
59-
}
60-
}
6137
}
6238
}

src/ImageSharp.Drawing/Processing/PathGradientBrush.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ public Edge(Path path, Color startColor, Color endColor)
139139
{
140140
this.path = path;
141141

142-
Vector2[] points = path.LineSegments.SelectMany(s => s.Flatten()).Select(p => (Vector2)p).ToArray();
142+
Vector2[] points = path.LineSegments.SelectMany(s => s.Flatten().ToArray()).Select(p => (Vector2)p).ToArray();
143143

144144
this.Start = points[0];
145145
this.StartColor = (Vector4)startColor;

src/ImageSharp.Drawing/Processing/PatternBrush.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
using System;
55
using System.Buffers;
66
using System.Numerics;
7-
using SixLabors.ImageSharp.Advanced;
7+
using SixLabors.ImageSharp.Drawing.Utilities;
88
using SixLabors.ImageSharp.Memory;
99
using SixLabors.ImageSharp.PixelFormats;
1010

@@ -155,7 +155,7 @@ public override void Apply(Span<float> scanline, int x, int y)
155155

156156
for (int i = 0; i < scanline.Length; i++)
157157
{
158-
amountSpan[i] = NumberUtilities.ClampFloat(scanline[i] * this.Options.BlendPercentage, 0, 1F);
158+
amountSpan[i] = NumericUtilities.ClampFloat(scanline[i] * this.Options.BlendPercentage, 0, 1F);
159159

160160
int patternX = (x + i) % this.pattern.Columns;
161161
overlaySpan[i] = this.pattern[patternY, patternX];

src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ namespace SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing
1212
/// </summary>
1313
public class FillRegionProcessor : IImageProcessor
1414
{
15+
/// <summary>
16+
/// Minimum subpixel count for rasterization, being applied even if antialiasing is off.
17+
/// </summary>
18+
internal const int MinimumSubpixelCount = 8;
19+
1520
/// <summary>
1621
/// Initializes a new instance of the <see cref="FillRegionProcessor" /> class.
1722
/// </summary>
@@ -42,7 +47,6 @@ public FillRegionProcessor(ShapeGraphicsOptions options, IBrush brush, Region re
4247

4348
/// <inheritdoc />
4449
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
45-
where TPixel : unmanaged, IPixel<TPixel>
46-
=> new FillRegionProcessor<TPixel>(configuration, this, source, sourceRectangle);
50+
where TPixel : unmanaged, IPixel<TPixel> => new FillRegionProcessor<TPixel>(configuration, this, source, sourceRectangle);
4751
}
4852
}

src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor{TPixel}.cs

Lines changed: 50 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33

44
using System;
55
using System.Buffers;
6-
using SixLabors.ImageSharp.Advanced;
6+
using SixLabors.ImageSharp.Drawing.Shapes;
7+
using SixLabors.ImageSharp.Drawing.Shapes.Rasterization;
78
using SixLabors.ImageSharp.Memory;
89
using SixLabors.ImageSharp.PixelFormats;
910
using SixLabors.ImageSharp.Processing.Processors;
@@ -54,127 +55,85 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)
5455
return; // no effect inside image;
5556
}
5657

57-
int maxIntersections = region.MaxIntersections;
58-
float subpixelCount = 4;
58+
int subpixelCount = FillRegionProcessor.MinimumSubpixelCount;
5959

6060
// we need to offset the pixel grid to account for when we outline a path.
6161
// basically if the line is [1,2] => [3,2] then when outlining at 1 we end up with a region of [0.5,1.5],[1.5, 1.5],[3.5,2.5],[2.5,2.5]
6262
// and this can cause missed fills when not using antialiasing.so we offset the pixel grid by 0.5 in the x & y direction thus causing the#
6363
// region to align with the pixel grid.
6464
if (graphicsOptions.Antialias)
6565
{
66-
subpixelCount = graphicsOptions.AntialiasSubpixelDepth;
67-
if (subpixelCount < 4)
68-
{
69-
subpixelCount = 4;
70-
}
66+
subpixelCount = Math.Max(subpixelCount, graphicsOptions.AntialiasSubpixelDepth);
7167
}
7268

73-
using (BrushApplicator<TPixel> applicator = brush.CreateApplicator(configuration, graphicsOptions, source, rect))
69+
using BrushApplicator<TPixel> applicator = brush.CreateApplicator(configuration, graphicsOptions, source, rect);
70+
int scanlineWidth = maxX - minX;
71+
MemoryAllocator allocator = this.Configuration.MemoryAllocator;
72+
bool scanlineDirty = true;
73+
74+
var scanner = PolygonScanner.Create(
75+
region.Shape,
76+
minY,
77+
maxY,
78+
subpixelCount,
79+
shapeOptions.IntersectionRule,
80+
configuration.MemoryAllocator);
81+
82+
try
7483
{
75-
int scanlineWidth = maxX - minX;
76-
MemoryAllocator allocator = this.Configuration.MemoryAllocator;
77-
using (IMemoryOwner<float> bBuffer = allocator.Allocate<float>(maxIntersections))
78-
using (IMemoryOwner<float> bScanline = allocator.Allocate<float>(scanlineWidth))
84+
using IMemoryOwner<float> bScanline = allocator.Allocate<float>(scanlineWidth);
85+
Span<float> scanline = bScanline.Memory.Span;
86+
87+
while (scanner.MoveToNextPixelLine())
7988
{
80-
bool scanlineDirty = true;
81-
float subpixelFraction = 1f / subpixelCount;
82-
float subpixelFractionPoint = subpixelFraction / subpixelCount;
89+
if (scanlineDirty)
90+
{
91+
scanline.Clear();
92+
}
8393

84-
Span<float> buffer = bBuffer.Memory.Span;
85-
Span<float> scanline = bScanline.Memory.Span;
94+
scanlineDirty = scanner.ScanCurrentPixelLineInto(minX, 0, scanline);
8695

87-
for (int y = minY; y < maxY; y++)
96+
if (scanlineDirty)
8897
{
89-
if (scanlineDirty)
98+
int y = scanner.PixelLineY;
99+
if (!graphicsOptions.Antialias)
90100
{
91-
scanline.Clear();
92-
scanlineDirty = false;
93-
}
94-
95-
float yPlusOne = y + 1;
96-
for (float subPixel = y; subPixel < yPlusOne; subPixel += subpixelFraction)
97-
{
98-
int pointsFound = region.Scan(subPixel, buffer, configuration, shapeOptions.IntersectionRule);
99-
if (pointsFound == 0)
101+
bool hasOnes = false;
102+
bool hasZeros = false;
103+
for (int x = 0; x < scanline.Length; x++)
100104
{
101-
// nothing on this line, skip
102-
continue;
103-
}
104-
105-
for (int point = 0; point < pointsFound && point < buffer.Length - 1; point += 2)
106-
{
107-
// points will be paired up
108-
float scanStart = buffer[point] - minX;
109-
float scanEnd = buffer[point + 1] - minX;
110-
int startX = (int)MathF.Floor(scanStart);
111-
int endX = (int)MathF.Floor(scanEnd);
112-
113-
if (startX >= 0 && startX < scanline.Length)
105+
if (scanline[x] >= 0.5)
114106
{
115-
for (float x = scanStart; x < startX + 1; x += subpixelFraction)
116-
{
117-
scanline[startX] += subpixelFractionPoint;
118-
scanlineDirty = true;
119-
}
107+
scanline[x] = 1;
108+
hasOnes = true;
120109
}
121-
122-
if (endX >= 0 && endX < scanline.Length)
123-
{
124-
for (float x = endX; x < scanEnd; x += subpixelFraction)
125-
{
126-
scanline[endX] += subpixelFractionPoint;
127-
scanlineDirty = true;
128-
}
129-
}
130-
131-
int nextX = startX + 1;
132-
endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge
133-
nextX = Math.Max(nextX, 0);
134-
for (int x = nextX; x < endX; x++)
110+
else
135111
{
136-
scanline[x] += subpixelFraction;
137-
scanlineDirty = true;
112+
scanline[x] = 0;
113+
hasZeros = true;
138114
}
139115
}
140-
}
141116

142-
if (scanlineDirty)
143-
{
144-
if (!graphicsOptions.Antialias)
117+
if (isSolidBrushWithoutBlending && hasOnes != hasZeros)
145118
{
146-
bool hasOnes = false;
147-
bool hasZeros = false;
148-
for (int x = 0; x < scanlineWidth; x++)
119+
if (hasOnes)
149120
{
150-
if (scanline[x] >= 0.5)
151-
{
152-
scanline[x] = 1;
153-
hasOnes = true;
154-
}
155-
else
156-
{
157-
scanline[x] = 0;
158-
hasZeros = true;
159-
}
121+
source.GetPixelRowSpan(y).Slice(minX, scanlineWidth).Fill(solidBrushColor);
160122
}
161123

162-
if (isSolidBrushWithoutBlending && hasOnes != hasZeros)
163-
{
164-
if (hasOnes)
165-
{
166-
source.GetPixelRowSpan(y).Slice(minX, scanlineWidth).Fill(solidBrushColor);
167-
}
168-
169-
continue;
170-
}
124+
continue;
171125
}
172-
173-
applicator.Apply(scanline, minX, y);
174126
}
127+
128+
applicator.Apply(scanline, minX, y);
175129
}
176130
}
177131
}
132+
finally
133+
{
134+
// ref structs can't implement interfaces so technically PolygonScanner is not IDisposable
135+
scanner.Dispose();
136+
}
178137
}
179138

180139
private static bool IsSolidBrushWithoutBlending(GraphicsOptions options, IBrush inputBrush, out SolidBrush solidBrush)

0 commit comments

Comments
 (0)