Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fast polygon scanning with active edge list #96

Merged
merged 107 commits into from
Nov 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
907df3e
hack dev inner loop as usual
antonfirsov Sep 19, 2020
e20fa94
_IntersectionExperiments
antonfirsov Sep 19, 2020
7765409
ScanTests + infrastructure
antonfirsov Sep 20, 2020
053a907
TessellatedMultiPolygon works
antonfirsov Sep 21, 2020
abbe94d
EdgeData skeleton
antonfirsov Sep 23, 2020
5ee8e1c
ScanEdgeCollection tests
antonfirsov Sep 23, 2020
6de0e46
SimplePolygon_AllEmitCases works
antonfirsov Sep 24, 2020
4c493fd
cleanup + more tests
antonfirsov Sep 24, 2020
a077f4e
use Sign2 to determine polygon orientation
antonfirsov Sep 24, 2020
37d6d02
SortHelper
antonfirsov Sep 24, 2020
2deaeff
QuicSort, SortHelper -> SortUtility
antonfirsov Sep 24, 2020
c4e5001
started implementing PolygonScanner
antonfirsov Sep 24, 2020
cb38780
ScanEdge.GetX
antonfirsov Sep 24, 2020
2b68483
VertexCategoriesAndEmitRules.jpg
antonfirsov Sep 25, 2020
6697127
Some docs about VertexCategory rules
antonfirsov Sep 25, 2020
dbb7bdd
Move types
antonfirsov Sep 25, 2020
7fc5f82
ActiveEdgeList skeleton
antonfirsov Sep 26, 2020
254827b
NumericCornerCases
antonfirsov Oct 8, 2020
5bb6933
better VertexCategory rules
antonfirsov Oct 8, 2020
1fb0578
robust rules for categorizing horizontal edges
antonfirsov Oct 8, 2020
ef3992c
RoundToPositiveInfinity
antonfirsov Oct 8, 2020
3bd5e94
+1 corner case
antonfirsov Oct 8, 2020
48ce7cc
some progress with PolygonScanner
antonfirsov Oct 8, 2020
99d7052
some tests pass
antonfirsov Oct 9, 2020
ea57b49
even more tests passing
antonfirsov Oct 9, 2020
722e57f
FuzzyFloat
antonfirsov Oct 11, 2020
a2296be
distort lines according to rounding rules
antonfirsov Oct 11, 2020
b6859e9
comment on SelfIntersecting01
antonfirsov Oct 12, 2020
b634dea
remove TolerantComparer
antonfirsov Oct 15, 2020
d3d1fe7
FillPolygon_Solid_Basic
antonfirsov Oct 16, 2020
da87b79
ClassicPolygonScanner (for safer refactor path)
antonfirsov Oct 16, 2020
78a9b3e
trying to utilize ClassicPolygonScanner
antonfirsov Oct 16, 2020
a4edef4
ClassicPolygonScanner works
antonfirsov Oct 22, 2020
b7a145e
update PolygonScanner API
antonfirsov Oct 22, 2020
7e61da4
FillRegionProcessor + PolygonScanner
antonfirsov Oct 22, 2020
cd2517b
add GeoJson test
antonfirsov Oct 23, 2020
87daad2
allow selecting implementations with ShapeOptions
antonfirsov Oct 24, 2020
d50474f
Mississippi
antonfirsov Oct 24, 2020
38cc13c
minimal repro for numeric issues
antonfirsov Oct 24, 2020
e2091ef
Missisippi_Skia
antonfirsov Oct 25, 2020
1615360
simple trick to shave hair from Mississippi
antonfirsov Oct 25, 2020
90243a2
simplify the centered line equation
antonfirsov Oct 25, 2020
069055a
use doubles
antonfirsov Oct 25, 2020
c8a1524
Revert "use doubles"
antonfirsov Oct 25, 2020
ae8a0f8
benchmarks
antonfirsov Oct 25, 2020
c10e957
LargeGeoJson_Fill
antonfirsov Oct 26, 2020
3e3d18e
more benchmarks
antonfirsov Oct 26, 2020
de699cd
fix FillPolygonSmall
antonfirsov Oct 26, 2020
2e061bb
DefaultSubpixelCount = 8, replace half of test output
antonfirsov Oct 30, 2020
6986e5e
replace part of FillPolygonTests output + optimize PNG
antonfirsov Oct 30, 2020
ca2bb3d
replace SolidBezierTests output
antonfirsov Oct 30, 2020
adfa0f3
FillPolygon_Solid_Rgba32_White_A1_NoAntialias
antonfirsov Oct 30, 2020
05e87b0
update FillPolygon_Solid_Basic
antonfirsov Nov 1, 2020
ad3a203
delete _IntersectionExperiments
antonfirsov Nov 1, 2020
1950f1e
first attempt to handle nonzero rule
antonfirsov Nov 1, 2020
c73a016
nonzero case WIP
antonfirsov Nov 1, 2020
a364fbc
Nonzero intersections implemented
antonfirsov Nov 2, 2020
d74183a
drop FillRegionProcessorTests.MinimumAntialiasSubpixelDepth
antonfirsov Nov 2, 2020
3b70d78
DrawCircleOutsideBoundsDrawingArea
antonfirsov Nov 2, 2020
6a19722
SkipEdgesBeforeMinY
antonfirsov Nov 6, 2020
3d4c644
FillOutsideBoundsTests
antonfirsov Nov 6, 2020
12c4326
handle empty PolygonScanner in SkipEdgesBeforeMinY
antonfirsov Nov 6, 2020
64f76ef
started refactoring DrawTextProcessor
antonfirsov Nov 6, 2020
7705809
ClassicPolygonScanner renders nicely
antonfirsov Nov 6, 2020
d714eef
some shapes are lost
antonfirsov Nov 6, 2020
d60f5e4
i
antonfirsov Nov 6, 2020
4c178a8
do not re-orient font shapes
antonfirsov Nov 6, 2020
8e1ee25
update reference output
antonfirsov Nov 6, 2020
2e30745
OrientationHandling
antonfirsov Nov 11, 2020
247506f
add tests for reverse polygon scanning
antonfirsov Nov 11, 2020
ee0ea20
fix reverse Nonzero scanning
antonfirsov Nov 11, 2020
871cb1c
update DrawTextOnImageTests reference image
antonfirsov Nov 11, 2020
ab3677b
fix TessellatedMultipolygonTests.Create_FromComplexPolygon
antonfirsov Nov 11, 2020
dc754f8
change default orientation handling to ForcePositiveOrientationOnSimp…
antonfirsov Nov 11, 2020
5e372ce
refactor
antonfirsov Nov 12, 2020
e87e876
ScanCurrentSubpixelLineInto
antonfirsov Nov 12, 2020
5327707
ScanCurrentPixelLineInto
antonfirsov Nov 12, 2020
e94ab3b
always use PolygonScanner
antonfirsov Nov 12, 2020
41a415c
fix benchmarks
antonfirsov Nov 12, 2020
c5309d5
update DrawText benchmark
antonfirsov Nov 12, 2020
d1f7c9d
profiling
antonfirsov Nov 12, 2020
5380e54
some optimization
antonfirsov Nov 12, 2020
9b74e78
remove ClassicPolygonScanner and Region.Scan
antonfirsov Nov 12, 2020
9edc0e2
make StyleCop happy
antonfirsov Nov 12, 2020
a39afa7
put a comment about GeoJson test "image"
antonfirsov Nov 12, 2020
d33fd4a
remove MathF.Ceiling in rasterization
antonfirsov Nov 13, 2020
46ca49f
add optipng.exe
antonfirsov Nov 13, 2020
882fae6
update ReferenceOutput
antonfirsov Nov 13, 2020
14c5897
cleanup + add regression testing in DrawingRobustnessTests
antonfirsov Nov 13, 2020
0946c10
Revert "hack dev inner loop as usual"
antonfirsov Nov 13, 2020
cdeafe3
fix build errors against old targets
antonfirsov Nov 13, 2020
3e1715e
relax comparison on .NET Framework
antonfirsov Nov 13, 2020
f873ce7
Merge branch 'master' into af/polygon-lab
antonfirsov Nov 13, 2020
9cca58a
NumberUtilities + NumericExtensions = NumericUtilities, some SIMD
antonfirsov Nov 13, 2020
55829c1
skip DrawingProfilingBenchmarks
antonfirsov Nov 13, 2020
8798c4a
fix bug, improve bencmharks
antonfirsov Nov 13, 2020
25a8e1a
PoygonScanning.MD
antonfirsov Nov 13, 2020
df263a9
undo hack in Benchmarks
antonfirsov Nov 13, 2020
e2e1ef6
improve PolygonScanning.MD
antonfirsov Nov 13, 2020
7bbb5af
improvve PolygonScanning.MD and add benchmark results
antonfirsov Nov 13, 2020
1eccfb8
simplify PolygonScanner factory method
antonfirsov Nov 13, 2020
6d1e7a1
use latest stable versions of Fonts and ImageSharp
antonfirsov Nov 13, 2020
47f5e8e
dedupe EdgeData creation code
antonfirsov Nov 20, 2020
3ff3e59
remove OrientationHandling customization
antonfirsov Nov 20, 2020
c221b81
remove OrientationHandling
antonfirsov Nov 20, 2020
4ac3523
more sophisticated way to "argue" with StyleCop
antonfirsov Nov 20, 2020
722ea9f
do not DebugDraw on CI
antonfirsov Nov 20, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

