Browse Source

WIP - Improve polygon fill speed

pull/71/head
Scott Williams 9 years ago
parent
commit
bea633161b
  1. 130
      src/ImageSharp.Drawing/Paths/InternalPath.cs
  2. 268
      src/ImageSharp.Drawing/Processors/FillShapeProcessorFast.cs
  3. 24
      src/ImageSharp.Drawing/Shapes/BezierPolygon.cs
  4. 37
      src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs
  5. 21
      src/ImageSharp.Drawing/Shapes/IShape.cs
  6. 30
      src/ImageSharp.Drawing/Shapes/LinearPolygon.cs
  7. 24
      src/ImageSharp.Drawing/Shapes/Polygon.cs
  8. 24
      src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs
  9. 49
      tests/ImageSharp.Benchmarks/Drawing/FillPolygonStatagies.cs

130
src/ImageSharp.Drawing/Paths/InternalPath.cs

@ -14,6 +14,11 @@ namespace ImageSharp.Drawing.Paths
/// </summary>
internal class InternalPath
{
/// <summary>
/// The maximum vector
/// </summary>
private static readonly Vector2 MaxVector = new Vector2(float.MaxValue);
/// <summary>
/// The locker.
/// </summary>
@ -163,6 +168,45 @@ namespace ImageSharp.Drawing.Paths
};
}
/// <summary>
/// Finds the intersections.
/// </summary>
/// <param name="start">The start.</param>
/// <param name="end">The end.</param>
/// <param name="buffer">The buffer.</param>
/// <param name="count">The count.</param>
/// <param name="offset">The offset.</param>
/// <returns>number iof intersections hit</returns>
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;
}
/// <summary>
/// Points the in polygon.
/// </summary>
@ -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;
}
/// <summary>
/// Simplifies the collection of segments.
/// </summary>

268
src/ImageSharp.Drawing/Processors/FillShapeProcessorFast.cs

@ -0,0 +1,268 @@
// <copyright file="FillShapeProcessorFast.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
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;
/// <summary>
/// Usinf a brsuh and a shape fills shape with contents of brush the
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <seealso cref="ImageSharp.Processing.ImageProcessor{TColor}" />
public class FillShapeProcessorFast<TColor> : ImageProcessor<TColor>
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
private const float AntialiasFactor = 1f;
private const int DrawPadding = 1;
private readonly IBrush<TColor> fillColor;
private readonly IShape poly;
private readonly GraphicsOptions options;
/// <summary>
/// Initializes a new instance of the <see cref="FillShapeProcessorFast{TColor}"/> class.
/// </summary>
/// <param name="brush">The brush.</param>
/// <param name="shape">The shape.</param>
/// <param name="options">The options.</param>
public FillShapeProcessorFast(IBrush<TColor> brush, IShape shape, GraphicsOptions options)
{
this.poly = shape;
this.fillColor = brush;
this.options = options;
}
/// <inheritdoc/>
protected override void OnApply(ImageBase<TColor> 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<Vector2> arrayPool = ArrayPool<Vector2>.Shared;
int maxIntersections = this.poly.MaxIntersections;
using (PixelAccessor<TColor> sourcePixels = source.Lock())
using (IBrushApplicator<TColor> 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;
}
}
}
}
}

24
src/ImageSharp.Drawing/Shapes/BezierPolygon.cs

@ -34,6 +34,14 @@ namespace ImageSharp.Drawing.Shapes
/// </value>
public RectangleF Bounds => this.innerPolygon.Bounds;
/// <summary>
/// Gets the maximum number intersections that a shape can have when testing a line.
/// </summary>
/// <value>
/// The maximum intersections.
/// </value>
public int MaxIntersections => this.innerPolygon.MaxIntersections;
/// <summary>
/// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds
/// </summary>
@ -43,6 +51,22 @@ namespace ImageSharp.Drawing.Shapes
/// </returns>
public float Distance(Vector2 point) => this.innerPolygon.Distance(point);
/// <summary>
/// Finds the intersections.
/// </summary>
/// <param name="start">The start point of the line.</param>
/// <param name="end">The end point of the line.</param>
/// <param name="buffer">The buffer that will be populated with intersections.</param>
/// <param name="count">The count.</param>
/// <param name="offset">The offset.</param>
/// <returns>
/// The number of intersections populated into the buffer.
/// </returns>
public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset)
{
return this.innerPolygon.FindIntersections(start, end, buffer, count, offset);
}
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>

37
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
/// </value>
public RectangleF Bounds { get; }
/// <summary>
/// Gets the maximum number intersections that a shape can have when testing a line.
/// </summary>
/// <value>
/// The maximum intersections.
/// </value>
public int MaxIntersections { get; }
/// <summary>
/// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds
/// </summary>
@ -103,6 +112,22 @@ namespace ImageSharp.Drawing.Shapes
return dist;
}
/// <summary>
/// Finds the intersections.
/// </summary>
/// <param name="start">The start point of the line.</param>
/// <param name="end">The end point of the line.</param>
/// <param name="buffer">The buffer that will be populated with intersections.</param>
/// <param name="count">The count.</param>
/// <param name="offset">The offset.</param>
/// <returns>
/// The number of intersections populated into the buffer.
/// </returns>
public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset)
{
throw new NotImplementedException();
}
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
@ -180,7 +205,7 @@ namespace ImageSharp.Drawing.Shapes
}
}
private void FixAndSetShapes(IEnumerable<IShape> outlines, IEnumerable<IShape> holes)
private int FixAndSetShapes(IEnumerable<IShape> outlines, IEnumerable<IShape> 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;
}
}
}

