Browse Source

Add optimised RectanglePolygon

af/merge-core
Scott Williams 9 years ago
parent
commit
ea6ccd2eff
  1. 132
      src/ImageSharp/Drawing/Draw_Rectangle.cs
  2. 92
      src/ImageSharp/Drawing/Fill_Rectangle.cs
  3. 4
      src/ImageSharp/Drawing/Processors/DrawPathProcessor.cs
  4. 1
      src/ImageSharp/Drawing/Processors/FillShapeProcessor.cs
  5. 237
      src/ImageSharp/Drawing/Shapes/RectangularPolygon.cs
  6. 71
      tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs
  7. 8
      tests/ImageSharp.Tests/Drawing/PolygonTests.cs
  8. 2
      tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs

132
src/ImageSharp/Drawing/Draw_Rectangle.cs

@ -0,0 +1,132 @@
// <copyright file="Draw_Rectangle.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
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;
/// <summary>
/// Extension methods for the <see cref="Image{TColor, TPacked}"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Draws the outline of the polygon with the provided pen.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="pen">The pen.</param>
/// <param name="shape">The shape.</param>
/// <param name="options">The options.</param>
/// <returns>
/// The Image
/// </returns>
public static Image<TColor, TPacked> DrawPolygon<TColor, TPacked>(this Image<TColor, TPacked> source, IPen<TColor, TPacked> pen, RectangleF shape, GraphicsOptions options)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct, IEquatable<TPacked>
{
return source.Process(new DrawPathProcessor<TColor, TPacked>(pen, (IPath)new RectangularPolygon(shape), options));
}
/// <summary>
/// Draws the outline of the polygon with the provided pen.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="pen">The pen.</param>
/// <param name="shape">The shape.</param>
/// <returns>The Image</returns>
public static Image<TColor, TPacked> DrawPolygon<TColor, TPacked>(this Image<TColor, TPacked> source, IPen<TColor, TPacked> pen, RectangleF shape)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct, IEquatable<TPacked>
{
return source.DrawPolygon(pen, shape, GraphicsOptions.Default);
}
/// <summary>
/// Draws the outline of the polygon with the provided brush at the provided thickness.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="brush">The brush.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="shape">The shape.</param>
/// <param name="options">The options.</param>
/// <returns>
/// The Image
/// </returns>
public static Image<TColor, TPacked> DrawPolygon<TColor, TPacked>(this Image<TColor, TPacked> source, IBrush<TColor, TPacked> brush, float thickness, RectangleF shape, GraphicsOptions options)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct, IEquatable<TPacked>
{
return source.DrawPolygon(new Pen<TColor, TPacked>(brush, thickness), shape, options);
}
/// <summary>
/// Draws the outline of the polygon with the provided brush at the provided thickness.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="brush">The brush.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="shape">The shape.</param>
/// <returns>The Image</returns>
public static Image<TColor, TPacked> DrawPolygon<TColor, TPacked>(this Image<TColor, TPacked> source, IBrush<TColor, TPacked> brush, float thickness, RectangleF shape)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct, IEquatable<TPacked>
{
return source.DrawPolygon(new Pen<TColor, TPacked>(brush, thickness), shape);
}
/// <summary>
/// Draws the outline of the polygon with the provided brush at the provided thickness.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="color">The color.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="shape">The shape.</param>
/// <param name="options">The options.</param>
/// <returns>
/// The Image
/// </returns>
public static Image<TColor, TPacked> DrawPolygon<TColor, TPacked>(this Image<TColor, TPacked> source, TColor color, float thickness, RectangleF shape, GraphicsOptions options)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct, IEquatable<TPacked>
{
return source.DrawPolygon(new SolidBrush<TColor, TPacked>(color), thickness, shape, options);
}
/// <summary>
/// Draws the outline of the polygon with the provided brush at the provided thickness.
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="color">The color.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="shape">The shape.</param>
/// <returns>The Image</returns>
public static Image<TColor, TPacked> DrawPolygon<TColor, TPacked>(this Image<TColor, TPacked> source, TColor color, float thickness, RectangleF shape)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct, IEquatable<TPacked>
{
return source.DrawPolygon(new SolidBrush<TColor, TPacked>(color), thickness, shape);
}
}
}

92
src/ImageSharp/Drawing/Fill_Rectangle.cs

