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); }