From bea633161bec167dad6a282008d8ef1b72112410 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Wed, 4 Jan 2017 08:36:42 +0000 Subject: [PATCH 01/10] WIP - Improve polygon fill speed --- src/ImageSharp.Drawing/Paths/InternalPath.cs | 130 +++++++++ .../Processors/FillShapeProcessorFast.cs | 268 ++++++++++++++++++ .../Shapes/BezierPolygon.cs | 24 ++ .../Shapes/ComplexPolygon.cs | 37 ++- src/ImageSharp.Drawing/Shapes/IShape.cs | 21 ++ .../Shapes/LinearPolygon.cs | 30 ++ src/ImageSharp.Drawing/Shapes/Polygon.cs | 24 ++ .../Shapes/RectangularPolygon.cs | 24 ++ .../Drawing/FillPolygonStatagies.cs | 49 ++++ 9 files changed, 605 insertions(+), 2 deletions(-) create mode 100644 src/ImageSharp.Drawing/Processors/FillShapeProcessorFast.cs create mode 100644 tests/ImageSharp.Benchmarks/Drawing/FillPolygonStatagies.cs 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 From fd301f288413d8f3585143319e0f8f54b1cb13ce Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Wed, 4 Jan 2017 13:59:31 +0000 Subject: [PATCH 02/10] skip blank sections while filling polygons --- src/ImageSharp.Drawing/Fill.cs | 4 +- src/ImageSharp.Drawing/FillRectangle.cs | 4 +- .../Processors/FillShapeProcessorFast.cs | 258 +++++++++++++++--- .../Shapes/RectangularPolygon.cs | 20 +- src/ImageSharp/ImageProcessor.cs | 4 + .../Drawing/FillPolygonStatagies.cs | 36 ++- .../Drawing/SolidBezierTests.cs | 1 + .../Drawing/SolidPolygonTests.cs | 2 +- 8 files changed, 282 insertions(+), 47 deletions(-) diff --git a/src/ImageSharp.Drawing/Fill.cs b/src/ImageSharp.Drawing/Fill.cs index c0f43bdd18..1e14d8b11c 100644 --- a/src/ImageSharp.Drawing/Fill.cs +++ b/src/ImageSharp.Drawing/Fill.cs @@ -56,7 +56,7 @@ namespace ImageSharp public static Image Fill(this Image source, IBrush brush, IShape shape, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(new FillShapeProcessor(brush, shape, options)); + return source.Apply(new FillShapeProcessorFast(brush, shape, options)); } /// @@ -70,7 +70,7 @@ namespace ImageSharp public static Image Fill(this Image source, IBrush brush, IShape shape) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(new FillShapeProcessor(brush, shape, GraphicsOptions.Default)); + return source.Apply(new FillShapeProcessorFast(brush, shape, GraphicsOptions.Default)); } /// diff --git a/src/ImageSharp.Drawing/FillRectangle.cs b/src/ImageSharp.Drawing/FillRectangle.cs index d29b58e1b8..322133a4d5 100644 --- a/src/ImageSharp.Drawing/FillRectangle.cs +++ b/src/ImageSharp.Drawing/FillRectangle.cs @@ -31,7 +31,7 @@ namespace ImageSharp public static Image Fill(this Image source, IBrush brush, RectangleF shape, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(new FillShapeProcessor(brush, new RectangularPolygon(shape), options)); + return source.Apply(new FillShapeProcessorFast(brush, new RectangularPolygon(shape), options)); } /// @@ -45,7 +45,7 @@ namespace ImageSharp public static Image Fill(this Image source, IBrush brush, RectangleF shape) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(new FillShapeProcessor(brush, new RectangularPolygon(shape), GraphicsOptions.Default)); + return source.Apply(new FillShapeProcessorFast(brush, new RectangularPolygon(shape), GraphicsOptions.Default)); } /// diff --git a/src/ImageSharp.Drawing/Processors/FillShapeProcessorFast.cs b/src/ImageSharp.Drawing/Processors/FillShapeProcessorFast.cs index 29b2b01d1b..d192dc6c3a 100644 --- a/src/ImageSharp.Drawing/Processors/FillShapeProcessorFast.cs +++ b/src/ImageSharp.Drawing/Processors/FillShapeProcessorFast.cs @@ -62,17 +62,6 @@ namespace ImageSharp.Drawing.Processors 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; @@ -80,18 +69,15 @@ namespace ImageSharp.Drawing.Processors 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); + var left = new Vector2(startX, y); + var right = new Vector2(endX, y); // foreach line we get all the points where this line crosses the polygon var pointsFound = this.poly.FindIntersections(left, right, buffer, maxIntersections, 0); @@ -103,11 +89,11 @@ namespace ImageSharp.Drawing.Processors return; } - QuickSort(buffer, 0, pointsFound); + QuickSortX(buffer, 0, pointsFound); int currentIntersection = 0; float nextPoint = buffer[0].X; - float lastPoint = left.X; + float lastPoint = float.MinValue; float targetPoint = nextPoint; bool isInside = false; @@ -116,12 +102,11 @@ namespace ImageSharp.Drawing.Processors for (int x = minX; x < maxX; x++) { - int offsetX = x - startX; - currentPoint.X = offsetX; - currentPoint.Y = offsetY; + currentPoint.X = x; + currentPoint.Y = y; if (!isInside) { - if (offsetX < (nextPoint - DrawPadding) && offsetX > (lastPoint + DrawPadding)) + if (x < (nextPoint - DrawPadding) && x > (lastPoint + DrawPadding)) { if (nextPoint == right.X) { @@ -131,13 +116,13 @@ namespace ImageSharp.Drawing.Processors } // lets just jump forward - x = (int)Math.Floor(nextPoint) + startX - DrawPadding; + x = (int)Math.Floor(nextPoint) - DrawPadding; } } bool onCorner = false; // there seems to be some issue with this switch. - if (offsetX >= nextPoint) + if (x >= nextPoint) { currentIntersection++; lastPoint = nextPoint; @@ -175,20 +160,20 @@ namespace ImageSharp.Drawing.Processors if (this.options.Antialias) { float distance = float.MaxValue; - if (offsetX == lastPoint || offsetX == nextPoint) + if (x == lastPoint || x == nextPoint) { // we are to far away from the line distance = 0; } - else if (nextPoint - AntialiasFactor < offsetX) + else if (nextPoint - AntialiasFactor < x) { // we are near the left of the line - distance = nextPoint - offsetX; + distance = nextPoint - x; } - else if (lastPoint + AntialiasFactor > offsetX) + else if (lastPoint + AntialiasFactor > x) { // we are near the right of the line - distance = offsetX - lastPoint; + distance = x - lastPoint; } else { @@ -205,7 +190,7 @@ namespace ImageSharp.Drawing.Processors if (opacity > Constants.Epsilon) { - Vector4 backgroundVector = sourcePixels[offsetX, offsetY].ToVector4(); + Vector4 backgroundVector = sourcePixels[x, y].ToVector4(); Vector4 sourceVector = applicator.GetColor(currentPoint).ToVector4(); Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); @@ -213,34 +198,233 @@ namespace ImageSharp.Drawing.Processors TColor packed = default(TColor); packed.PackFromVector4(finalColor); - sourcePixels[offsetX, offsetY] = packed; + sourcePixels[x, y] = packed; } } 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 => + { + var buffer = arrayPool.Rent(maxIntersections); + var left = new Vector2(x, polyStartY); + var right = new Vector2(x, polyEndY); + + // 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; + } + + QuickSortY(buffer, 0, pointsFound); + + int currentIntersection = 0; + float nextPoint = buffer[0].Y; + float lastPoint = left.Y; + float targetPoint = nextPoint; + 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; + } + } + + arrayPool.Return(buffer); + }); + } + } + } + + private static void QuickSortX(Vector2[] data, int left, int right) + { + int i = left - 1; + int j = right; + + while (true) + { + float x = data[left].X; + do + { + i++; + } + while (data[i].X < x); + + do + { + j--; + } + while (data[j].X > x); + + if (i < j) + { + Vector2 tmp = data[i]; + data[i] = data[j]; + data[j] = tmp; + } + else + { + if (left < j) + { + QuickSortX(data, left, j); + } + + if (++j < right) + { + QuickSortX(data, j, right); + } + + return; + } } } - private static void QuickSort(Vector2[] data, int left, int right) + private static void QuickSortY(Vector2[] data, int left, int right) { int i = left - 1; int j = right; while (true) { - float d = data[left].X; + float d = data[left].Y; do { i++; } - while (data[i].X < d); + while (data[i].Y < d); do { j--; } - while (data[j].X > d); + while (data[j].Y > d); if (i < j) { @@ -252,12 +436,12 @@ namespace ImageSharp.Drawing.Processors { if (left < j) { - QuickSort(data, left, j); + QuickSortY(data, left, j); } if (++j < right) { - QuickSort(data, j, right); + QuickSortY(data, j, right); } return; diff --git a/src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs b/src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs index 170653c143..578616706f 100644 --- a/src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs +++ b/src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs @@ -162,7 +162,25 @@ namespace ImageSharp.Drawing.Shapes /// public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset) { - throw new NotImplementedException(); + var 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) diff --git a/src/ImageSharp/ImageProcessor.cs b/src/ImageSharp/ImageProcessor.cs index 79bc3ee1e6..0697bf4ba5 100644 --- a/src/ImageSharp/ImageProcessor.cs +++ b/src/ImageSharp/ImageProcessor.cs @@ -39,7 +39,11 @@ namespace ImageSharp.Processing } catch (Exception ex) { +#if DEBUG + throw; +#else throw new ImageProcessingException($"An error occured when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); +#endif } } diff --git a/tests/ImageSharp.Benchmarks/Drawing/FillPolygonStatagies.cs b/tests/ImageSharp.Benchmarks/Drawing/FillPolygonStatagies.cs index 5018ed8f12..a6f932439a 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/FillPolygonStatagies.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/FillPolygonStatagies.cs @@ -18,7 +18,7 @@ namespace ImageSharp.Benchmarks public class FillPolygonStatagies : BenchmarkBase { - [Benchmark(Baseline = true, Description = "Simple Fill Polygon")] + [Benchmark(Baseline = true, Description = "Simple Fill Polygon - antialias")] public void DrawSolidPolygonSimple() { CoreImage image = new CoreImage(800, 800); @@ -32,18 +32,46 @@ namespace ImageSharp.Benchmarks image.Apply(new FillShapeProcessor(brush, shape, Drawing.GraphicsOptions.Default)); } - [Benchmark(Description = "Fast Fill Polygon")] + [Benchmark(Description = "Fast Fill Polygon - antialias")] public void DrawSolidPolygonFast() { CoreImage image = new CoreImage(800, 800); var brush = Drawing.Brushes.Brushes.Solid(CoreColor.HotPink); - var shape = new Drawing.Shapes.LinearPolygon(new[] { + 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)); + } + + [Benchmark(Description = "Simple Fill Polygon - antialias disabled")] + public void DrawSolidPolygonSimpleDisableAntiAliase() + { + 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, new Drawing.GraphicsOptions(false))); + } + + [Benchmark(Description = "Fast Fill Polygon - antialias disabledp")] + public void DrawSolidPolygonFasteDisableAntiAliase() + { + 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)); + image.Apply(new FillShapeProcessorFast(brush, shape, new Drawing.GraphicsOptions(false))); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs index f6bcf49065..43ed13b3c2 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs @@ -19,6 +19,7 @@ namespace ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByFilledPolygon() { + Configuration.Default.ParallelOptions.MaxDegreeOfParallelism = 1; string path = CreateOutputDirectory("Drawing", "FilledBezier"); var simplePath = new[] { new Vector2(10, 400), 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); } From d07e0885dbbccd5c09f46f3462b850098f0a6750 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Wed, 4 Jan 2017 14:19:10 +0000 Subject: [PATCH 03/10] use abstract base class instead of interface --- src/ImageSharp.Drawing/Brushes/IBrush.cs | 4 ++-- .../Brushes/ImageBrush{TColor}.cs | 8 ++++---- .../Brushes/PatternBrush{TColor}.cs | 8 ++++---- .../{IBrushApplicator.cs => BrushApplicator.cs} | 11 ++++++++--- .../Brushes/RecolorBrush{TColor}.cs | 9 ++++----- .../Brushes/SolidBrush{TColor}.cs | 8 ++++---- src/ImageSharp.Drawing/Pens/Pen{TColor}.cs | 16 ++++++++-------- .../Pens/Processors/IPenApplicator.cs | 11 ++++++++--- .../Processors/FillProcessor.cs | 2 +- .../Processors/FillShapeProcessor.cs | 2 +- .../Processors/FillShapeProcessorFast.cs | 2 +- 11 files changed, 45 insertions(+), 36 deletions(-) rename src/ImageSharp.Drawing/Brushes/Processors/{IBrushApplicator.cs => BrushApplicator.cs} (61%) 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/Pens/Pen{TColor}.cs b/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs index 0af039ac77..ebe027374b 100644 --- a/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs +++ b/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs @@ -125,7 +125,7 @@ namespace ImageSharp.Drawing.Pens private class SolidPenApplicator : IPenApplicator { - 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); @@ -166,7 +166,7 @@ namespace ImageSharp.Drawing.Pens private class PatternPenApplicator : IPenApplicator { - 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/IPenApplicator.cs index 7159dfeec3..adc7a28d78 100644 --- a/src/ImageSharp.Drawing/Pens/Processors/IPenApplicator.cs +++ b/src/ImageSharp.Drawing/Pens/Processors/IPenApplicator.cs @@ -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 IPenApplicator : 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/FillProcessor.cs b/src/ImageSharp.Drawing/Processors/FillProcessor.cs index 309d3670f5..9219f9f999 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, diff --git a/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs b/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs index d0655341b7..489d27423e 100644 --- a/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs @@ -73,7 +73,7 @@ namespace ImageSharp.Drawing.Processors } using (PixelAccessor sourcePixels = source.Lock()) - using (IBrushApplicator applicator = this.fillColor.CreateApplicator(sourcePixels, rect)) + using (BrushApplicator applicator = this.fillColor.CreateApplicator(sourcePixels, rect)) { Parallel.For( minY, diff --git a/src/ImageSharp.Drawing/Processors/FillShapeProcessorFast.cs b/src/ImageSharp.Drawing/Processors/FillShapeProcessorFast.cs index d192dc6c3a..9fae2ca8db 100644 --- a/src/ImageSharp.Drawing/Processors/FillShapeProcessorFast.cs +++ b/src/ImageSharp.Drawing/Processors/FillShapeProcessorFast.cs @@ -67,7 +67,7 @@ namespace ImageSharp.Drawing.Processors 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, From 353440b1458fdd1d73c1bdcbb62a6af8d6a59496 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Wed, 4 Jan 2017 14:21:15 +0000 Subject: [PATCH 04/10] fix class name --- src/ImageSharp.Drawing/Pens/IPen.cs | 2 +- src/ImageSharp.Drawing/Pens/Pen{TColor}.cs | 6 +++--- .../Pens/Processors/{IPenApplicator.cs => PenApplicator.cs} | 4 ++-- src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) rename src/ImageSharp.Drawing/Pens/Processors/{IPenApplicator.cs => PenApplicator.cs} (90%) 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 ebe027374b..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,7 +123,7 @@ 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 BrushApplicator brush; private readonly float halfWidth; @@ -164,7 +164,7 @@ namespace ImageSharp.Drawing.Pens } } - private class PatternPenApplicator : IPenApplicator + private class PatternPenApplicator : PenApplicator { private readonly BrushApplicator brush; private readonly float halfWidth; diff --git a/src/ImageSharp.Drawing/Pens/Processors/IPenApplicator.cs b/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs similarity index 90% rename from src/ImageSharp.Drawing/Pens/Processors/IPenApplicator.cs rename to src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs index adc7a28d78..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 abstract class IPenApplicator : IDisposable + public abstract class PenApplicator : IDisposable where TColor : struct, IPackedPixel, IEquatable { /// 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); From 4dc25ee2727d40978a4b3c1733cee0cc780afc8e Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Wed, 4 Jan 2017 15:10:38 +0000 Subject: [PATCH 05/10] drop old code --- src/ImageSharp.Drawing/Fill.cs | 4 +- src/ImageSharp.Drawing/FillRectangle.cs | 4 +- .../Processors/FillShapeProcessor.cs | 383 +++++++++++++-- .../Processors/FillShapeProcessorFast.cs | 452 ------------------ .../Drawing/FillPolygonStatagies.cs | 77 --- 5 files changed, 353 insertions(+), 567 deletions(-) delete mode 100644 src/ImageSharp.Drawing/Processors/FillShapeProcessorFast.cs delete mode 100644 tests/ImageSharp.Benchmarks/Drawing/FillPolygonStatagies.cs diff --git a/src/ImageSharp.Drawing/Fill.cs b/src/ImageSharp.Drawing/Fill.cs index 1e14d8b11c..c0f43bdd18 100644 --- a/src/ImageSharp.Drawing/Fill.cs +++ b/src/ImageSharp.Drawing/Fill.cs @@ -56,7 +56,7 @@ namespace ImageSharp public static Image Fill(this Image source, IBrush brush, IShape shape, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(new FillShapeProcessorFast(brush, shape, options)); + return source.Apply(new FillShapeProcessor(brush, shape, options)); } /// @@ -70,7 +70,7 @@ namespace ImageSharp public static Image Fill(this Image source, IBrush brush, IShape shape) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(new FillShapeProcessorFast(brush, shape, GraphicsOptions.Default)); + return source.Apply(new FillShapeProcessor(brush, shape, GraphicsOptions.Default)); } /// diff --git a/src/ImageSharp.Drawing/FillRectangle.cs b/src/ImageSharp.Drawing/FillRectangle.cs index 322133a4d5..d29b58e1b8 100644 --- a/src/ImageSharp.Drawing/FillRectangle.cs +++ b/src/ImageSharp.Drawing/FillRectangle.cs @@ -31,7 +31,7 @@ namespace ImageSharp public static Image Fill(this Image source, IBrush brush, RectangleF shape, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(new FillShapeProcessorFast(brush, new RectangularPolygon(shape), options)); + return source.Apply(new FillShapeProcessor(brush, new RectangularPolygon(shape), options)); } /// @@ -45,7 +45,7 @@ namespace ImageSharp public static Image Fill(this Image source, IBrush brush, RectangleF shape) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(new FillShapeProcessorFast(brush, new RectangularPolygon(shape), GraphicsOptions.Default)); + return source.Apply(new FillShapeProcessor(brush, new RectangularPolygon(shape), GraphicsOptions.Default)); } /// diff --git a/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs b/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs index 489d27423e..867ca7c90c 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,16 +62,9 @@ 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 (BrushApplicator applicator = this.fillColor.CreateApplicator(sourcePixels, rect)) @@ -81,21 +75,122 @@ namespace ImageSharp.Drawing.Processors this.ParallelOptions, y => { - int offsetY = y - polyStartY; + var buffer = arrayPool.Rent(maxIntersections); + var left = new Vector2(startX, y); + var right = new Vector2(endX, y); + + // 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; + } + + QuickSortX(buffer, 0, pointsFound); + + int currentIntersection = 0; + float nextPoint = buffer[0].X; + float lastPoint = float.MinValue; + 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; - float dist = this.poly.Distance(currentPoint); - float opacity = this.Opacity(dist); + 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; + + // 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 (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[offsetX, offsetY].ToVector4(); + Vector4 backgroundVector = sourcePixels[x, y].ToVector4(); Vector4 sourceVector = applicator.GetColor(currentPoint).ToVector4(); Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); @@ -103,35 +198,255 @@ namespace ImageSharp.Drawing.Processors TColor packed = default(TColor); packed.PackFromVector4(finalColor); - sourcePixels[offsetX, offsetY] = packed; + sourcePixels[x, y] = packed; } } + + 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 => + { + var buffer = arrayPool.Rent(maxIntersections); + var left = new Vector2(x, polyStartY); + var right = new Vector2(x, polyEndY); + + // 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; + } + + QuickSortY(buffer, 0, pointsFound); + + int currentIntersection = 0; + float nextPoint = buffer[0].Y; + float lastPoint = left.Y; + float targetPoint = nextPoint; + 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; + } + } + + arrayPool.Return(buffer); + }); + } } } - /// - /// Returns the correct alpha value for the given distance. - /// - /// - /// The distance. - /// - /// - /// The . - /// - private float Opacity(float distance) + private static void QuickSortX(Vector2[] data, int left, int right) { - if (distance <= 0) + int i = left - 1; + int j = right; + + while (true) { - return 1; + float x = data[left].X; + do + { + i++; + } + while (data[i].X < x); + + do + { + j--; + } + while (data[j].X > x); + + if (i < j) + { + Vector2 tmp = data[i]; + data[i] = data[j]; + data[j] = tmp; + } + else + { + if (left < j) + { + QuickSortX(data, left, j); + } + + if (++j < right) + { + QuickSortX(data, j, right); + } + + return; + } } + } + + private static void QuickSortY(Vector2[] data, int left, int right) + { + int i = left - 1; + int j = right; - if (this.options.Antialias && distance < AntialiasFactor) + while (true) { - return 1 - (distance / AntialiasFactor); - } + float d = data[left].Y; + do + { + i++; + } + while (data[i].Y < d); + + do + { + j--; + } + while (data[j].Y > d); - return 0; + if (i < j) + { + Vector2 tmp = data[i]; + data[i] = data[j]; + data[j] = tmp; + } + else + { + if (left < j) + { + QuickSortY(data, left, j); + } + + if (++j < right) + { + QuickSortY(data, j, right); + } + + return; + } + } } } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processors/FillShapeProcessorFast.cs b/src/ImageSharp.Drawing/Processors/FillShapeProcessorFast.cs deleted file mode 100644 index 9fae2ca8db..0000000000 --- a/src/ImageSharp.Drawing/Processors/FillShapeProcessorFast.cs +++ /dev/null @@ -1,452 +0,0 @@ -// -// 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); - - ArrayPool arrayPool = ArrayPool.Shared; - - int maxIntersections = this.poly.MaxIntersections; - - using (PixelAccessor sourcePixels = source.Lock()) - using (BrushApplicator applicator = this.fillColor.CreateApplicator(sourcePixels, rect)) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - var buffer = arrayPool.Rent(maxIntersections); - var left = new Vector2(startX, y); - var right = new Vector2(endX, y); - - // 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; - } - - QuickSortX(buffer, 0, pointsFound); - - int currentIntersection = 0; - float nextPoint = buffer[0].X; - float lastPoint = float.MinValue; - 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++) - { - 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; - - // 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 (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; - } - } - - 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 => - { - var buffer = arrayPool.Rent(maxIntersections); - var left = new Vector2(x, polyStartY); - var right = new Vector2(x, polyEndY); - - // 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; - } - - QuickSortY(buffer, 0, pointsFound); - - int currentIntersection = 0; - float nextPoint = buffer[0].Y; - float lastPoint = left.Y; - float targetPoint = nextPoint; - 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; - } - } - - arrayPool.Return(buffer); - }); - } - } - } - - private static void QuickSortX(Vector2[] data, int left, int right) - { - int i = left - 1; - int j = right; - - while (true) - { - float x = data[left].X; - do - { - i++; - } - while (data[i].X < x); - - do - { - j--; - } - while (data[j].X > x); - - if (i < j) - { - Vector2 tmp = data[i]; - data[i] = data[j]; - data[j] = tmp; - } - else - { - if (left < j) - { - QuickSortX(data, left, j); - } - - if (++j < right) - { - QuickSortX(data, j, right); - } - - return; - } - } - } - - private static void QuickSortY(Vector2[] data, int left, int right) - { - int i = left - 1; - int j = right; - - while (true) - { - float d = data[left].Y; - do - { - i++; - } - while (data[i].Y < d); - - do - { - j--; - } - while (data[j].Y > d); - - if (i < j) - { - Vector2 tmp = data[i]; - data[i] = data[j]; - data[j] = tmp; - } - else - { - if (left < j) - { - QuickSortY(data, left, j); - } - - if (++j < right) - { - QuickSortY(data, j, right); - } - - return; - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Drawing/FillPolygonStatagies.cs b/tests/ImageSharp.Benchmarks/Drawing/FillPolygonStatagies.cs deleted file mode 100644 index a6f932439a..0000000000 --- a/tests/ImageSharp.Benchmarks/Drawing/FillPolygonStatagies.cs +++ /dev/null @@ -1,77 +0,0 @@ -// -// 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 - antialias")] - 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 - antialias")] - 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)); - } - - [Benchmark(Description = "Simple Fill Polygon - antialias disabled")] - public void DrawSolidPolygonSimpleDisableAntiAliase() - { - 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, new Drawing.GraphicsOptions(false))); - } - - [Benchmark(Description = "Fast Fill Polygon - antialias disabledp")] - public void DrawSolidPolygonFasteDisableAntiAliase() - { - 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, new Drawing.GraphicsOptions(false))); - } - } -} \ No newline at end of file From e443a60bebe0728c691e5828bf56fe60737fbce1 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Wed, 4 Jan 2017 16:13:53 +0000 Subject: [PATCH 06/10] Fix complex polygon fill --- .../Processors/FillShapeProcessor.cs | 117 ++++++++++-------- .../Shapes/ComplexPolygon.cs | 11 +- .../Drawing/SolidBezierTests.cs | 1 - .../Drawing/SolidComplexPolygonTests.cs | 6 + 4 files changed, 80 insertions(+), 55 deletions(-) diff --git a/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs b/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs index 867ca7c90c..b742682a11 100644 --- a/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs @@ -78,6 +78,10 @@ namespace ImageSharp.Drawing.Processors var buffer = arrayPool.Rent(maxIntersections); var left = new Vector2(startX, y); var right = new Vector2(endX, y); + if (y == 100) + { + var sdf = ""; + } // foreach line we get all the points where this line crosses the polygon var pointsFound = this.poly.FindIntersections(left, right, buffer, maxIntersections, 0); @@ -89,7 +93,7 @@ namespace ImageSharp.Drawing.Processors return; } - QuickSortX(buffer, 0, pointsFound); + QuickSortX(buffer, pointsFound); int currentIntersection = 0; float nextPoint = buffer[0].X; @@ -228,7 +232,7 @@ namespace ImageSharp.Drawing.Processors return; } - QuickSortY(buffer, 0, pointsFound); + QuickSortY(buffer, pointsFound); int currentIntersection = 0; float nextPoint = buffer[0].Y; @@ -363,89 +367,96 @@ namespace ImageSharp.Drawing.Processors } } - private static void QuickSortX(Vector2[] data, int left, int right) + 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[] A, int size) + { + QuickSortY(A, 0, size - 1); + } + + private static void QuickSortY(Vector2[] A, int lo, int hi) + { + if (lo < hi) + { + int p = PartitionY(A, lo, hi); + QuickSortY(A, lo, p); + QuickSortY(A, p + 1, hi); + } + } + + private static void QuickSortX(Vector2[] A, int size) { - int i = left - 1; - int j = right; + QuickSortX(A, 0, size - 1); + } + private static void QuickSortX(Vector2[] A, int lo, int hi) + { + if (lo < hi) + { + int p = PartitionX(A, lo, hi); + QuickSortX(A, lo, p); + QuickSortX(A, p + 1, hi); + } + } + + private static int PartitionX(Vector2[] A, int lo, int hi) + { + float pivot = A[lo].X; + int i = lo - 1; + int j = hi + 1; while (true) { - float x = data[left].X; do { - i++; + i = i + 1; } - while (data[i].X < x); + while (A[i].X < pivot); do { - j--; + j = j - 1; } - while (data[j].X > x); + while (A[j].X > pivot); - if (i < j) + if (i >= j) { - Vector2 tmp = data[i]; - data[i] = data[j]; - data[j] = tmp; + return j; } - else - { - if (left < j) - { - QuickSortX(data, left, j); - } - if (++j < right) - { - QuickSortX(data, j, right); - } - - return; - } + Swap(A, i, j); } } - private static void QuickSortY(Vector2[] data, int left, int right) + private static int PartitionY(Vector2[] A, int lo, int hi) { - int i = left - 1; - int j = right; - + float pivot = A[lo].Y; + int i = lo - 1; + int j = hi + 1; while (true) { - float d = data[left].Y; do { - i++; + i = i + 1; } - while (data[i].Y < d); + while (A[i].Y < pivot); do { - j--; + j = j - 1; } - while (data[j].Y > d); + while (A[j].Y > pivot); - if (i < j) + if (i >= j) { - Vector2 tmp = data[i]; - data[i] = data[j]; - data[j] = tmp; + return j; } - else - { - if (left < j) - { - QuickSortY(data, left, j); - } - if (++j < right) - { - QuickSortY(data, j, right); - } - - return; - } + Swap(A, i, j); } } } diff --git a/src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs b/src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs index 32de68fb9f..98ce846082 100644 --- a/src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs +++ b/src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs @@ -125,7 +125,16 @@ namespace ImageSharp.Drawing.Shapes /// public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset) { - throw new NotImplementedException(); + 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; } /// diff --git a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs index 43ed13b3c2..f6bcf49065 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs @@ -19,7 +19,6 @@ namespace ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByFilledPolygon() { - Configuration.Default.ParallelOptions.MaxDegreeOfParallelism = 1; string path = CreateOutputDirectory("Drawing", "FilledBezier"); var simplePath = new[] { new Vector2(10, 400), 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 From 698785aa7c6fb5852bbbbcd2e82b8e7f1cbffd19 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Wed, 4 Jan 2017 16:27:35 +0000 Subject: [PATCH 07/10] drop test code --- src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs b/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs index b742682a11..3d574b8557 100644 --- a/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs @@ -78,10 +78,6 @@ namespace ImageSharp.Drawing.Processors var buffer = arrayPool.Rent(maxIntersections); var left = new Vector2(startX, y); var right = new Vector2(endX, y); - if (y == 100) - { - var sdf = ""; - } // foreach line we get all the points where this line crosses the polygon var pointsFound = this.poly.FindIntersections(left, right, buffer, maxIntersections, 0); From d6b4315b4d012fca4ee7b5b794dc2e716b5f2a19 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Wed, 4 Jan 2017 16:44:24 +0000 Subject: [PATCH 08/10] style :cop: --- .../Processors/FillShapeProcessor.cs | 44 +++++++++---------- src/ImageSharp/ImageProcessor.cs | 4 -- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs b/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs index 3d574b8557..d68042ea46 100644 --- a/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs @@ -370,39 +370,39 @@ namespace ImageSharp.Drawing.Processors data[right] = tmp; } - private static void QuickSortY(Vector2[] A, int size) + private static void QuickSortY(Vector2[] data, int size) { - QuickSortY(A, 0, size - 1); + QuickSortY(data, 0, size - 1); } - private static void QuickSortY(Vector2[] A, int lo, int hi) + private static void QuickSortY(Vector2[] data, int lo, int hi) { if (lo < hi) { - int p = PartitionY(A, lo, hi); - QuickSortY(A, lo, p); - QuickSortY(A, p + 1, hi); + int p = PartitionY(data, lo, hi); + QuickSortY(data, lo, p); + QuickSortY(data, p + 1, hi); } } - private static void QuickSortX(Vector2[] A, int size) + private static void QuickSortX(Vector2[] data, int size) { - QuickSortX(A, 0, size - 1); + QuickSortX(data, 0, size - 1); } - private static void QuickSortX(Vector2[] A, int lo, int hi) + private static void QuickSortX(Vector2[] data, int lo, int hi) { if (lo < hi) { - int p = PartitionX(A, lo, hi); - QuickSortX(A, lo, p); - QuickSortX(A, p + 1, hi); + int p = PartitionX(data, lo, hi); + QuickSortX(data, lo, p); + QuickSortX(data, p + 1, hi); } } - private static int PartitionX(Vector2[] A, int lo, int hi) + private static int PartitionX(Vector2[] data, int lo, int hi) { - float pivot = A[lo].X; + float pivot = data[lo].X; int i = lo - 1; int j = hi + 1; while (true) @@ -411,26 +411,26 @@ namespace ImageSharp.Drawing.Processors { i = i + 1; } - while (A[i].X < pivot); + while (data[i].X < pivot); do { j = j - 1; } - while (A[j].X > pivot); + while (data[j].X > pivot); if (i >= j) { return j; } - Swap(A, i, j); + Swap(data, i, j); } } - private static int PartitionY(Vector2[] A, int lo, int hi) + private static int PartitionY(Vector2[] data, int lo, int hi) { - float pivot = A[lo].Y; + float pivot = data[lo].Y; int i = lo - 1; int j = hi + 1; while (true) @@ -439,20 +439,20 @@ namespace ImageSharp.Drawing.Processors { i = i + 1; } - while (A[i].Y < pivot); + while (data[i].Y < pivot); do { j = j - 1; } - while (A[j].Y > pivot); + while (data[j].Y > pivot); if (i >= j) { return j; } - Swap(A, i, j); + Swap(data, i, j); } } } diff --git a/src/ImageSharp/ImageProcessor.cs b/src/ImageSharp/ImageProcessor.cs index 0697bf4ba5..79bc3ee1e6 100644 --- a/src/ImageSharp/ImageProcessor.cs +++ b/src/ImageSharp/ImageProcessor.cs @@ -39,11 +39,7 @@ namespace ImageSharp.Processing } catch (Exception ex) { -#if DEBUG - throw; -#else throw new ImageProcessingException($"An error occured when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); -#endif } } From 086115fd7813075ab8bbcde813f23667a6243a37 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Thu, 5 Jan 2017 08:35:17 +0000 Subject: [PATCH 09/10] code cleanup and comments --- src/ImageSharp.Drawing/Paths/InternalPath.cs | 72 ++-- .../Processors/FillProcessor.cs | 2 +- .../Processors/FillShapeProcessor.cs | 368 +++++++++--------- .../Shapes/BezierPolygon.cs | 3 +- .../Shapes/ComplexPolygon.cs | 28 +- src/ImageSharp.Drawing/Shapes/IShape.cs | 3 +- .../Shapes/LinearPolygon.cs | 3 +- src/ImageSharp.Drawing/Shapes/Polygon.cs | 5 +- .../Shapes/RectangularPolygon.cs | 8 +- 9 files changed, 266 insertions(+), 226 deletions(-) diff --git a/src/ImageSharp.Drawing/Paths/InternalPath.cs b/src/ImageSharp.Drawing/Paths/InternalPath.cs index aab71937de..7bb9134e70 100644 --- a/src/ImageSharp.Drawing/Paths/InternalPath.cs +++ b/src/ImageSharp.Drawing/Paths/InternalPath.cs @@ -169,7 +169,8 @@ namespace ImageSharp.Drawing.Paths } /// - /// Finds the intersections. + /// Based on a line described by and + /// populate a buffer for all points on the path that the line intersects. /// /// The start. /// The end. @@ -195,7 +196,7 @@ namespace ImageSharp.Drawing.Paths next = 0; } - var point = FindIntersection(this.points[i], this.points[next], start, end); + Vector2 point = FindIntersection(this.points[i], this.points[next], start, end); if (point != MaxVector) { buffer[position + offset] = point; @@ -208,7 +209,7 @@ namespace ImageSharp.Drawing.Paths } /// - /// Points the in polygon. + /// Determines if the specified point is inside or outside the path. /// /// The point. /// Returns true if the point is inside the closed path. @@ -247,47 +248,67 @@ 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. + /// private static bool BoundingBoxesIntersect(Vector2 line1Start, Vector2 line1End, Vector2 line2Start, Vector2 line2End) { - var topLeft1 = Vector2.Min(line1Start, line1End); - var bottomRight1 = Vector2.Max(line1Start, line1End); + Vector2 topLeft1 = Vector2.Min(line1Start, line1End); + Vector2 bottomRight1 = Vector2.Max(line1Start, line1End); - var topLeft2 = Vector2.Min(line2Start, line2End); - var bottomRight2 = Vector2.Max(line2Start, line2End); + Vector2 topLeft2 = Vector2.Min(line2Start, line2End); + Vector2 bottomRight2 = Vector2.Max(line2Start, line2End); - var left1 = topLeft1.X; - var right1 = bottomRight1.X; - var top1 = topLeft1.Y; - var bottom1 = bottomRight1.Y; + float left1 = topLeft1.X; + float right1 = bottomRight1.X; + float top1 = topLeft1.Y; + float bottom1 = bottomRight1.Y; - var left2 = topLeft2.X; - var right2 = bottomRight2.X; - var top2 = topLeft2.Y; - var bottom2 = bottomRight2.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 lines cross at all + // do bounding boxes overlap, if not then the lines can't and return fast. if (!BoundingBoxesIntersect(line1Start, line1End, line2Start, line2End)) { return MaxVector; } - var line1Diff = line1End - line1Start; - var line2Diff = line2End - line2Start; + Vector2 line1Diff = line1End - line1Start; + Vector2 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; + float yinter = line2Start.Y - (slope * line2Start.X); + float y = (line1Start.X * slope) + yinter; point = new Vector2(line1Start.X, y); // horizontal and vertical lines @@ -295,8 +316,8 @@ namespace ImageSharp.Drawing.Paths else if (line2Diff.X == 0) { float slope = line1Diff.Y / line1Diff.X; - var yinter = line1Start.Y - (slope * line1Start.X); - var y = (line2Start.X * slope) + yinter; + float yinter = line1Start.Y - (slope * line1Start.X); + float y = (line2Start.X * slope) + yinter; point = new Vector2(line2Start.X, y); // horizontal and vertical lines @@ -306,8 +327,8 @@ namespace ImageSharp.Drawing.Paths 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); + float yinter1 = line1Start.Y - (slope1 * line1Start.X); + float yinter2 = line2Start.Y - (slope2 * line2Start.X); if (slope1 == slope2 && yinter1 != yinter2) { @@ -315,7 +336,6 @@ namespace ImageSharp.Drawing.Paths } float x = (yinter2 - yinter1) / (slope1 - slope2); - float y = (slope1 * x) + yinter1; point = new Vector2(x, y); diff --git a/src/ImageSharp.Drawing/Processors/FillProcessor.cs b/src/ImageSharp.Drawing/Processors/FillProcessor.cs index 9219f9f999..dc87e6da6c 100644 --- a/src/ImageSharp.Drawing/Processors/FillProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillProcessor.cs @@ -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 d68042ea46..d7469b7529 100644 --- a/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs @@ -75,151 +75,15 @@ namespace ImageSharp.Drawing.Processors this.ParallelOptions, y => { - var buffer = arrayPool.Rent(maxIntersections); - var left = new Vector2(startX, y); - var right = new Vector2(endX, y); + Vector2[] buffer = arrayPool.Rent(maxIntersections); - // 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) + try { - arrayPool.Return(buffer); - - // nothign on this line skip - return; - } - - QuickSortX(buffer, pointsFound); - - int currentIntersection = 0; - float nextPoint = buffer[0].X; - float lastPoint = float.MinValue; - 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++) - { - 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; - - // 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 (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; - } - } - - 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 => - { - var buffer = arrayPool.Rent(maxIntersections); - var left = new Vector2(x, polyStartY); - var right = new Vector2(x, polyEndY); + 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 - var pointsFound = this.poly.FindIntersections(left, right, buffer, maxIntersections, 0); + int pointsFound = this.poly.FindIntersections(left, right, buffer, maxIntersections, 0); if (pointsFound == 0) { arrayPool.Return(buffer); @@ -228,65 +92,50 @@ namespace ImageSharp.Drawing.Processors return; } - QuickSortY(buffer, pointsFound); + QuickSortX(buffer, pointsFound); int currentIntersection = 0; - float nextPoint = buffer[0].Y; - float lastPoint = left.Y; - float targetPoint = nextPoint; + 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 y = minY; y < maxY; y++) + for (int x = minX; x < maxX; x++) { 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 (x < (nextPoint - DrawPadding) && x > (lastPoint + DrawPadding)) { - if (nextPoint == right.Y) + if (nextPoint == right.X) { // we are in the ends run skip it - y = maxY; + x = maxX; continue; } // lets just jump forward - y = (int)Math.Floor(nextPoint); + x = (int)Math.Floor(nextPoint) - DrawPadding; } } bool onCorner = false; - if (y >= nextPoint) + // there seems to be some issue with this switch. + if (x >= nextPoint) { currentIntersection++; lastPoint = nextPoint; if (currentIntersection == pointsFound) { - nextPoint = right.Y; + nextPoint = right.X; } else { - nextPoint = buffer[currentIntersection].Y; + nextPoint = buffer[currentIntersection].X; // double point from a corner flip the bit back and move on again if (nextPoint == lastPoint) @@ -296,11 +145,11 @@ namespace ImageSharp.Drawing.Processors currentIntersection++; if (currentIntersection == pointsFound) { - nextPoint = right.Y; + nextPoint = right.X; } else { - nextPoint = buffer[currentIntersection].Y; + nextPoint = buffer[currentIntersection].X; } } } @@ -314,20 +163,20 @@ namespace ImageSharp.Drawing.Processors if (this.options.Antialias) { float distance = float.MaxValue; - if (y == lastPoint || y == nextPoint) + if (x == lastPoint || x == nextPoint) { // we are to far away from the line distance = 0; } - else if (nextPoint - AntialiasFactor < y) + else if (nextPoint - AntialiasFactor < x) { // we are near the left of the line - distance = nextPoint - y; + distance = nextPoint - x; } - else if (lastPoint + AntialiasFactor > y) + else if (lastPoint + AntialiasFactor > x) { // we are near the right of the line - distance = y - lastPoint; + distance = x - lastPoint; } else { @@ -342,8 +191,7 @@ namespace ImageSharp.Drawing.Processors } } - // don't set full opactiy color as it will have been gotten by the first scan - if (opacity > Constants.Epsilon && opacity < 1) + if (opacity > Constants.Epsilon) { Vector4 backgroundVector = sourcePixels[x, y].ToVector4(); Vector4 sourceVector = applicator.GetColor(currentPoint).ToVector4(); @@ -356,8 +204,172 @@ namespace ImageSharp.Drawing.Processors 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); + } }); } } diff --git a/src/ImageSharp.Drawing/Shapes/BezierPolygon.cs b/src/ImageSharp.Drawing/Shapes/BezierPolygon.cs index 1f32814cb6..33b84e2d9c 100644 --- a/src/ImageSharp.Drawing/Shapes/BezierPolygon.cs +++ b/src/ImageSharp.Drawing/Shapes/BezierPolygon.cs @@ -52,7 +52,8 @@ namespace ImageSharp.Drawing.Shapes public float Distance(Vector2 point) => this.innerPolygon.Distance(point); /// - /// Finds the intersections. + /// 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. diff --git a/src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs b/src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs index 98ce846082..2e528c76ac 100644 --- a/src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs +++ b/src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs @@ -47,10 +47,10 @@ namespace ImageSharp.Drawing.Shapes 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); } @@ -89,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) { @@ -113,7 +113,9 @@ namespace ImageSharp.Drawing.Shapes } /// - /// Finds the intersections. + /// 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. @@ -170,7 +172,7 @@ namespace ImageSharp.Drawing.Shapes } else { - foreach (var path in shape) + foreach (IPath path in shape) { clipper.AddPath( path, @@ -181,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); } @@ -201,14 +203,14 @@ 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); } @@ -216,13 +218,13 @@ namespace ImageSharp.Drawing.Shapes 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(); @@ -233,7 +235,7 @@ namespace ImageSharp.Drawing.Shapes this.paths = paths.ToArray(); int intersections = 0; - foreach (var s in this.shapes) + foreach (IShape s in this.shapes) { intersections += s.MaxIntersections; } diff --git a/src/ImageSharp.Drawing/Shapes/IShape.cs b/src/ImageSharp.Drawing/Shapes/IShape.cs index 5c9afc1f99..603eab4208 100644 --- a/src/ImageSharp.Drawing/Shapes/IShape.cs +++ b/src/ImageSharp.Drawing/Shapes/IShape.cs @@ -40,7 +40,8 @@ namespace ImageSharp.Drawing.Shapes float Distance(Vector2 point); /// - /// Finds the intersections. + /// 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. diff --git a/src/ImageSharp.Drawing/Shapes/LinearPolygon.cs b/src/ImageSharp.Drawing/Shapes/LinearPolygon.cs index d590cbabff..fb851f30a6 100644 --- a/src/ImageSharp.Drawing/Shapes/LinearPolygon.cs +++ b/src/ImageSharp.Drawing/Shapes/LinearPolygon.cs @@ -58,7 +58,8 @@ namespace ImageSharp.Drawing.Shapes public float Distance(Vector2 point) => this.innerPolygon.Distance(point); /// - /// Finds the intersections. + /// 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. diff --git a/src/ImageSharp.Drawing/Shapes/Polygon.cs b/src/ImageSharp.Drawing/Shapes/Polygon.cs index 9fcb4a6a10..86c3c9ee43 100644 --- a/src/ImageSharp.Drawing/Shapes/Polygon.cs +++ b/src/ImageSharp.Drawing/Shapes/Polygon.cs @@ -82,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; @@ -137,7 +137,8 @@ namespace ImageSharp.Drawing.Shapes } /// - /// Finds the intersections. + /// 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. diff --git a/src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs b/src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs index 578616706f..6be73dda8d 100644 --- a/src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs +++ b/src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs @@ -110,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; @@ -150,7 +150,9 @@ namespace ImageSharp.Drawing.Shapes } /// - /// Finds the intersections. + /// 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. @@ -162,7 +164,7 @@ namespace ImageSharp.Drawing.Shapes /// public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset) { - var discovered = 0; + int discovered = 0; Vector2 startPoint = Vector2.Clamp(start, this.topLeft, this.bottomRight); Vector2 endPoint = Vector2.Clamp(end, this.topLeft, this.bottomRight); From 83f1f796ceccd8740337e023c03a8bad8de01ebb Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Thu, 5 Jan 2017 11:21:15 +0000 Subject: [PATCH 10/10] style :cop: --- src/ImageSharp.Drawing/Paths/InternalPath.cs | 2 +- src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs | 1 - src/ImageSharp.Drawing/Shapes/BezierPolygon.cs | 2 +- src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs | 2 +- src/ImageSharp.Drawing/Shapes/IShape.cs | 2 +- src/ImageSharp.Drawing/Shapes/LinearPolygon.cs | 2 +- src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs | 4 ++-- 7 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp.Drawing/Paths/InternalPath.cs b/src/ImageSharp.Drawing/Paths/InternalPath.cs index 7bb9134e70..36a0704cbe 100644 --- a/src/ImageSharp.Drawing/Paths/InternalPath.cs +++ b/src/ImageSharp.Drawing/Paths/InternalPath.cs @@ -257,7 +257,7 @@ namespace ImageSharp.Drawing.Paths /// 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); diff --git a/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs b/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs index d7469b7529..36f89f8ec1 100644 --- a/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs @@ -211,7 +211,6 @@ namespace ImageSharp.Drawing.Processors } }); - if (this.options.Antialias) { // we only need to do the X can for antialiasing purposes diff --git a/src/ImageSharp.Drawing/Shapes/BezierPolygon.cs b/src/ImageSharp.Drawing/Shapes/BezierPolygon.cs index 33b84e2d9c..b69ded2075 100644 --- a/src/ImageSharp.Drawing/Shapes/BezierPolygon.cs +++ b/src/ImageSharp.Drawing/Shapes/BezierPolygon.cs @@ -52,7 +52,7 @@ namespace ImageSharp.Drawing.Shapes public float Distance(Vector2 point) => this.innerPolygon.Distance(point); /// - /// Based on a line described by and + /// 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. diff --git a/src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs b/src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs index 2e528c76ac..fd709719c1 100644 --- a/src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs +++ b/src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs @@ -113,7 +113,7 @@ namespace ImageSharp.Drawing.Shapes } /// - /// Based on a line described by and + /// 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. /// diff --git a/src/ImageSharp.Drawing/Shapes/IShape.cs b/src/ImageSharp.Drawing/Shapes/IShape.cs index 603eab4208..242e3bd8ec 100644 --- a/src/ImageSharp.Drawing/Shapes/IShape.cs +++ b/src/ImageSharp.Drawing/Shapes/IShape.cs @@ -40,7 +40,7 @@ namespace ImageSharp.Drawing.Shapes float Distance(Vector2 point); /// - /// Based on a line described by and + /// 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. diff --git a/src/ImageSharp.Drawing/Shapes/LinearPolygon.cs b/src/ImageSharp.Drawing/Shapes/LinearPolygon.cs index fb851f30a6..30a30c20f9 100644 --- a/src/ImageSharp.Drawing/Shapes/LinearPolygon.cs +++ b/src/ImageSharp.Drawing/Shapes/LinearPolygon.cs @@ -58,7 +58,7 @@ namespace ImageSharp.Drawing.Shapes public float Distance(Vector2 point) => this.innerPolygon.Distance(point); /// - /// Based on a line described by and + /// 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. diff --git a/src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs b/src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs index 6be73dda8d..5002bee406 100644 --- a/src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs +++ b/src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs @@ -150,8 +150,8 @@ namespace ImageSharp.Drawing.Shapes } /// - /// Based on a line described by and - /// populate a buffer for all points on the edges of the + /// 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.