diff --git a/src/ImageSharp.Drawing/Paths/InternalPath.cs b/src/ImageSharp.Drawing/Paths/InternalPath.cs
index 52d43b6e8c..aab71937de 100644
--- a/src/ImageSharp.Drawing/Paths/InternalPath.cs
+++ b/src/ImageSharp.Drawing/Paths/InternalPath.cs
@@ -14,6 +14,11 @@ namespace ImageSharp.Drawing.Paths
///
internal class InternalPath
{
+ ///
+ /// The maximum vector
+ ///
+ private static readonly Vector2 MaxVector = new Vector2(float.MaxValue);
+
///
/// The locker.
///
@@ -163,6 +168,45 @@ namespace ImageSharp.Drawing.Paths
};
}
+ ///
+ /// Finds the intersections.
+ ///
+ /// The start.
+ /// The end.
+ /// The buffer.
+ /// The count.
+ /// The offset.
+ /// number iof intersections hit
+ public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset)
+ {
+ int polyCorners = this.points.Length;
+
+ if (!this.closedPath)
+ {
+ polyCorners -= 1;
+ }
+
+ int position = 0;
+ for (int i = 0; i < polyCorners && count > 0; i++)
+ {
+ int next = i + 1;
+ if (this.closedPath && next == polyCorners)
+ {
+ next = 0;
+ }
+
+ var point = FindIntersection(this.points[i], this.points[next], start, end);
+ if (point != MaxVector)
+ {
+ buffer[position + offset] = point;
+ position++;
+ count--;
+ }
+ }
+
+ return position;
+ }
+
///
/// Points the in polygon.
///
@@ -203,6 +247,92 @@ namespace ImageSharp.Drawing.Paths
return oddNodes;
}
+ private static bool BoundingBoxesIntersect(Vector2 line1Start, Vector2 line1End, Vector2 line2Start, Vector2 line2End)
+ {
+ var topLeft1 = Vector2.Min(line1Start, line1End);
+ var bottomRight1 = Vector2.Max(line1Start, line1End);
+
+ var topLeft2 = Vector2.Min(line2Start, line2End);
+ var bottomRight2 = Vector2.Max(line2Start, line2End);
+
+ var left1 = topLeft1.X;
+ var right1 = bottomRight1.X;
+ var top1 = topLeft1.Y;
+ var bottom1 = bottomRight1.Y;
+
+ var left2 = topLeft2.X;
+ var right2 = bottomRight2.X;
+ var top2 = topLeft2.Y;
+ var bottom2 = bottomRight2.Y;
+
+ return left1 <= right2 && right1 >= left2
+ &&
+ top1 <= bottom2 && bottom1 >= top2;
+ }
+
+ private static Vector2 FindIntersection(Vector2 line1Start, Vector2 line1End, Vector2 line2Start, Vector2 line2End)
+ {
+ // do lines cross at all
+ if (!BoundingBoxesIntersect(line1Start, line1End, line2Start, line2End))
+ {
+ return MaxVector;
+ }
+
+ var line1Diff = line1End - line1Start;
+ var line2Diff = line2End - line2Start;
+
+ Vector2 point;
+ if (line1Diff.X == 0)
+ {
+ float slope = line2Diff.Y / line2Diff.X;
+
+ var yinter = line2Start.Y - (slope * line2Start.X);
+ var y = (line1Start.X * slope) + yinter;
+ point = new Vector2(line1Start.X, y);
+
+ // horizontal and vertical lines
+ }
+ else if (line2Diff.X == 0)
+ {
+ float slope = line1Diff.Y / line1Diff.X;
+ var yinter = line1Start.Y - (slope * line1Start.X);
+ var y = (line2Start.X * slope) + yinter;
+ point = new Vector2(line2Start.X, y);
+
+ // horizontal and vertical lines
+ }
+ else
+ {
+ float slope1 = line1Diff.Y / line1Diff.X;
+ float slope2 = line2Diff.Y / line2Diff.X;
+
+ var yinter1 = line1Start.Y - (slope1 * line1Start.X);
+ var yinter2 = line2Start.Y - (slope2 * line2Start.X);
+
+ if (slope1 == slope2 && yinter1 != yinter2)
+ {
+ return MaxVector;
+ }
+
+ float x = (yinter2 - yinter1) / (slope1 - slope2);
+
+ float y = (slope1 * x) + yinter1;
+
+ point = new Vector2(x, y);
+ }
+
+ if (BoundingBoxesIntersect(line1Start, line1End, point, point))
+ {
+ return point;
+ }
+ else if (BoundingBoxesIntersect(line2Start, line2End, point, point))
+ {
+ return point;
+ }
+
+ return MaxVector;
+ }
+
///
/// Simplifies the collection of segments.
///
diff --git a/src/ImageSharp.Drawing/Processors/FillShapeProcessorFast.cs b/src/ImageSharp.Drawing/Processors/FillShapeProcessorFast.cs
new file mode 100644
index 0000000000..29b2b01d1b
--- /dev/null
+++ b/src/ImageSharp.Drawing/Processors/FillShapeProcessorFast.cs
@@ -0,0 +1,268 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Drawing.Processors
+{
+ using System;
+ using System.Buffers;
+ using System.Numerics;
+ using System.Threading.Tasks;
+ using Drawing;
+ using ImageSharp.Processing;
+ using Shapes;
+ using Rectangle = ImageSharp.Rectangle;
+
+ ///
+ /// Usinf a brsuh and a shape fills shape with contents of brush the
+ ///
+ /// The type of the color.
+ ///
+ public class FillShapeProcessorFast : ImageProcessor
+ where TColor : struct, IPackedPixel, IEquatable
+ {
+ private const float AntialiasFactor = 1f;
+ private const int DrawPadding = 1;
+ private readonly IBrush fillColor;
+ private readonly IShape poly;
+ private readonly GraphicsOptions options;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The brush.
+ /// The shape.
+ /// The options.
+ public FillShapeProcessorFast(IBrush brush, IShape shape, GraphicsOptions options)
+ {
+ this.poly = shape;
+ this.fillColor = brush;
+ this.options = options;
+ }
+
+ ///
+ protected override void OnApply(ImageBase source, Rectangle sourceRectangle)
+ {
+ Rectangle rect = RectangleF.Ceiling(this.poly.Bounds); // rounds the points out away from the center
+
+ int polyStartY = rect.Y - DrawPadding;
+ int polyEndY = rect.Bottom + DrawPadding;
+ int startX = rect.X - DrawPadding;
+ int endX = rect.Right + DrawPadding;
+
+ int minX = Math.Max(sourceRectangle.Left, startX);
+ int maxX = Math.Min(sourceRectangle.Right - 1, endX);
+ int minY = Math.Max(sourceRectangle.Top, polyStartY);
+ int maxY = Math.Min(sourceRectangle.Bottom - 1, polyEndY);
+
+ // Align start/end positions.
+ minX = Math.Max(0, minX);
+ maxX = Math.Min(source.Width, maxX);
+ minY = Math.Max(0, minY);
+ maxY = Math.Min(source.Height, maxY);
+
+ // Reset offset if necessary.
+ if (minX > 0)
+ {
+ startX = 0;
+ }
+
+ if (minY > 0)
+ {
+ polyStartY = 0;
+ }
+
+ ArrayPool arrayPool = ArrayPool.Shared;
+
+ int maxIntersections = this.poly.MaxIntersections;
+
+ using (PixelAccessor sourcePixels = source.Lock())
+ using (IBrushApplicator applicator = this.fillColor.CreateApplicator(sourcePixels, rect))
+ {
+ // we need to repeat this vertically to set anitialiasing vertically
+ // but we only have to get colors/fills for the external points nearest transitions in the X Pass ands only is anitialiasing is enabled
+ Parallel.For(
+ minY,
+ maxY,
+ this.ParallelOptions,
+ y =>
+ {
+ int offsetY = y - polyStartY;
+ var buffer = arrayPool.Rent(maxIntersections);
+ var left = new Vector2(startX, offsetY);
+ var right = new Vector2(endX, offsetY);
+
+ // foreach line we get all the points where this line crosses the polygon
+ var pointsFound = this.poly.FindIntersections(left, right, buffer, maxIntersections, 0);
+ if (pointsFound == 0)
+ {
+ arrayPool.Return(buffer);
+
+ // nothign on this line skip
+ return;
+ }
+
+ QuickSort(buffer, 0, pointsFound);
+
+ int currentIntersection = 0;
+ float nextPoint = buffer[0].X;
+ float lastPoint = left.X;
+ float targetPoint = nextPoint;
+ bool isInside = false;
+
+ // every odd point is the start of a line
+ Vector2 currentPoint = default(Vector2);
+
+ for (int x = minX; x < maxX; x++)
+ {
+ int offsetX = x - startX;
+ currentPoint.X = offsetX;
+ currentPoint.Y = offsetY;
+ if (!isInside)
+ {
+ if (offsetX < (nextPoint - DrawPadding) && offsetX > (lastPoint + DrawPadding))
+ {
+ if (nextPoint == right.X)
+ {
+ // we are in the ends run skip it
+ x = maxX;
+ continue;
+ }
+
+ // lets just jump forward
+ x = (int)Math.Floor(nextPoint) + startX - DrawPadding;
+ }
+ }
+ bool onCorner = false;
+
+ // there seems to be some issue with this switch.
+ if (offsetX >= nextPoint)
+ {
+ currentIntersection++;
+ lastPoint = nextPoint;
+ if (currentIntersection == pointsFound)
+ {
+ nextPoint = right.X;
+ }
+ else
+ {
+ nextPoint = buffer[currentIntersection].X;
+
+ // double point from a corner flip the bit back and move on again
+ if (nextPoint == lastPoint)
+ {
+ onCorner = true;
+ isInside ^= true;
+ currentIntersection++;
+ if (currentIntersection == pointsFound)
+ {
+ nextPoint = right.X;
+ }
+ else
+ {
+ nextPoint = buffer[currentIntersection].X;
+ }
+ }
+ }
+
+ isInside ^= true;
+ }
+
+ float opacity = 1;
+ if (!isInside && !onCorner)
+ {
+ if (this.options.Antialias)
+ {
+ float distance = float.MaxValue;
+ if (offsetX == lastPoint || offsetX == nextPoint)
+ {
+ // we are to far away from the line
+ distance = 0;
+ }
+ else if (nextPoint - AntialiasFactor < offsetX)
+ {
+ // we are near the left of the line
+ distance = nextPoint - offsetX;
+ }
+ else if (lastPoint + AntialiasFactor > offsetX)
+ {
+ // we are near the right of the line
+ distance = offsetX - lastPoint;
+ }
+ else
+ {
+ // we are to far away from the line
+ continue;
+ }
+ opacity = 1 - (distance / AntialiasFactor);
+ }
+ else
+ {
+ continue;
+ }
+ }
+
+ if (opacity > Constants.Epsilon)
+ {
+ Vector4 backgroundVector = sourcePixels[offsetX, offsetY].ToVector4();
+ Vector4 sourceVector = applicator.GetColor(currentPoint).ToVector4();
+
+ Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity);
+ finalColor.W = backgroundVector.W;
+
+ TColor packed = default(TColor);
+ packed.PackFromVector4(finalColor);
+ sourcePixels[offsetX, offsetY] = packed;
+ }
+ }
+
+ arrayPool.Return(buffer);
+ });
+ }
+ }
+
+ private static void QuickSort(Vector2[] data, int left, int right)
+ {
+ int i = left - 1;
+ int j = right;
+
+ while (true)
+ {
+ float d = data[left].X;
+ do
+ {
+ i++;
+ }
+ while (data[i].X < d);
+
+ do
+ {
+ j--;
+ }
+ while (data[j].X > d);
+
+ if (i < j)
+ {
+ Vector2 tmp = data[i];
+ data[i] = data[j];
+ data[j] = tmp;
+ }
+ else
+ {
+ if (left < j)
+ {
+ QuickSort(data, left, j);
+ }
+
+ if (++j < right)
+ {
+ QuickSort(data, j, right);
+ }
+
+ return;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp.Drawing/Shapes/BezierPolygon.cs b/src/ImageSharp.Drawing/Shapes/BezierPolygon.cs
index 0365588238..1f32814cb6 100644
--- a/src/ImageSharp.Drawing/Shapes/BezierPolygon.cs
+++ b/src/ImageSharp.Drawing/Shapes/BezierPolygon.cs
@@ -34,6 +34,14 @@ namespace ImageSharp.Drawing.Shapes
///
public RectangleF Bounds => this.innerPolygon.Bounds;
+ ///
+ /// Gets the maximum number intersections that a shape can have when testing a line.
+ ///
+ ///
+ /// The maximum intersections.
+ ///
+ public int MaxIntersections => this.innerPolygon.MaxIntersections;
+
///
/// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds
///
@@ -43,6 +51,22 @@ namespace ImageSharp.Drawing.Shapes
///
public float Distance(Vector2 point) => this.innerPolygon.Distance(point);
+ ///
+ /// Finds the intersections.
+ ///
+ /// The start point of the line.
+ /// The end point of the line.
+ /// The buffer that will be populated with intersections.
+ /// The count.
+ /// The offset.
+ ///
+ /// The number of intersections populated into the buffer.
+ ///
+ public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset)
+ {
+ return this.innerPolygon.FindIntersections(start, end, buffer, count, offset);
+ }
+
///
/// Returns an enumerator that iterates through the collection.
///
diff --git a/src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs b/src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs
index d9b83d3415..32de68fb9f 100644
--- a/src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs
+++ b/src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs
@@ -5,6 +5,7 @@
namespace ImageSharp.Drawing.Shapes
{
+ using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
@@ -44,7 +45,7 @@ namespace ImageSharp.Drawing.Shapes
Guard.NotNull(outlines, nameof(outlines));
Guard.MustBeGreaterThanOrEqualTo(outlines.Length, 1, nameof(outlines));
- this.FixAndSetShapes(outlines, holes);
+ this.MaxIntersections = this.FixAndSetShapes(outlines, holes);
var minX = this.shapes.Min(x => x.Bounds.Left);
var maxX = this.shapes.Max(x => x.Bounds.Right);
@@ -62,6 +63,14 @@ namespace ImageSharp.Drawing.Shapes
///
public RectangleF Bounds { get; }
+ ///
+ /// Gets the maximum number intersections that a shape can have when testing a line.
+ ///
+ ///
+ /// The maximum intersections.
+ ///
+ public int MaxIntersections { get; }
+
///
/// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds
///
@@ -103,6 +112,22 @@ namespace ImageSharp.Drawing.Shapes
return dist;
}
+ ///
+ /// Finds the intersections.
+ ///
+ /// The start point of the line.
+ /// The end point of the line.
+ /// The buffer that will be populated with intersections.
+ /// The count.
+ /// The offset.
+ ///
+ /// The number of intersections populated into the buffer.
+ ///
+ public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset)
+ {
+ throw new NotImplementedException();
+ }
+
///
/// Returns an enumerator that iterates through the collection.
///
@@ -180,7 +205,7 @@ namespace ImageSharp.Drawing.Shapes
}
}
- private void FixAndSetShapes(IEnumerable outlines, IEnumerable holes)
+ private int FixAndSetShapes(IEnumerable outlines, IEnumerable holes)
{
var clipper = new Clipper();
@@ -197,6 +222,14 @@ namespace ImageSharp.Drawing.Shapes
this.ExtractOutlines(tree, shapes, paths);
this.shapes = shapes.ToArray();
this.paths = paths.ToArray();
+
+ int intersections = 0;
+ foreach (var s in this.shapes)
+ {
+ intersections += s.MaxIntersections;
+ }
+
+ return intersections;
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp.Drawing/Shapes/IShape.cs b/src/ImageSharp.Drawing/Shapes/IShape.cs
index 2640b33aa4..5c9afc1f99 100644
--- a/src/ImageSharp.Drawing/Shapes/IShape.cs
+++ b/src/ImageSharp.Drawing/Shapes/IShape.cs
@@ -22,6 +22,14 @@ namespace ImageSharp.Drawing.Shapes
///
RectangleF Bounds { get; }
+ ///
+ /// Gets the maximum number intersections that a shape can have when testing a line.
+ ///
+ ///
+ /// The maximum intersections.
+ ///
+ int MaxIntersections { get; }
+
///
/// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds
///
@@ -30,5 +38,18 @@ namespace ImageSharp.Drawing.Shapes
/// Returns the distance from the shape to the point
///
float Distance(Vector2 point);
+
+ ///
+ /// Finds the intersections.
+ ///
+ /// The start point of the line.
+ /// The end point of the line.
+ /// The buffer that will be populated with intersections.
+ /// The count.
+ /// The offset.
+ ///
+ /// The number of intersections populated into the buffer.
+ ///
+ int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset);
}
}
diff --git a/src/ImageSharp.Drawing/Shapes/LinearPolygon.cs b/src/ImageSharp.Drawing/Shapes/LinearPolygon.cs
index 03069b6785..d590cbabff 100644
--- a/src/ImageSharp.Drawing/Shapes/LinearPolygon.cs
+++ b/src/ImageSharp.Drawing/Shapes/LinearPolygon.cs
@@ -34,6 +34,20 @@ namespace ImageSharp.Drawing.Shapes
///
public RectangleF Bounds => this.innerPolygon.Bounds;
+ ///
+ /// Gets the maximum number intersections that a shape can have when testing a line.
+ ///
+ ///
+ /// The maximum intersections.
+ ///
+ public int MaxIntersections
+ {
+ get
+ {
+ return this.innerPolygon.MaxIntersections;
+ }
+ }
+
///
/// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds
///
@@ -43,6 +57,22 @@ namespace ImageSharp.Drawing.Shapes
///
public float Distance(Vector2 point) => this.innerPolygon.Distance(point);
+ ///
+ /// Finds the intersections.
+ ///
+ /// The start point of the line.
+ /// The end point of the line.
+ /// The buffer that will be populated with intersections.
+ /// The count.
+ /// The offset.
+ ///
+ /// The number of intersections populated into the buffer.
+ ///
+ public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset)
+ {
+ return this.innerPolygon.FindIntersections(start, end, buffer, count, offset);
+ }
+
///
/// Returns an enumerator that iterates through the collection.
///
diff --git a/src/ImageSharp.Drawing/Shapes/Polygon.cs b/src/ImageSharp.Drawing/Shapes/Polygon.cs
index 6da27cf488..9fcb4a6a10 100644
--- a/src/ImageSharp.Drawing/Shapes/Polygon.cs
+++ b/src/ImageSharp.Drawing/Shapes/Polygon.cs
@@ -63,6 +63,14 @@ namespace ImageSharp.Drawing.Shapes
///
public bool IsClosed => true;
+ ///
+ /// Gets the maximum number intersections that a shape can have when testing a line.
+ ///
+ ///
+ /// The maximum intersections.
+ ///
+ public int MaxIntersections => this.innerPath.Points.Length;
+
///
/// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds
///
@@ -127,5 +135,21 @@ namespace ImageSharp.Drawing.Shapes
{
return this.innerPath.Points;
}
+
+ ///
+ /// Finds the intersections.
+ ///
+ /// The start point of the line.
+ /// The end point of the line.
+ /// The buffer that will be populated with intersections.
+ /// The count.
+ /// The offset.
+ ///
+ /// The number of intersections populated into the buffer.
+ ///
+ public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset)
+ {
+ return this.innerPath.FindIntersections(start, end, buffer, count, offset);
+ }
}
}
diff --git a/src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs b/src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs
index f05dadc7cc..170653c143 100644
--- a/src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs
+++ b/src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs
@@ -79,6 +79,14 @@ namespace ImageSharp.Drawing.Shapes
///
public float Length { get; }
+ ///
+ /// Gets the maximum number intersections that a shape can have when testing a line.
+ ///
+ ///
+ /// The maximum intersections.
+ ///
+ public int MaxIntersections => 4;
+
///
/// Calculates the distance along and away from the path for a specified point.
///
@@ -141,6 +149,22 @@ namespace ImageSharp.Drawing.Shapes
return this.points;
}
+ ///
+ /// Finds the intersections.
+ ///
+ /// The start point of the line.
+ /// The end point of the line.
+ /// The buffer that will be populated with intersections.
+ /// The count.
+ /// The offset.
+ ///
+ /// The number of intersections populated into the buffer.
+ ///
+ public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset)
+ {
+ throw new NotImplementedException();
+ }
+
private PointInfo Distance(Vector2 point, bool getDistanceAwayOnly, out bool isInside)
{
// point in rectangle
diff --git a/tests/ImageSharp.Benchmarks/Drawing/FillPolygonStatagies.cs b/tests/ImageSharp.Benchmarks/Drawing/FillPolygonStatagies.cs
new file mode 100644
index 0000000000..5018ed8f12
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/Drawing/FillPolygonStatagies.cs
@@ -0,0 +1,49 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Benchmarks
+{
+ using System.Drawing;
+ using System.Drawing.Drawing2D;
+ using System.IO;
+ using System.Numerics;
+
+ using BenchmarkDotNet.Attributes;
+
+ using CoreColor = ImageSharp.Color;
+ using CoreImage = ImageSharp.Image;
+ using Drawing.Processors;
+
+ public class FillPolygonStatagies : BenchmarkBase
+ {
+ [Benchmark(Baseline = true, Description = "Simple Fill Polygon")]
+ public void DrawSolidPolygonSimple()
+ {
+ CoreImage image = new CoreImage(800, 800);
+ var brush = Drawing.Brushes.Brushes.Solid(CoreColor.HotPink);
+ var shape = new Drawing.Shapes.LinearPolygon(new[] {
+ new Vector2(10, 10),
+ new Vector2(550, 50),
+ new Vector2(200, 400)
+ });
+
+ image.Apply(new FillShapeProcessor(brush, shape, Drawing.GraphicsOptions.Default));
+ }
+
+ [Benchmark(Description = "Fast Fill Polygon")]
+ public void DrawSolidPolygonFast()
+ {
+ CoreImage image = new CoreImage(800, 800);
+ var brush = Drawing.Brushes.Brushes.Solid(CoreColor.HotPink);
+ var shape = new Drawing.Shapes.LinearPolygon(new[] {
+ new Vector2(10, 10),
+ new Vector2(550, 50),
+ new Vector2(200, 400)
+ });
+
+ image.Apply(new FillShapeProcessorFast(brush, shape, Drawing.GraphicsOptions.Default));
+ }
+ }
+}
\ No newline at end of file