diff --git a/src/ImageSharp.Drawing/Fill.cs b/src/ImageSharp.Drawing/Fill.cs index 1e14d8b11..c0f43bdd1 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 322133a4d..d29b58e1b 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 489d27423..867ca7c90 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 9fae2ca8d..000000000 --- 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 a6f932439..000000000 --- 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