21
src/ImageSharp.Drawing/Shapes/IShape.cs

@ -22,6 +22,14 @@ namespace ImageSharp.Drawing.Shapes
/// </value>
RectangleF Bounds { get; }
/// <summary>
/// Gets the maximum number intersections that a shape can have when testing a line.
/// </summary>
/// <value>
/// The maximum intersections.
/// </value>
int MaxIntersections { get; }
/// <summary>
/// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds
/// </summary>
@ -30,5 +38,18 @@ namespace ImageSharp.Drawing.Shapes
/// Returns the distance from the shape to the point
/// </returns>
float Distance(Vector2 point);
/// <summary>
/// Finds the intersections.
/// </summary>
/// <param name="start">The start point of the line.</param>
/// <param name="end">The end point of the line.</param>
/// <param name="buffer">The buffer that will be populated with intersections.</param>
/// <param name="count">The count.</param>
/// <param name="offset">The offset.</param>
/// <returns>
/// The number of intersections populated into the buffer.
/// </returns>
int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset);
}
}

30
src/ImageSharp.Drawing/Shapes/LinearPolygon.cs

@ -34,6 +34,20 @@ namespace ImageSharp.Drawing.Shapes
/// </value>
public RectangleF Bounds => this.innerPolygon.Bounds;
/// <summary>
/// Gets the maximum number intersections that a shape can have when testing a line.
/// </summary>
/// <value>
/// The maximum intersections.
/// </value>
public int MaxIntersections
{
get
{
return this.innerPolygon.MaxIntersections;
}
}
/// <summary>
/// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds
/// </summary>
@ -43,6 +57,22 @@ namespace ImageSharp.Drawing.Shapes
/// </returns>
public float Distance(Vector2 point) => this.innerPolygon.Distance(point);
/// <summary>
/// Finds the intersections.
/// </summary>
/// <param name="start">The start point of the line.</param>
/// <param name="end">The end point of the line.</param>
/// <param name="buffer">The buffer that will be populated with intersections.</param>
/// <param name="count">The count.</param>
/// <param name="offset">The offset.</param>
/// <returns>
/// The number of intersections populated into the buffer.
/// </returns>
public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset)
{
return this.innerPolygon.FindIntersections(start, end, buffer, count, offset);
}
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>

24
src/ImageSharp.Drawing/Shapes/Polygon.cs

@ -63,6 +63,14 @@ namespace ImageSharp.Drawing.Shapes
/// </value>
public bool IsClosed => true;
/// <summary>
/// Gets the maximum number intersections that a shape can have when testing a line.
/// </summary>
/// <value>
/// The maximum intersections.
/// </value>
public int MaxIntersections => this.innerPath.Points.Length;
/// <summary>
/// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds
/// </summary>
@ -127,5 +135,21 @@ namespace ImageSharp.Drawing.Shapes
{
return this.innerPath.Points;
}
/// <summary>
/// Finds the intersections.
/// </summary>
/// <param name="start">The start point of the line.</param>
/// <param name="end">The end point of the line.</param>
/// <param name="buffer">The buffer that will be populated with intersections.</param>
/// <param name="count">The count.</param>
/// <param name="offset">The offset.</param>
/// <returns>
/// The number of intersections populated into the buffer.
/// </returns>
public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset)
{
return this.innerPath.FindIntersections(start, end, buffer, count, offset);
}
}
}

24
src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs

@ -79,6 +79,14 @@ namespace ImageSharp.Drawing.Shapes
/// </value>
public float Length { get; }
/// <summary>
/// Gets the maximum number intersections that a shape can have when testing a line.
/// </summary>
/// <value>
/// The maximum intersections.
/// </value>
public int MaxIntersections => 4;
/// <summary>
/// Calculates the distance along and away from the path for a specified point.
/// </summary>
@ -141,6 +149,22 @@ namespace ImageSharp.Drawing.Shapes
return this.points;
}
/// <summary>
/// Finds the intersections.
/// </summary>
/// <param name="start">The start point of the line.</param>
/// <param name="end">The end point of the line.</param>
/// <param name="buffer">The buffer that will be populated with intersections.</param>
/// <param name="count">The count.</param>
/// <param name="offset">The offset.</param>
/// <returns>
/// The number of intersections populated into the buffer.
/// </returns>
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

49
tests/ImageSharp.Benchmarks/Drawing/FillPolygonStatagies.cs

@ -0,0 +1,49 @@
// <copyright file="FillPolygon.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
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<ImageSharp.Color>(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<ImageSharp.Color>(brush, shape, Drawing.GraphicsOptions.Default));
}
}
}
Loading…
Cancel
Save