<!--Src Dependencies-->
<PackageReference Update="SixLabors.Fonts" Version="1.0.0-beta0013" />
<PackageReference Update="SixLabors.ImageSharp" Version="1.0.0-rc0003" />
<PackageReference Update="SixLabors.ImageSharp" Version="1.0.2" />
</ItemGroup>

</Project>
2 changes: 1 addition & 1 deletion src/ImageSharp.Drawing/ImageSharp.Drawing.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<ItemGroup>
<PackageReference Include="SixLabors.Fonts" />
<PackageReference Include="SixLabors.ImageSharp" />
<PackageReference Include="Microsoft.SourceLink.GitHub"/>
<PackageReference Include="Microsoft.SourceLink.GitHub" />
<PackageReference Include="MinVer" PrivateAssets="All" />
</ItemGroup>

Expand Down
19 changes: 2 additions & 17 deletions src/ImageSharp.Drawing/Primitives/Region.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,12 @@ namespace SixLabors.ImageSharp.Drawing
/// </summary>
public abstract class Region
{
/// <summary>
/// Gets the maximum number of intersections to could be returned.
/// </summary>
public abstract int MaxIntersections { get; }

/// <summary>
/// Gets the bounding box that entirely surrounds this region.
/// </summary>
/// <remarks>
/// This should always contains all possible points returned from <see cref="Scan"/>.
/// </remarks>
public abstract Rectangle Bounds { get; }

/// <summary>
/// Scans the X axis for intersections at the Y axis position.
/// </summary>
/// <param name="y">The position along the y axis to find intersections.</param>
/// <param name="buffer">The buffer.</param>
/// <param name="configuration">A <see cref="Configuration"/> instance in the context of the caller.</param>
/// <param name="intersectionRule">How intersections are handled.</param>
/// <returns>The number of intersections found.</returns>
public abstract int Scan(float y, Span<float> buffer, Configuration configuration, IntersectionRule intersectionRule);
// We should consider removing Region, so keeping this internal for now.
internal abstract IPath Shape { get; }
}
}
26 changes: 1 addition & 25 deletions src/ImageSharp.Drawing/Primitives/ShapeRegion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ internal class ShapeRegion : Region
public ShapeRegion(IPath shape)
{
IPath closedPath = shape.AsClosedPath();
this.MaxIntersections = closedPath.MaxIntersections;
this.Shape = closedPath;
int left = (int)MathF.Floor(shape.Bounds.Left);
int top = (int)MathF.Floor(shape.Bounds.Top);
Expand All @@ -31,32 +30,9 @@ public ShapeRegion(IPath shape)
/// <summary>
/// Gets the fillable shape
/// </summary>
public IPath Shape { get; }

/// <inheritdoc/>
public override int MaxIntersections { get; }
internal override IPath Shape { get; }

/// <inheritdoc/>
public override Rectangle Bounds { get; }

/// <inheritdoc/>
public override int Scan(float y, Span<float> buffer, Configuration configuration, IntersectionRule intersectionRule)
{
var start = new PointF(this.Bounds.Left - 1, y);
var end = new PointF(this.Bounds.Right + 1, y);

using (IMemoryOwner<PointF> tempBuffer = configuration.MemoryAllocator.Allocate<PointF>(buffer.Length))
{
Span<PointF> innerBuffer = tempBuffer.Memory.Span;
int count = this.Shape.FindIntersections(start, end, innerBuffer, intersectionRule);

for (int i = 0; i < count; i++)
{
buffer[i] = innerBuffer[i].X;
}

return count;
}
}
}
}
2 changes: 1 addition & 1 deletion src/ImageSharp.Drawing/Processing/PathGradientBrush.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ public Edge(Path path, Color startColor, Color endColor)
{
this.path = path;

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

this.Start = points[0];
this.StartColor = (Vector4)startColor;
Expand Down
4 changes: 2 additions & 2 deletions src/ImageSharp.Drawing/Processing/PatternBrush.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Drawing.Utilities;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;

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

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

int patternX = (x + i) % this.pattern.Columns;
overlaySpan[i] = this.pattern[patternY, patternX];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ namespace SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing
/// </summary>
public class FillRegionProcessor : IImageProcessor
{
/// <summary>
/// Minimum subpixel count for rasterization, being applied even if antialiasing is off.
/// </summary>
internal const int MinimumSubpixelCount = 8;

/// <summary>
/// Initializes a new instance of the <see cref="FillRegionProcessor" /> class.
/// </summary>
Expand Down Expand Up @@ -42,7 +47,6 @@ public FillRegionProcessor(ShapeGraphicsOptions options, IBrush brush, Region re

/// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : unmanaged, IPixel<TPixel>
=> new FillRegionProcessor<TPixel>(configuration, this, source, sourceRectangle);
where TPixel : unmanaged, IPixel<TPixel> => new FillRegionProcessor<TPixel>(configuration, this, source, sourceRectangle);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

using System;
using System.Buffers;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Drawing.Shapes;
using SixLabors.ImageSharp.Drawing.Shapes.Rasterization;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors;
Expand Down Expand Up @@ -54,127 +55,85 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)
return; // no effect inside image;
}

int maxIntersections = region.MaxIntersections;
float subpixelCount = 4;
int subpixelCount = FillRegionProcessor.MinimumSubpixelCount;

// we need to offset the pixel grid to account for when we outline a path.
// 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]
// 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#
// region to align with the pixel grid.
if (graphicsOptions.Antialias)
{
subpixelCount = graphicsOptions.AntialiasSubpixelDepth;
if (subpixelCount < 4)
{
subpixelCount = 4;
}
subpixelCount = Math.Max(subpixelCount, graphicsOptions.AntialiasSubpixelDepth);
}

using (BrushApplicator<TPixel> applicator = brush.CreateApplicator(configuration, graphicsOptions, source, rect))
using BrushApplicator<TPixel> applicator = brush.CreateApplicator(configuration, graphicsOptions, source, rect);
int scanlineWidth = maxX - minX;
MemoryAllocator allocator = this.Configuration.MemoryAllocator;
bool scanlineDirty = true;

var scanner = PolygonScanner.Create(
region.Shape,
minY,
maxY,
subpixelCount,
shapeOptions.IntersectionRule,
configuration.MemoryAllocator);

try
{
int scanlineWidth = maxX - minX;
MemoryAllocator allocator = this.Configuration.MemoryAllocator;
using (IMemoryOwner<float> bBuffer = allocator.Allocate<float>(maxIntersections))
using (IMemoryOwner<float> bScanline = allocator.Allocate<float>(scanlineWidth))
using IMemoryOwner<float> bScanline = allocator.Allocate<float>(scanlineWidth);
Span<float> scanline = bScanline.Memory.Span;

while (scanner.MoveToNextPixelLine())
{
bool scanlineDirty = true;
float subpixelFraction = 1f / subpixelCount;
float subpixelFractionPoint = subpixelFraction / subpixelCount;
if (scanlineDirty)
{
scanline.Clear();
}

Span<float> buffer = bBuffer.Memory.Span;
Span<float> scanline = bScanline.Memory.Span;
scanlineDirty = scanner.ScanCurrentPixelLineInto(minX, 0, scanline);

for (int y = minY; y < maxY; y++)
if (scanlineDirty)
{
if (scanlineDirty)
int y = scanner.PixelLineY;
if (!graphicsOptions.Antialias)
{
scanline.Clear();
scanlineDirty = false;
}

float yPlusOne = y + 1;
for (float subPixel = y; subPixel < yPlusOne; subPixel += subpixelFraction)
{
int pointsFound = region.Scan(subPixel, buffer, configuration, shapeOptions.IntersectionRule);
if (pointsFound == 0)
bool hasOnes = false;
bool hasZeros = false;
for (int x = 0; x < scanline.Length; x++)
{
// nothing on this line, skip
continue;
}

for (int point = 0; point < pointsFound && point < buffer.Length - 1; point += 2)
{
// points will be paired up
float scanStart = buffer[point] - minX;
float scanEnd = buffer[point + 1] - minX;
int startX = (int)MathF.Floor(scanStart);
int endX = (int)MathF.Floor(scanEnd);

if (startX >= 0 && startX < scanline.Length)
if (scanline[x] >= 0.5)
{
for (float x = scanStart; x < startX + 1; x += subpixelFraction)
{
scanline[startX] += subpixelFractionPoint;
scanlineDirty = true;
}
scanline[x] = 1;
hasOnes = true;
}

if (endX >= 0 && endX < scanline.Length)
{
for (float x = endX; x < scanEnd; x += subpixelFraction)
{
scanline[endX] += subpixelFractionPoint;
scanlineDirty = true;
}
}

int nextX = startX + 1;
endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge
nextX = Math.Max(nextX, 0);
for (int x = nextX; x < endX; x++)
else
{
scanline[x] += subpixelFraction;
scanlineDirty = true;
scanline[x] = 0;
hasZeros = true;
}
}
}

if (scanlineDirty)
{
if (!graphicsOptions.Antialias)
if (isSolidBrushWithoutBlending && hasOnes != hasZeros)
{
bool hasOnes = false;
bool hasZeros = false;
for (int x = 0; x < scanlineWidth; x++)
if (hasOnes)
{
if (scanline[x] >= 0.5)
{
scanline[x] = 1;
hasOnes = true;
}
else
{
scanline[x] = 0;
hasZeros = true;
}
source.GetPixelRowSpan(y).Slice(minX, scanlineWidth).Fill(solidBrushColor);
JimBobSquarePants marked this conversation as resolved.
Show resolved Hide resolved
}

if (isSolidBrushWithoutBlending && hasOnes != hasZeros)
{
if (hasOnes)
{
source.GetPixelRowSpan(y).Slice(minX, scanlineWidth).Fill(solidBrushColor);
}

continue;
}
continue;
}

applicator.Apply(scanline, minX, y);
}

applicator.Apply(scanline, minX, y);
}
}
}
finally
{
// ref structs can't implement interfaces so technically PolygonScanner is not IDisposable
scanner.Dispose();
}
}

private static bool IsSolidBrushWithoutBlending(GraphicsOptions options, IBrush inputBrush, out SolidBrush solidBrush)
Expand Down
Loading