@ -0,0 +1,92 @@
// <copyright file="Fill_Rectangle.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using System.Numerics;
using Drawing;
using Drawing.Brushes;
using Drawing.Paths;
using Drawing.Processors;
using Drawing.Shapes;
using Processors;
/// <summary>
/// Extension methods for the <see cref="Image{TColor, TPacked}"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Flood fills the image in the shape o fhte provided polygon with the specified brush..
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="brush">The brush.</param>
/// <param name="shape">The shape.</param>
/// <param name="options">The options.</param>
/// <returns>
/// The Image
/// </returns>
public static Image<TColor, TPacked> Fill<TColor, TPacked>(this Image<TColor, TPacked> source, IBrush<TColor, TPacked> brush, RectangleF shape, GraphicsOptions options)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct, IEquatable<TPacked>
{
return source.Process(new FillShapeProcessor<TColor, TPacked>(brush, new RectangularPolygon(shape), options));
}
/// <summary>
/// Flood fills the image in the shape o fhte provided polygon with the specified brush..
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="brush">The brush.</param>
/// <param name="shape">The shape.</param>
/// <returns>The Image</returns>
public static Image<TColor, TPacked> Fill<TColor, TPacked>(this Image<TColor, TPacked> source, IBrush<TColor, TPacked> brush, RectangleF shape)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct, IEquatable<TPacked>
{
return source.Process(new FillShapeProcessor<TColor, TPacked>(brush, new RectangularPolygon(shape), GraphicsOptions.Default));
}
/// <summary>
/// Flood fills the image in the shape o fhte provided polygon with the specified brush..
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="color">The color.</param>
/// <param name="shape">The shape.</param>
/// <param name="options">The options.</param>
/// <returns>
/// The Image
/// </returns>
public static Image<TColor, TPacked> Fill<TColor, TPacked>(this Image<TColor, TPacked> source, TColor color, RectangleF shape, GraphicsOptions options)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct, IEquatable<TPacked>
{
return source.Fill(new SolidBrush<TColor, TPacked>(color), shape, options);
}
/// <summary>
/// Flood fills the image in the shape o fhte provided polygon with the specified brush..
/// </summary>
/// <typeparam name="TColor">The type of the color.</typeparam>
/// <typeparam name="TPacked">The type of the packed.</typeparam>
/// <param name="source">The source.</param>
/// <param name="color">The color.</param>
/// <param name="shape">The shape.</param>
/// <returns>The Image</returns>
public static Image<TColor, TPacked> Fill<TColor, TPacked>(this Image<TColor, TPacked> source, TColor color, RectangleF shape)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct, IEquatable<TPacked>
{
return source.Fill(new SolidBrush<TColor, TPacked>(color), shape);
}
}
}

4
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;
/// <summary>
/// Draws a path using the processor pipeline
@ -87,6 +88,9 @@ namespace ImageSharp.Drawing.Processors
/// <inheritdoc/>
protected override void OnApply(ImageBase<TColor, TPacked> source, Rectangle sourceRectangle)
{
#if DEBUG
this.ParallelOptions.MaxDegreeOfParallelism = 1;
#endif
using (IPenApplicator<TColor, TPacked> applicator = this.pen.CreateApplicator(this.region))
{
var rect = RectangleF.Ceiling(applicator.RequiredRegion);

1
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;
/// <summary>
/// Usinf a brsuh and a shape fills shape with contents of brush the

237
src/ImageSharp/Drawing/Shapes/RectangularPolygon.cs

@ -0,0 +1,237 @@
// <copyright file="RectangularPolygon.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
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;
/// <summary>
/// A way of optermising drawing rectangles.
/// </summary>
/// <seealso cref="ImageSharp.Drawing.Shapes.IShape" />
public class RectangularPolygon : IShape, IPath
{
private readonly RectangleF rectangle;
private readonly Vector2 topLeft;
private readonly Vector2 bottomRight;
private readonly Vector2[] points;
private readonly IEnumerable<IPath> pathCollection;
private readonly float halfLength;
/// <summary>
/// Initializes a new instance of the <see cref="RectangularPolygon"/> class.
/// </summary>
/// <param name="rect">The rect.</param>
public RectangularPolygon(ImageSharp.Rectangle rect)
: this((RectangleF)rect)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RectangularPolygon"/> class.
/// </summary>
/// <param name="rect">The rect.</param>
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 };
}
/// <summary>
/// Gets the bounding box of this shape.
/// </summary>
/// <value>
/// The bounds.
/// </value>
public RectangleF Bounds => this.rectangle;
/// <summary>
/// Gets a value indicating whether this instance is closed.
/// </summary>
/// <value>
/// <c>true</c> if this instance is closed; otherwise, <c>false</c>.
/// </value>
public bool IsClosed => true;
/// <summary>
/// Gets the length of the path
/// </summary>
/// <value>
/// The length.
/// </value>
public float Length { get; }
/// <summary>
/// Calculates the distance along and away from the path for a specified point.
/// </summary>
/// <param name="point">The point along the path.</param>
/// <returns>
/// Returns details about the point and its distance away from the path.
/// </returns>
PointInfo IPath.Distance(Vector2 point)
{
bool inside; // dont care about inside/outside for paths just distance
return this.Distance(point, false, out inside);
}
/// <summary>
/// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds
/// </summary>
/// <param name="point">The point.</param>
/// <returns>
/// Returns the distance from the shape to the point
/// </returns>
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;
}
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>
/// An enumerator that can be used to iterate through the collection.
/// </returns>
public IEnumerator<IPath> GetEnumerator()
{
return this.pathCollection.GetEnumerator();
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
/// </returns>
IEnumerator IEnumerable.GetEnumerator()
{
return this.pathCollection.GetEnumerator();
}
/// <summary>
/// Converts the <see cref="ILineSegment" /> into a simple linear path..
/// </summary>
/// <returns>
/// Returns the current <see cref="ILineSegment" /> as simple linear path.
/// </returns>
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
};
}
}
}

71
tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs

@ -0,0 +1,71 @@
// <copyright file="Crop.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
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);
}
}
}
}

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

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

Loading…
Cancel
Save