diff --git a/src/ImageSharp.Drawing/Brushes/IBrush.cs b/src/ImageSharp.Drawing/Brushes/IBrush.cs
index 1eea302a56..b281802048 100644
--- a/src/ImageSharp.Drawing/Brushes/IBrush.cs
+++ b/src/ImageSharp.Drawing/Brushes/IBrush.cs
@@ -14,7 +14,7 @@ namespace ImageSharp.Drawing
///
/// The pixel format.
///
- /// A brush is a simple class that will return an that will perform the
+ /// A brush is a simple class that will return an that will perform the
/// logic for converting a pixel location to a .
///
public interface IBrush
@@ -32,6 +32,6 @@ namespace ImageSharp.Drawing
/// The when being applied to things like shapes would usually be the
/// bounding box of the shape not necessarily the bounds of the whole image
///
- IBrushApplicator CreateApplicator(PixelAccessor pixelSource, RectangleF region);
+ BrushApplicator CreateApplicator(PixelAccessor pixelSource, RectangleF region);
}
}
\ No newline at end of file
diff --git a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs
index 5daf03b935..9ce235a847 100644
--- a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs
+++ b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs
@@ -32,7 +32,7 @@ namespace ImageSharp.Drawing.Brushes
}
///
- public IBrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region)
+ public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region)
{
return new ImageBrushApplicator(this.image, region);
}
@@ -40,7 +40,7 @@ namespace ImageSharp.Drawing.Brushes
///
/// The image brush applicator.
///
- private class ImageBrushApplicator : IBrushApplicator
+ private class ImageBrushApplicator : BrushApplicator
{
///
/// The source pixel accessor.
@@ -86,7 +86,7 @@ namespace ImageSharp.Drawing.Brushes
///
/// The color
///
- public TColor GetColor(Vector2 point)
+ public override TColor GetColor(Vector2 point)
{
// Offset the requested pixel by the value in the rectangle (the shapes position)
point = point - this.offset;
@@ -97,7 +97,7 @@ namespace ImageSharp.Drawing.Brushes
}
///
- public void Dispose()
+ public override void Dispose()
{
this.source.Dispose();
}
diff --git a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs
index 76b11236aa..7749f5ba84 100644
--- a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs
+++ b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs
@@ -95,7 +95,7 @@ namespace ImageSharp.Drawing.Brushes
}
///
- public IBrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region)
+ public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region)
{
return new PatternBrushApplicator(this.pattern, this.stride);
}
@@ -103,7 +103,7 @@ namespace ImageSharp.Drawing.Brushes
///
/// The pattern brush applicator.
///
- private class PatternBrushApplicator : IBrushApplicator
+ private class PatternBrushApplicator : BrushApplicator
{
///
/// The patter x-length.
@@ -139,7 +139,7 @@ namespace ImageSharp.Drawing.Brushes
///
/// The color
///
- public TColor GetColor(Vector2 point)
+ public override TColor GetColor(Vector2 point)
{
int x = (int)point.X % this.xLength;
int y = (int)point.Y % this.stride;
@@ -148,7 +148,7 @@ namespace ImageSharp.Drawing.Brushes
}
///
- public void Dispose()
+ public override void Dispose()
{
// noop
}
diff --git a/src/ImageSharp.Drawing/Brushes/Processors/IBrushApplicator.cs b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs
similarity index 61%
rename from src/ImageSharp.Drawing/Brushes/Processors/IBrushApplicator.cs
rename to src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs
index 9b09f87db3..885be57157 100644
--- a/src/ImageSharp.Drawing/Brushes/Processors/IBrushApplicator.cs
+++ b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs
@@ -1,4 +1,4 @@
-//
+//
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
@@ -13,14 +13,19 @@ namespace ImageSharp.Drawing.Processors
///
/// The pixel format.
///
- public interface IBrushApplicator : IDisposable // disposable will be required if/when there is an ImageBrush
+ public abstract class BrushApplicator : IDisposable // disposable will be required if/when there is an ImageBrush
where TColor : struct, IPackedPixel, IEquatable
{
+ ///
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ ///
+ public abstract void Dispose();
+
///
/// Gets the color for a single pixel.
///
/// The point.
/// The color
- TColor GetColor(Vector2 point);
+ public abstract TColor GetColor(Vector2 point);
}
}
diff --git a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs
index 35f72c5bfa..7149f22a01 100644
--- a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs
+++ b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs
@@ -55,7 +55,7 @@ namespace ImageSharp.Drawing.Brushes
public TColor TargetColor { get; }
///
- public IBrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region)
+ public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region)
{
return new RecolorBrushApplicator(sourcePixels, this.SourceColor, this.TargetColor, this.Threshold);
}
@@ -63,7 +63,7 @@ namespace ImageSharp.Drawing.Brushes
///
/// The recolor brush applicator.
///
- private class RecolorBrushApplicator : IBrushApplicator
+ private class RecolorBrushApplicator : BrushApplicator
{
///
/// The source pixel accessor.
@@ -113,7 +113,7 @@ namespace ImageSharp.Drawing.Brushes
///
/// The color
///
- public TColor GetColor(Vector2 point)
+ public override TColor GetColor(Vector2 point)
{
// Offset the requested pixel by the value in the rectangle (the shapes position)
TColor result = this.source[(int)point.X, (int)point.Y];
@@ -130,9 +130,8 @@ namespace ImageSharp.Drawing.Brushes
}
///
- public void Dispose()
+ public override void Dispose()
{
- // we didn't make the lock on the PixelAccessor we shouldn't release it.
}
}
}
diff --git a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs
index ac3986bba9..c3e3113992 100644
--- a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs
+++ b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs
@@ -40,7 +40,7 @@ namespace ImageSharp.Drawing.Brushes
public TColor Color => this.color;
///
- public IBrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region)
+ public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region)
{
return new SolidBrushApplicator(this.color);
}
@@ -48,7 +48,7 @@ namespace ImageSharp.Drawing.Brushes
///
/// The solid brush applicator.
///
- private class SolidBrushApplicator : IBrushApplicator
+ private class SolidBrushApplicator : BrushApplicator
{
///
/// The solid color.
@@ -71,13 +71,13 @@ namespace ImageSharp.Drawing.Brushes
///
/// The color
///
- public TColor GetColor(Vector2 point)
+ public override TColor GetColor(Vector2 point)
{
return this.color;
}
///
- public void Dispose()
+ public override void Dispose()
{
// noop
}
diff --git a/src/ImageSharp.Drawing/Paths/InternalPath.cs b/src/ImageSharp.Drawing/Paths/InternalPath.cs
index 52d43b6e8c..36a0704cbe 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.
///
@@ -164,7 +169,47 @@ namespace ImageSharp.Drawing.Paths
}
///
- /// Points the in polygon.
+ /// Based on a line described by and
+ /// populate a buffer for all points on the path that the line intersects.
+ ///
+ /// 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;
+ }
+
+ Vector2 point = FindIntersection(this.points[i], this.points[next], start, end);
+ if (point != MaxVector)
+ {
+ buffer[position + offset] = point;
+ position++;
+ count--;
+ }
+ }
+
+ return position;
+ }
+
+ ///
+ /// Determines if the specified point is inside or outside the path.
///
/// The point.
/// Returns true if the point is inside the closed path.
@@ -203,6 +248,111 @@ namespace ImageSharp.Drawing.Paths
return oddNodes;
}
+ ///
+ /// Determins if the bounding box for 2 lines
+ /// described by and
+ /// and and overlap.
+ ///
+ /// The line1 start.
+ /// The line1 end.
+ /// The line2 start.
+ /// The line2 end.
+ /// Returns true it the bounding box of the 2 lines intersect
+ private static bool BoundingBoxesIntersect(Vector2 line1Start, Vector2 line1End, Vector2 line2Start, Vector2 line2End)
+ {
+ Vector2 topLeft1 = Vector2.Min(line1Start, line1End);
+ Vector2 bottomRight1 = Vector2.Max(line1Start, line1End);
+
+ Vector2 topLeft2 = Vector2.Min(line2Start, line2End);
+ Vector2 bottomRight2 = Vector2.Max(line2Start, line2End);
+
+ float left1 = topLeft1.X;
+ float right1 = bottomRight1.X;
+ float top1 = topLeft1.Y;
+ float bottom1 = bottomRight1.Y;
+
+ float left2 = topLeft2.X;
+ float right2 = bottomRight2.X;
+ float top2 = topLeft2.Y;
+ float bottom2 = bottomRight2.Y;
+
+ return left1 <= right2 && right1 >= left2
+ &&
+ top1 <= bottom2 && bottom1 >= top2;
+ }
+
+ ///
+ /// Finds the point on line described by and
+ /// that intersects with line described by and
+ ///
+ /// The line1 start.
+ /// The line1 end.
+ /// The line2 start.
+ /// The line2 end.
+ ///
+ /// A describing the point that the 2 lines cross or if they do not.
+ ///
+ private static Vector2 FindIntersection(Vector2 line1Start, Vector2 line1End, Vector2 line2Start, Vector2 line2End)
+ {
+ // do bounding boxes overlap, if not then the lines can't and return fast.
+ if (!BoundingBoxesIntersect(line1Start, line1End, line2Start, line2End))
+ {
+ return MaxVector;
+ }
+
+ Vector2 line1Diff = line1End - line1Start;
+ Vector2 line2Diff = line2End - line2Start;
+
+ Vector2 point;
+ if (line1Diff.X == 0)
+ {
+ float slope = line2Diff.Y / line2Diff.X;
+ float yinter = line2Start.Y - (slope * line2Start.X);
+ float 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;
+ float yinter = line1Start.Y - (slope * line1Start.X);
+ float 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;
+
+ float yinter1 = line1Start.Y - (slope1 * line1Start.X);
+ float 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/Pens/IPen.cs b/src/ImageSharp.Drawing/Pens/IPen.cs
index 46b4a2c9d6..0cf473427f 100644
--- a/src/ImageSharp.Drawing/Pens/IPen.cs
+++ b/src/ImageSharp.Drawing/Pens/IPen.cs
@@ -26,6 +26,6 @@ namespace ImageSharp.Drawing.Pens
///
/// The when being applied to things like shapes would usually be the bounding box of the shape not necessarily the shape of the whole image.
///
- IPenApplicator CreateApplicator(PixelAccessor pixelSource, RectangleF region);
+ PenApplicator CreateApplicator(PixelAccessor pixelSource, RectangleF region);
}
}
diff --git a/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs b/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs
index 0af039ac77..a08c7a7faf 100644
--- a/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs
+++ b/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs
@@ -111,7 +111,7 @@ namespace ImageSharp.Drawing.Pens
/// The when being applied to things like shapes would ussually be the
/// bounding box of the shape not necorserrally the shape of the whole image
///
- public IPenApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region)
+ public PenApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region)
{
if (this.pattern == null || this.pattern.Length < 2)
{
@@ -123,9 +123,9 @@ namespace ImageSharp.Drawing.Pens
return new PatternPenApplicator(sourcePixels, this.Brush, region, this.Width, this.pattern);
}
- private class SolidPenApplicator : IPenApplicator
+ private class SolidPenApplicator : PenApplicator
{
- private readonly IBrushApplicator brush;
+ private readonly BrushApplicator brush;
private readonly float halfWidth;
public SolidPenApplicator(PixelAccessor sourcePixels, IBrush brush, RectangleF region, float width)
@@ -135,17 +135,17 @@ namespace ImageSharp.Drawing.Pens
this.RequiredRegion = RectangleF.Outset(region, width);
}
- public RectangleF RequiredRegion
+ public override RectangleF RequiredRegion
{
get;
}
- public void Dispose()
+ public override void Dispose()
{
this.brush.Dispose();
}
- public ColoredPointInfo GetColor(PointInfo info)
+ public override ColoredPointInfo GetColor(PointInfo info)
{
var result = default(ColoredPointInfo);
result.Color = this.brush.GetColor(info.SearchPoint);
@@ -164,9 +164,9 @@ namespace ImageSharp.Drawing.Pens
}
}
- private class PatternPenApplicator : IPenApplicator
+ private class PatternPenApplicator : PenApplicator
{
- private readonly IBrushApplicator brush;
+ private readonly BrushApplicator brush;
private readonly float halfWidth;
private readonly float[] pattern;
private readonly float totalLength;
@@ -188,17 +188,17 @@ namespace ImageSharp.Drawing.Pens
this.RequiredRegion = RectangleF.Outset(region, width);
}
- public RectangleF RequiredRegion
+ public override RectangleF RequiredRegion
{
get;
}
- public void Dispose()
+ public override void Dispose()
{
this.brush.Dispose();
}
- public ColoredPointInfo GetColor(PointInfo info)
+ public override ColoredPointInfo GetColor(PointInfo info)
{
var infoResult = default(ColoredPointInfo);
infoResult.DistanceFromElement = float.MaxValue; // is really outside the element
diff --git a/src/ImageSharp.Drawing/Pens/Processors/IPenApplicator.cs b/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs
similarity index 68%
rename from src/ImageSharp.Drawing/Pens/Processors/IPenApplicator.cs
rename to src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs
index 7159dfeec3..e07b969495 100644
--- a/src/ImageSharp.Drawing/Pens/Processors/IPenApplicator.cs
+++ b/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs
@@ -1,4 +1,4 @@
-//
+//
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
@@ -12,7 +12,7 @@ namespace ImageSharp.Drawing.Processors
/// primitive that converts a into a color and a distance away from the drawable part of the path.
///
/// The type of the color.
- public interface IPenApplicator : IDisposable
+ public abstract class PenApplicator : IDisposable
where TColor : struct, IPackedPixel, IEquatable
{
///
@@ -21,13 +21,18 @@ namespace ImageSharp.Drawing.Processors
///
/// The required region.
///
- RectangleF RequiredRegion { get; }
+ public abstract RectangleF RequiredRegion { get; }
+
+ ///
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ ///
+ public abstract void Dispose();
///
/// Gets a from a point represented by a .
///
/// The information to extract color details about.
/// Returns the color details and distance from a solid bit of the line.
- ColoredPointInfo GetColor(PointInfo info);
+ public abstract ColoredPointInfo GetColor(PointInfo info);
}
}
diff --git a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs
index 7fcdb95a6f..f7bdcb6895 100644
--- a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs
+++ b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs
@@ -84,7 +84,7 @@ namespace ImageSharp.Drawing.Processors
protected override void OnApply(ImageBase source, Rectangle sourceRectangle)
{
using (PixelAccessor sourcePixels = source.Lock())
- using (IPenApplicator applicator = this.pen.CreateApplicator(sourcePixels, this.region))
+ using (PenApplicator applicator = this.pen.CreateApplicator(sourcePixels, this.region))
{
var rect = RectangleF.Ceiling(applicator.RequiredRegion);
diff --git a/src/ImageSharp.Drawing/Processors/FillProcessor.cs b/src/ImageSharp.Drawing/Processors/FillProcessor.cs
index 309d3670f5..dc87e6da6c 100644
--- a/src/ImageSharp.Drawing/Processors/FillProcessor.cs
+++ b/src/ImageSharp.Drawing/Processors/FillProcessor.cs
@@ -62,7 +62,7 @@ namespace ImageSharp.Drawing.Processors
// for example If brush is SolidBrush then we could just get the color upfront
// and skip using the IBrushApplicator?.
using (PixelAccessor sourcePixels = source.Lock())
- using (IBrushApplicator applicator = this.brush.CreateApplicator(sourcePixels, sourceRectangle))
+ using (BrushApplicator applicator = this.brush.CreateApplicator(sourcePixels, sourceRectangle))
{
Parallel.For(
minY,
@@ -83,7 +83,7 @@ namespace ImageSharp.Drawing.Processors
Vector4 backgroundVector = sourcePixels[offsetX, offsetY].ToVector4();
Vector4 sourceVector = applicator.GetColor(currentPoint).ToVector4();
- var finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, 1);
+ Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, 1);
TColor packed = default(TColor);
packed.PackFromVector4(finalColor);
diff --git a/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs b/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs
index d0655341b7..36f89f8ec1 100644
--- a/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs
+++ b/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs
@@ -6,6 +6,7 @@
namespace ImageSharp.Drawing.Processors
{
using System;
+ using System.Buffers;
using System.Numerics;
using System.Threading.Tasks;
using Drawing;
@@ -32,7 +33,7 @@ namespace ImageSharp.Drawing.Processors
///
/// The brush.
/// The shape.
- /// The graphics options.
+ /// The options.
public FillShapeProcessor(IBrush brush, IShape shape, GraphicsOptions options)
{
this.poly = shape;
@@ -61,19 +62,12 @@ namespace ImageSharp.Drawing.Processors
minY = Math.Max(0, minY);
maxY = Math.Min(source.Height, maxY);
- // Reset offset if necessary.
- if (minX > 0)
- {
- startX = 0;
- }
+ ArrayPool arrayPool = ArrayPool.Shared;
- if (minY > 0)
- {
- polyStartY = 0;
- }
+ int maxIntersections = this.poly.MaxIntersections;
using (PixelAccessor sourcePixels = source.Lock())
- using (IBrushApplicator applicator = this.fillColor.CreateApplicator(sourcePixels, rect))
+ using (BrushApplicator applicator = this.fillColor.CreateApplicator(sourcePixels, rect))
{
Parallel.For(
minY,
@@ -81,57 +75,396 @@ namespace ImageSharp.Drawing.Processors
this.ParallelOptions,
y =>
{
- int offsetY = y - polyStartY;
-
- Vector2 currentPoint = default(Vector2);
+ Vector2[] buffer = arrayPool.Rent(maxIntersections);
- for (int x = minX; x < maxX; x++)
+ try
{
- int offsetX = x - startX;
- currentPoint.X = offsetX;
- currentPoint.Y = offsetY;
- float dist = this.poly.Distance(currentPoint);
- float opacity = this.Opacity(dist);
+ Vector2 left = new Vector2(startX, y);
+ Vector2 right = new Vector2(endX, y);
+
+ // foreach line we get all the points where this line crosses the polygon
+ int pointsFound = this.poly.FindIntersections(left, right, buffer, maxIntersections, 0);
+ if (pointsFound == 0)
+ {
+ arrayPool.Return(buffer);
+
+ // nothign on this line skip
+ return;
+ }
+
+ QuickSortX(buffer, pointsFound);
- if (opacity > Constants.Epsilon)
+ int currentIntersection = 0;
+ float nextPoint = buffer[0].X;
+ float lastPoint = float.MinValue;
+ bool isInside = false;
+
+ // every odd point is the start of a line
+ Vector2 currentPoint = default(Vector2);
+
+ for (int x = minX; x < maxX; x++)
{
- Vector4 backgroundVector = sourcePixels[offsetX, offsetY].ToVector4();
- Vector4 sourceVector = applicator.GetColor(currentPoint).ToVector4();
+ currentPoint.X = x;
+ currentPoint.Y = y;
+ if (!isInside)
+ {
+ if (x < (nextPoint - DrawPadding) && x > (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) - DrawPadding;
+ }
+ }
+
+ bool onCorner = false;
+
+ // there seems to be some issue with this switch.
+ if (x >= nextPoint)
+ {
+ currentIntersection++;
+ lastPoint = nextPoint;
+ if (currentIntersection == pointsFound)
+ {
+ nextPoint = right.X;
+ }
+ else
+ {
+ nextPoint = buffer[currentIntersection].X;
- Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity);
- finalColor.W = backgroundVector.W;
+ // 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;
+ }
+ }
+ }
- TColor packed = default(TColor);
- packed.PackFromVector4(finalColor);
- sourcePixels[offsetX, offsetY] = packed;
+ isInside ^= true;
+ }
+
+ float opacity = 1;
+ if (!isInside && !onCorner)
+ {
+ if (this.options.Antialias)
+ {
+ float distance = float.MaxValue;
+ if (x == lastPoint || x == nextPoint)
+ {
+ // we are to far away from the line
+ distance = 0;
+ }
+ else if (nextPoint - AntialiasFactor < x)
+ {
+ // we are near the left of the line
+ distance = nextPoint - x;
+ }
+ else if (lastPoint + AntialiasFactor > x)
+ {
+ // we are near the right of the line
+ distance = x - lastPoint;
+ }
+ else
+ {
+ // we are to far away from the line
+ continue;
+ }
+ opacity = 1 - (distance / AntialiasFactor);
+ }
+ else
+ {
+ continue;
+ }
+ }
+
+ if (opacity > Constants.Epsilon)
+ {
+ Vector4 backgroundVector = sourcePixels[x, y].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[x, y] = packed;
+ }
}
}
+ finally
+ {
+ arrayPool.Return(buffer);
+ }
});
+
+ if (this.options.Antialias)
+ {
+ // we only need to do the X can for antialiasing purposes
+ Parallel.For(
+ minX,
+ maxX,
+ this.ParallelOptions,
+ x =>
+ {
+ Vector2[] buffer = arrayPool.Rent(maxIntersections);
+
+ try
+ {
+ Vector2 left = new Vector2(x, polyStartY);
+ Vector2 right = new Vector2(x, polyEndY);
+
+ // foreach line we get all the points where this line crosses the polygon
+ int pointsFound = this.poly.FindIntersections(left, right, buffer, maxIntersections, 0);
+ if (pointsFound == 0)
+ {
+ arrayPool.Return(buffer);
+
+ // nothign on this line skip
+ return;
+ }
+
+ QuickSortY(buffer, pointsFound);
+
+ int currentIntersection = 0;
+ float nextPoint = buffer[0].Y;
+ float lastPoint = left.Y;
+ bool isInside = false;
+
+ // every odd point is the start of a line
+ Vector2 currentPoint = default(Vector2);
+
+ for (int y = minY; y < maxY; y++)
+ {
+ currentPoint.X = x;
+ currentPoint.Y = y;
+ if (!isInside)
+ {
+ if (y < (nextPoint - DrawPadding) && y > (lastPoint + DrawPadding))
+ {
+ if (nextPoint == right.Y)
+ {
+ // we are in the ends run skip it
+ y = maxY;
+ continue;
+ }
+
+ // lets just jump forward
+ y = (int)Math.Floor(nextPoint) - DrawPadding;
+ }
+ }
+ else
+ {
+ if (y < nextPoint - DrawPadding)
+ {
+ if (nextPoint == right.Y)
+ {
+ // we are in the ends run skip it
+ y = maxY;
+ continue;
+ }
+
+ // lets just jump forward
+ y = (int)Math.Floor(nextPoint);
+ }
+ }
+
+ bool onCorner = false;
+
+ if (y >= nextPoint)
+ {
+ currentIntersection++;
+ lastPoint = nextPoint;
+ if (currentIntersection == pointsFound)
+ {
+ nextPoint = right.Y;
+ }
+ else
+ {
+ nextPoint = buffer[currentIntersection].Y;
+
+ // 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.Y;
+ }
+ else
+ {
+ nextPoint = buffer[currentIntersection].Y;
+ }
+ }
+ }
+
+ isInside ^= true;
+ }
+
+ float opacity = 1;
+ if (!isInside && !onCorner)
+ {
+ if (this.options.Antialias)
+ {
+ float distance = float.MaxValue;
+ if (y == lastPoint || y == nextPoint)
+ {
+ // we are to far away from the line
+ distance = 0;
+ }
+ else if (nextPoint - AntialiasFactor < y)
+ {
+ // we are near the left of the line
+ distance = nextPoint - y;
+ }
+ else if (lastPoint + AntialiasFactor > y)
+ {
+ // we are near the right of the line
+ distance = y - lastPoint;
+ }
+ else
+ {
+ // we are to far away from the line
+ continue;
+ }
+ opacity = 1 - (distance / AntialiasFactor);
+ }
+ else
+ {
+ continue;
+ }
+ }
+
+ // don't set full opactiy color as it will have been gotten by the first scan
+ if (opacity > Constants.Epsilon && opacity < 1)
+ {
+ Vector4 backgroundVector = sourcePixels[x, y].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[x, y] = packed;
+ }
+ }
+ }
+ finally
+ {
+ arrayPool.Return(buffer);
+ }
+ });
+ }
}
}
- ///
- /// Returns the correct alpha value for the given distance.
- ///
- ///
- /// The distance.
- ///
- ///
- /// The .
- ///
- private float Opacity(float distance)
+ private static void Swap(Vector2[] data, int left, int right)
+ {
+ Vector2 tmp = data[left];
+ data[left] = data[right];
+ data[right] = tmp;
+ }
+
+ private static void QuickSortY(Vector2[] data, int size)
{
- if (distance <= 0)
+ QuickSortY(data, 0, size - 1);
+ }
+
+ private static void QuickSortY(Vector2[] data, int lo, int hi)
+ {
+ if (lo < hi)
{
- return 1;
+ int p = PartitionY(data, lo, hi);
+ QuickSortY(data, lo, p);
+ QuickSortY(data, p + 1, hi);
}
+ }
+
+ private static void QuickSortX(Vector2[] data, int size)
+ {
+ QuickSortX(data, 0, size - 1);
+ }
- if (this.options.Antialias && distance < AntialiasFactor)
+ private static void QuickSortX(Vector2[] data, int lo, int hi)
+ {
+ if (lo < hi)
{
- return 1 - (distance / AntialiasFactor);
+ int p = PartitionX(data, lo, hi);
+ QuickSortX(data, lo, p);
+ QuickSortX(data, p + 1, hi);
}
+ }
- return 0;
+ private static int PartitionX(Vector2[] data, int lo, int hi)
+ {
+ float pivot = data[lo].X;
+ int i = lo - 1;
+ int j = hi + 1;
+ while (true)
+ {
+ do
+ {
+ i = i + 1;
+ }
+ while (data[i].X < pivot);
+
+ do
+ {
+ j = j - 1;
+ }
+ while (data[j].X > pivot);
+
+ if (i >= j)
+ {
+ return j;
+ }
+
+ Swap(data, i, j);
+ }
+ }
+
+ private static int PartitionY(Vector2[] data, int lo, int hi)
+ {
+ float pivot = data[lo].Y;
+ int i = lo - 1;
+ int j = hi + 1;
+ while (true)
+ {
+ do
+ {
+ i = i + 1;
+ }
+ while (data[i].Y < pivot);
+
+ do
+ {
+ j = j - 1;
+ }
+ while (data[j].Y > pivot);
+
+ if (i >= j)
+ {
+ return j;
+ }
+
+ Swap(data, i, j);
+ }
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp.Drawing/Shapes/BezierPolygon.cs b/src/ImageSharp.Drawing/Shapes/BezierPolygon.cs
index 0365588238..b69ded2075 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,23 @@ namespace ImageSharp.Drawing.Shapes
///
public float Distance(Vector2 point) => this.innerPolygon.Distance(point);
+ ///
+ /// Based on a line described by and
+ /// populate a buffer for all points on the polygon that the line intersects.
+ ///
+ /// 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..fd709719c1 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,12 +45,12 @@ 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);
- var minY = this.shapes.Min(x => x.Bounds.Top);
- var maxY = this.shapes.Max(x => x.Bounds.Bottom);
+ float minX = this.shapes.Min(x => x.Bounds.Left);
+ float maxX = this.shapes.Max(x => x.Bounds.Right);
+ float minY = this.shapes.Min(x => x.Bounds.Top);
+ float maxY = this.shapes.Max(x => x.Bounds.Bottom);
this.Bounds = new RectangleF(minX, minY, maxX - minX, maxY - minY);
}
@@ -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
///
@@ -80,7 +89,7 @@ namespace ImageSharp.Drawing.Shapes
bool inside = false;
foreach (IShape shape in this.shapes)
{
- var d = shape.Distance(point);
+ float d = shape.Distance(point);
if (d <= 0)
{
@@ -103,6 +112,33 @@ namespace ImageSharp.Drawing.Shapes
return dist;
}
+ ///
+ /// Based on a line described by and
+ /// populate a buffer for all points on all the polygons, that make up this complex shape,
+ /// that the line intersects.
+ ///
+ /// 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)
+ {
+ int totalAdded = 0;
+ for (int i = 0; i < this.shapes.Length; i++)
+ {
+ int added = this.shapes[i].FindIntersections(start, end, buffer, count, offset);
+ count -= added;
+ offset += added;
+ totalAdded += added;
+ }
+
+ return totalAdded;
+ }
+
///
/// Returns an enumerator that iterates through the collection.
///
@@ -136,7 +172,7 @@ namespace ImageSharp.Drawing.Shapes
}
else
{
- foreach (var path in shape)
+ foreach (IPath path in shape)
{
clipper.AddPath(
path,
@@ -147,7 +183,7 @@ namespace ImageSharp.Drawing.Shapes
private void AddPoints(Clipper clipper, IEnumerable shapes, PolyType polyType)
{
- foreach (var shape in shapes)
+ foreach (IShape shape in shapes)
{
this.AddPoints(clipper, shape, polyType);
}
@@ -167,28 +203,28 @@ namespace ImageSharp.Drawing.Shapes
else
{
// convert the Clipper Contour from scaled ints back down to the origional size (this is going to be lossy but not significantly)
- var polygon = new Polygon(new Paths.LinearLineSegment(tree.Contour.ToArray()));
+ Polygon polygon = new Polygon(new Paths.LinearLineSegment(tree.Contour.ToArray()));
shapes.Add(polygon);
paths.Add(polygon);
}
}
- foreach (var c in tree.Children)
+ foreach (PolyNode c in tree.Children)
{
this.ExtractOutlines(c, shapes, paths);
}
}
- private void FixAndSetShapes(IEnumerable outlines, IEnumerable holes)
+ private int FixAndSetShapes(IEnumerable outlines, IEnumerable holes)
{
- var clipper = new Clipper();
+ Clipper clipper = new Clipper();
// add the outlines and the holes to clipper, scaling up from the float source to the int based system clipper uses
this.AddPoints(clipper, outlines, PolyType.Subject);
this.AddPoints(clipper, holes, PolyType.Clip);
- var tree = clipper.Execute();
+ PolyTree tree = clipper.Execute();
List shapes = new List();
List paths = new List();
@@ -197,6 +233,14 @@ namespace ImageSharp.Drawing.Shapes
this.ExtractOutlines(tree, shapes, paths);
this.shapes = shapes.ToArray();
this.paths = paths.ToArray();
+
+ int intersections = 0;
+ foreach (IShape 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..242e3bd8ec 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,19 @@ namespace ImageSharp.Drawing.Shapes
/// Returns the distance from the shape to the point
///
float Distance(Vector2 point);
+
+ ///
+ /// Based on a line described by and
+ /// populate a buffer for all points on the polygon that the line intersects.
+ ///
+ /// 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..30a30c20f9 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,23 @@ namespace ImageSharp.Drawing.Shapes
///
public float Distance(Vector2 point) => this.innerPolygon.Distance(point);
+ ///
+ /// Based on a line described by and
+ /// populate a buffer for all points on the polygon that the line intersects.
+ ///
+ /// 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..86c3c9ee43 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
///
@@ -74,7 +82,7 @@ namespace ImageSharp.Drawing.Shapes
{
bool isInside = this.innerPath.PointInPolygon(point);
- var distance = this.innerPath.DistanceFromPath(point).DistanceFromPath;
+ float distance = this.innerPath.DistanceFromPath(point).DistanceFromPath;
if (isInside)
{
return -distance;
@@ -127,5 +135,22 @@ namespace ImageSharp.Drawing.Shapes
{
return this.innerPath.Points;
}
+
+ ///
+ /// Based on a line described by and
+ /// populate a buffer for all points on the polygon that the line intersects.
+ ///
+ /// 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..5002bee406 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.
///
@@ -102,7 +110,7 @@ namespace ImageSharp.Drawing.Shapes
public float Distance(Vector2 point)
{
bool insidePoly;
- var result = this.Distance(point, true, out insidePoly);
+ PointInfo result = this.Distance(point, true, out insidePoly);
// invert the distance from path when inside
return insidePoly ? -result.DistanceFromPath : result.DistanceFromPath;
@@ -141,6 +149,42 @@ namespace ImageSharp.Drawing.Shapes
return this.points;
}
+ ///
+ /// Based on a line described by and
+ /// populate a buffer for all points on the edges of the
+ /// that the line intersects.
+ ///
+ /// 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)
+ {
+ int discovered = 0;
+ Vector2 startPoint = Vector2.Clamp(start, this.topLeft, this.bottomRight);
+ Vector2 endPoint = Vector2.Clamp(end, this.topLeft, this.bottomRight);
+
+ if (startPoint == Vector2.Clamp(startPoint, start, end))
+ {
+ // if start closest is within line then its a valid point
+ discovered++;
+ buffer[offset++] = startPoint;
+ }
+
+ if (endPoint == Vector2.Clamp(endPoint, start, end))
+ {
+ // if start closest is within line then its a valid point
+ discovered++;
+ buffer[offset++] = endPoint;
+ }
+
+ return discovered;
+ }
+
private PointInfo Distance(Vector2 point, bool getDistanceAwayOnly, out bool isInside)
{
// point in rectangle
diff --git a/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs
index dd4cbfd2b9..cbd44d7d1c 100644
--- a/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs
+++ b/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs
@@ -48,6 +48,8 @@ namespace ImageSharp.Tests.Drawing
Assert.Equal(Color.HotPink, sourcePixels[50, 50]);
+ Assert.Equal(Color.HotPink, sourcePixels[35, 100]);
+
Assert.Equal(Color.Blue, sourcePixels[2, 2]);
//inside hole
@@ -88,6 +90,8 @@ namespace ImageSharp.Tests.Drawing
Assert.Equal(Color.HotPink, sourcePixels[50, 50]);
+ Assert.Equal(Color.HotPink, sourcePixels[35, 100]);
+
Assert.Equal(Color.Blue, sourcePixels[2, 2]);
//inside hole
@@ -131,6 +135,8 @@ namespace ImageSharp.Tests.Drawing
Assert.Equal(mergedColor, sourcePixels[50, 50]);
+ Assert.Equal(mergedColor, sourcePixels[35, 100]);
+
Assert.Equal(Color.Blue, sourcePixels[2, 2]);
//inside hole
diff --git a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs
index a70782478b..0d3b721d52 100644
--- a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs
+++ b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs
@@ -31,7 +31,7 @@ namespace ImageSharp.Tests.Drawing
{
image
.BackgroundColor(Color.Blue)
- .FillPolygon(Color.HotPink, simplePath)
+ .FillPolygon(Color.HotPink, simplePath, new GraphicsOptions(true))
.Save(output);
}