diff --git a/src/ImageSharp/Drawing/Draw_Rectangle.cs b/src/ImageSharp/Drawing/Draw_Rectangle.cs
new file mode 100644
index 000000000..80a0f143b
--- /dev/null
+++ b/src/ImageSharp/Drawing/Draw_Rectangle.cs
@@ -0,0 +1,132 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Numerics;
+ using Drawing;
+ using Drawing.Brushes;
+ using Drawing.Paths;
+ using Drawing.Pens;
+ using Drawing.Processors;
+ using Drawing.Shapes;
+ using Processors;
+
+ ///
+ /// Extension methods for the type.
+ ///
+ public static partial class ImageExtensions
+ {
+ ///
+ /// Draws the outline of the polygon with the provided pen.
+ ///
+ /// The type of the color.
+ /// The type of the packed.
+ /// The source.
+ /// The pen.
+ /// The shape.
+ /// The options.
+ ///
+ /// The Image
+ ///
+ public static Image DrawPolygon(this Image source, IPen pen, RectangleF shape, GraphicsOptions options)
+ where TColor : struct, IPackedPixel
+ where TPacked : struct, IEquatable
+ {
+ return source.Process(new DrawPathProcessor(pen, (IPath)new RectangularPolygon(shape), options));
+ }
+
+ ///
+ /// Draws the outline of the polygon with the provided pen.
+ ///
+ /// The type of the color.
+ /// The type of the packed.
+ /// The source.
+ /// The pen.
+ /// The shape.
+ /// The Image
+ public static Image DrawPolygon(this Image source, IPen pen, RectangleF shape)
+ where TColor : struct, IPackedPixel
+ where TPacked : struct, IEquatable
+ {
+ return source.DrawPolygon(pen, shape, GraphicsOptions.Default);
+ }
+
+ ///
+ /// Draws the outline of the polygon with the provided brush at the provided thickness.
+ ///
+ /// The type of the color.
+ /// The type of the packed.
+ /// The source.
+ /// The brush.
+ /// The thickness.
+ /// The shape.
+ /// The options.
+ ///
+ /// The Image
+ ///
+ public static Image DrawPolygon(this Image source, IBrush brush, float thickness, RectangleF shape, GraphicsOptions options)
+ where TColor : struct, IPackedPixel
+ where TPacked : struct, IEquatable
+ {
+ return source.DrawPolygon(new Pen(brush, thickness), shape, options);
+ }
+
+ ///
+ /// Draws the outline of the polygon with the provided brush at the provided thickness.
+ ///
+ /// The type of the color.
+ /// The type of the packed.
+ /// The source.
+ /// The brush.
+ /// The thickness.
+ /// The shape.
+ /// The Image
+ public static Image DrawPolygon(this Image source, IBrush brush, float thickness, RectangleF shape)
+ where TColor : struct, IPackedPixel
+ where TPacked : struct, IEquatable
+ {
+ return source.DrawPolygon(new Pen(brush, thickness), shape);
+ }
+
+ ///
+ /// Draws the outline of the polygon with the provided brush at the provided thickness.
+ ///
+ /// The type of the color.
+ /// The type of the packed.
+ /// The source.
+ /// The color.
+ /// The thickness.
+ /// The shape.
+ /// The options.
+ ///
+ /// The Image
+ ///
+ public static Image DrawPolygon(this Image source, TColor color, float thickness, RectangleF shape, GraphicsOptions options)
+ where TColor : struct, IPackedPixel
+ where TPacked : struct, IEquatable
+ {
+ return source.DrawPolygon(new SolidBrush(color), thickness, shape, options);
+ }
+
+ ///
+ /// Draws the outline of the polygon with the provided brush at the provided thickness.
+ ///
+ /// The type of the color.
+ /// The type of the packed.
+ /// The source.
+ /// The color.
+ /// The thickness.
+ /// The shape.
+ /// The Image
+ public static Image DrawPolygon(this Image source, TColor color, float thickness, RectangleF shape)
+ where TColor : struct, IPackedPixel
+ where TPacked : struct, IEquatable
+ {
+ return source.DrawPolygon(new SolidBrush(color), thickness, shape);
+ }
+ }
+}
diff --git a/src/ImageSharp/Drawing/Fill_Rectangle.cs b/src/ImageSharp/Drawing/Fill_Rectangle.cs
new file mode 100644
index 000000000..f9d075a9f
--- /dev/null
+++ b/src/ImageSharp/Drawing/Fill_Rectangle.cs
@@ -0,0 +1,92 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Numerics;
+ using Drawing;
+ using Drawing.Brushes;
+ using Drawing.Paths;
+ using Drawing.Processors;
+ using Drawing.Shapes;
+ using Processors;
+
+ ///
+ /// Extension methods for the type.
+ ///
+ public static partial class ImageExtensions
+ {
+ ///
+ /// Flood fills the image in the shape o fhte provided polygon with the specified brush..
+ ///
+ /// The type of the color.
+ /// The type of the packed.
+ /// The source.
+ /// The brush.
+ /// The shape.
+ /// The options.
+ ///
+ /// The Image
+ ///
+ public static Image Fill(this Image source, IBrush brush, RectangleF shape, GraphicsOptions options)
+ where TColor : struct, IPackedPixel
+ where TPacked : struct, IEquatable
+ {
+ return source.Process(new FillShapeProcessor(brush, new RectangularPolygon(shape), options));
+ }
+
+ ///
+ /// Flood fills the image in the shape o fhte provided polygon with the specified brush..
+ ///
+ /// The type of the color.
+ /// The type of the packed.
+ /// The source.
+ /// The brush.
+ /// The shape.
+ /// The Image
+ public static Image Fill(this Image source, IBrush brush, RectangleF shape)
+ where TColor : struct, IPackedPixel
+ where TPacked : struct, IEquatable
+ {
+ return source.Process(new FillShapeProcessor(brush, new RectangularPolygon(shape), GraphicsOptions.Default));
+ }
+
+ ///
+ /// Flood fills the image in the shape o fhte provided polygon with the specified brush..
+ ///
+ /// The type of the color.
+ /// The type of the packed.
+ /// The source.
+ /// The color.
+ /// The shape.
+ /// The options.
+ ///
+ /// The Image
+ ///
+ public static Image Fill(this Image source, TColor color, RectangleF shape, GraphicsOptions options)
+ where TColor : struct, IPackedPixel
+ where TPacked : struct, IEquatable
+ {
+ return source.Fill(new SolidBrush(color), shape, options);
+ }
+
+ ///
+ /// Flood fills the image in the shape o fhte provided polygon with the specified brush..
+ ///
+ /// The type of the color.
+ /// The type of the packed.
+ /// The source.
+ /// The color.
+ /// The shape.
+ /// The Image
+ public static Image Fill(this Image source, TColor color, RectangleF shape)
+ where TColor : struct, IPackedPixel
+ where TPacked : struct, IEquatable
+ {
+ return source.Fill(new SolidBrush(color), shape);
+ }
+ }
+}
diff --git a/src/ImageSharp/Drawing/Processors/DrawPathProcessor.cs b/src/ImageSharp/Drawing/Processors/DrawPathProcessor.cs
index af0af20fa..681369841 100644
--- a/src/ImageSharp/Drawing/Processors/DrawPathProcessor.cs
+++ b/src/ImageSharp/Drawing/Processors/DrawPathProcessor.cs
@@ -15,6 +15,7 @@ namespace ImageSharp.Drawing.Processors
using Pens;
using Pens.Processors;
using Shapes;
+ using Rectangle = ImageSharp.Rectangle;
///
/// Draws a path using the processor pipeline
@@ -87,6 +88,9 @@ namespace ImageSharp.Drawing.Processors
///
protected override void OnApply(ImageBase source, Rectangle sourceRectangle)
{
+#if DEBUG
+ this.ParallelOptions.MaxDegreeOfParallelism = 1;
+#endif
using (IPenApplicator applicator = this.pen.CreateApplicator(this.region))
{
var rect = RectangleF.Ceiling(applicator.RequiredRegion);
diff --git a/src/ImageSharp/Drawing/Processors/FillShapeProcessor.cs b/src/ImageSharp/Drawing/Processors/FillShapeProcessor.cs
index 3209ce9c5..62f0481d4 100644
--- a/src/ImageSharp/Drawing/Processors/FillShapeProcessor.cs
+++ b/src/ImageSharp/Drawing/Processors/FillShapeProcessor.cs
@@ -11,6 +11,7 @@ namespace ImageSharp.Drawing.Processors
using Drawing;
using ImageSharp.Processors;
using Shapes;
+ using Rectangle = ImageSharp.Rectangle;
///
/// Usinf a brsuh and a shape fills shape with contents of brush the
diff --git a/src/ImageSharp/Drawing/Shapes/RectangularPolygon.cs b/src/ImageSharp/Drawing/Shapes/RectangularPolygon.cs
new file mode 100644
index 000000000..f05dadc7c
--- /dev/null
+++ b/src/ImageSharp/Drawing/Shapes/RectangularPolygon.cs
@@ -0,0 +1,237 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Drawing.Shapes
+{
+ using System;
+ using System.Collections;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Numerics;
+ using System.Threading.Tasks;
+ using Paths;
+
+ ///
+ /// A way of optermising drawing rectangles.
+ ///
+ ///
+ public class RectangularPolygon : IShape, IPath
+ {
+ private readonly RectangleF rectangle;
+ private readonly Vector2 topLeft;
+ private readonly Vector2 bottomRight;
+ private readonly Vector2[] points;
+ private readonly IEnumerable pathCollection;
+ private readonly float halfLength;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The rect.
+ public RectangularPolygon(ImageSharp.Rectangle rect)
+ : this((RectangleF)rect)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The rect.
+ public RectangularPolygon(ImageSharp.RectangleF rect)
+ {
+ this.rectangle = rect;
+ this.points = new Vector2[4]
+ {
+ this.topLeft = new Vector2(rect.Left, rect.Top),
+ new Vector2(rect.Right, rect.Top),
+ this.bottomRight = new Vector2(rect.Right, rect.Bottom),
+ new Vector2(rect.Left, rect.Bottom)
+ };
+
+ this.halfLength = this.rectangle.Width + this.rectangle.Height;
+ this.Length = this.halfLength * 2;
+ this.pathCollection = new[] { this };
+ }
+
+ ///
+ /// Gets the bounding box of this shape.
+ ///
+ ///
+ /// The bounds.
+ ///
+ public RectangleF Bounds => this.rectangle;
+
+ ///
+ /// Gets a value indicating whether this instance is closed.
+ ///
+ ///
+ /// true if this instance is closed; otherwise, false.
+ ///
+ public bool IsClosed => true;
+
+ ///
+ /// Gets the length of the path
+ ///
+ ///
+ /// The length.
+ ///
+ public float Length { get; }
+
+ ///
+ /// Calculates the distance along and away from the path for a specified point.
+ ///
+ /// The point along the path.
+ ///
+ /// Returns details about the point and its distance away from the path.
+ ///
+ PointInfo IPath.Distance(Vector2 point)
+ {
+ bool inside; // dont care about inside/outside for paths just distance
+ return this.Distance(point, false, out inside);
+ }
+
+ ///
+ /// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds
+ ///
+ /// The point.
+ ///
+ /// Returns the distance from the shape to the point
+ ///
+ public float Distance(Vector2 point)
+ {
+ bool insidePoly;
+ var result = this.Distance(point, true, out insidePoly);
+
+ // invert the distance from path when inside
+ return insidePoly ? -result.DistanceFromPath : result.DistanceFromPath;
+ }
+
+ ///
+ /// Returns an enumerator that iterates through the collection.
+ ///
+ ///
+ /// An enumerator that can be used to iterate through the collection.
+ ///
+ public IEnumerator GetEnumerator()
+ {
+ return this.pathCollection.GetEnumerator();
+ }
+
+ ///
+ /// Returns an enumerator that iterates through a collection.
+ ///
+ ///
+ /// An object that can be used to iterate through the collection.
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return this.pathCollection.GetEnumerator();
+ }
+
+ ///
+ /// Converts the into a simple linear path..
+ ///
+ ///
+ /// Returns the current as simple linear path.
+ ///
+ public Vector2[] AsSimpleLinearPath()
+ {
+ return this.points;
+ }
+
+ private PointInfo Distance(Vector2 point, bool getDistanceAwayOnly, out bool isInside)
+ {
+ // point in rectangle
+ // if after its clamped by the extreams its still the same then it must be inside :)
+ Vector2 clamped = Vector2.Clamp(point, this.topLeft, this.bottomRight);
+ isInside = clamped == point;
+
+ float distanceFromEdge = float.MaxValue;
+ float distanceAlongEdge = 0f;
+
+ if (isInside)
+ {
+ // get the absolute distances from the extreams
+ Vector2 topLeftDist = Vector2.Abs(point - this.topLeft);
+ Vector2 bottomRightDist = Vector2.Abs(point - this.bottomRight);
+
+ // get the min components
+ Vector2 minDists = Vector2.Min(topLeftDist, bottomRightDist);
+
+ // and then the single smallest (dont have to worry about direction)
+ distanceFromEdge = Math.Min(minDists.X, minDists.Y);
+
+ if (!getDistanceAwayOnly)
+ {
+ // we need to make clamped the closest point
+ if (this.topLeft.X + distanceFromEdge == point.X)
+ {
+ // closer to lhf
+ clamped.X = this.topLeft.X; // y is already the same
+
+ // distance along edge is length minus the amout down we are from the top of the rect
+ distanceAlongEdge = this.Length - (clamped.Y - this.topLeft.Y);
+ }
+ else if (this.topLeft.Y + distanceFromEdge == point.Y)
+ {
+ // closer to top
+ clamped.Y = this.topLeft.Y; // x is already the same
+
+ distanceAlongEdge = clamped.X - this.topLeft.X;
+ }
+ else if (this.bottomRight.Y - distanceFromEdge == point.Y)
+ {
+ // closer to bottom
+ clamped.Y = this.bottomRight.Y; // x is already the same
+
+ distanceAlongEdge = (this.bottomRight.X - clamped.X) + this.halfLength;
+ }
+ else if (this.bottomRight.X - distanceFromEdge == point.X)
+ {
+ // closer to rhs
+ clamped.X = this.bottomRight.X; // x is already the same
+
+ distanceAlongEdge = (this.bottomRight.Y - clamped.Y) + this.rectangle.Width;
+ }
+ }
+ }
+ else
+ {
+ // clamped is the point on the path thats closest no matter what
+ distanceFromEdge = (clamped - point).Length();
+
+ if (!getDistanceAwayOnly)
+ {
+ // we need to figure out whats the cloests edge now and thus what distance/poitn is closest
+ if (this.topLeft.X == clamped.X)
+ {
+ // distance along edge is length minus the amout down we are from the top of the rect
+ distanceAlongEdge = this.Length - (clamped.Y - this.topLeft.Y);
+ }
+ else if (this.topLeft.Y == clamped.Y)
+ {
+ distanceAlongEdge = clamped.X - this.topLeft.X;
+ }
+ else if (this.bottomRight.Y == clamped.Y)
+ {
+ distanceAlongEdge = (this.bottomRight.X - clamped.X) + this.halfLength;
+ }
+ else if (this.bottomRight.X == clamped.X)
+ {
+ distanceAlongEdge = (this.bottomRight.Y - clamped.Y) + this.rectangle.Width;
+ }
+ }
+ }
+
+ return new PointInfo
+ {
+ SearchPoint = point,
+ DistanceFromPath = distanceFromEdge,
+ ClosestPointOnPath = clamped,
+ DistanceAlongPath = distanceAlongEdge
+ };
+ }
+ }
+}
diff --git a/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs b/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs
new file mode 100644
index 000000000..fdb3f52b8
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs
@@ -0,0 +1,71 @@
+//
+// 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 BenchmarkDotNet.Attributes;
+ using CoreImage = ImageSharp.Image;
+ using CoreRectangle = ImageSharp.Rectangle;
+ using CoreColor = ImageSharp.Color;
+ using System.IO;
+ using System.Numerics;
+
+ public class FillRectangle
+ {
+ [Benchmark(Baseline = true, Description = "System.Drawing Fill Rectangle")]
+ public void FillRectangleSystemDrawing()
+ {
+ using (Bitmap destination = new Bitmap(800, 800))
+ {
+
+ using (Graphics graphics = Graphics.FromImage(destination))
+ {
+ graphics.InterpolationMode = InterpolationMode.Default;
+ graphics.SmoothingMode = SmoothingMode.AntiAlias;
+ var pen = new Pen(Color.HotPink, 10);
+ graphics.FillRectangle(Brushes.HotPink, new Rectangle(10, 10, 190, 140));
+ }
+
+ using (MemoryStream ms = new MemoryStream())
+ {
+ destination.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
+ }
+ }
+ }
+
+ [Benchmark(Description = "ImageSharp Fill Rectangle")]
+ public void FillRactangleCore()
+ {
+ CoreImage image = new CoreImage(800, 800);
+
+ image.Fill(CoreColor.HotPink, new ImageSharp.Drawing.Shapes.RectangularPolygon(new CoreRectangle(10, 10, 190, 140)));
+
+ using (MemoryStream ms = new MemoryStream())
+ {
+ image.SaveAsBmp(ms);
+ }
+ }
+
+ [Benchmark(Description = "ImageSharp Fill Rectangle - As Polygon")]
+ public void FillPolygonCore()
+ {
+ CoreImage image = new CoreImage(800, 800);
+
+ image.FillPolygon(CoreColor.HotPink, new[] {
+ new Vector2(10, 10),
+ new Vector2(200, 10),
+ new Vector2(200, 150),
+ new Vector2(10, 150) });
+
+ using (MemoryStream ms = new MemoryStream())
+ {
+ image.SaveAsBmp(ms);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Drawing/PolygonTests.cs b/tests/ImageSharp.Tests/Drawing/PolygonTests.cs
index fbda689fa..68027d0bf 100644
--- a/tests/ImageSharp.Tests/Drawing/PolygonTests.cs
+++ b/tests/ImageSharp.Tests/Drawing/PolygonTests.cs
@@ -46,7 +46,6 @@ namespace ImageSharp.Tests.Drawing
}
}
-
[Fact]
public void ImageShouldBeOverlayedPolygonOutlineWithOpacity()
{
@@ -95,12 +94,7 @@ namespace ImageSharp.Tests.Drawing
{
image
.BackgroundColor(Color.Blue)
- .DrawPolygon(Color.HotPink, 10, new[] {
- new Vector2(10, 10),
- new Vector2(200, 10),
- new Vector2(200, 150),
- new Vector2(10, 150)
- })
+ .DrawPolygon(Color.HotPink, 10, new Rectangle(10, 10, 190, 140))
.Save(output);
}
diff --git a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs
index cc2e34acc..1eac231b4 100644
--- a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs
+++ b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs
@@ -153,7 +153,7 @@ namespace ImageSharp.Tests.Drawing
{
image
.BackgroundColor(Color.Blue)
- .FillPolygon(Color.HotPink, simplePath)
+ .Fill(Color.HotPink, new ImageSharp.Drawing.Shapes.RectangularPolygon(new Rectangle(10,10, 190, 140)))
.Save(output);
}