-
-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #144 from derBobo/master
Implenting addArc feature
- Loading branch information
Showing
10 changed files
with
311 additions
and
2 deletions.
There are no files selected for viewing
171 changes: 171 additions & 0 deletions
171
src/ImageSharp.Drawing/Shapes/EllipticalArcLineSegment.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
// Copyright (c) Six Labors. | ||
// Licensed under the Apache License, Version 2.0. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Numerics; | ||
|
||
namespace SixLabors.ImageSharp.Drawing | ||
{ | ||
/// <summary> | ||
/// Represents a line segment that contains radii and angles that will be rendered as a elliptical arc | ||
/// </summary> | ||
/// <seealso cref="ILineSegment" /> | ||
public sealed class EllipticalArcLineSegment : ILineSegment | ||
{ | ||
private const float MinimumSqrDistance = 1.75f; | ||
private readonly PointF[] linePoints; | ||
private readonly float x; | ||
private readonly float y; | ||
private readonly float radiusX; | ||
private readonly float radiusY; | ||
private readonly float rotation; | ||
private readonly float startAngle; | ||
private readonly float sweepAngle; | ||
private readonly Matrix3x2 transformation; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="EllipticalArcLineSegment"/> class. | ||
/// </summary> | ||
/// <param name="x"> The x-coordinate of the center point of the ellips from which the arc is taken.</param> | ||
/// <param name="y"> The y-coordinate of the center point of the ellips from which the arc is taken.</param> | ||
/// <param name="radiusX">X radius of the ellipsis.</param> | ||
/// <param name="radiusY">Y radius of the ellipsis.</param> | ||
/// <param name="rotation">The rotation of (<paramref name="radiusX"/> to the X-axis and (<paramref name="radiusY"/> to the Y-axis, measured in degrees clockwise.</param> | ||
/// <param name="startAngle">The Start angle of the ellipsis, measured in degrees anticlockwise from the Y-axis.</param> | ||
/// <param name="sweepAngle"> The angle between (<paramref name="startAngle"/> and the end of the arc. </param> | ||
/// <param name="transformation">The Tranformation matrix, that should be used on the arc.</param> | ||
public EllipticalArcLineSegment(float x, float y, float radiusX, float radiusY, float rotation, float startAngle, float sweepAngle, Matrix3x2 transformation) | ||
{ | ||
Guard.MustBeGreaterThanOrEqualTo(radiusX, 0, nameof(radiusX)); | ||
Guard.MustBeGreaterThanOrEqualTo(radiusY, 0, nameof(radiusY)); | ||
this.x = x; | ||
this.y = y; | ||
this.radiusX = radiusX; | ||
this.radiusY = radiusY; | ||
this.rotation = rotation % 360; | ||
this.startAngle = startAngle % 360; | ||
this.transformation = transformation; | ||
this.sweepAngle = sweepAngle; | ||
if (sweepAngle > 360) | ||
{ | ||
this.sweepAngle = 360; | ||
} | ||
|
||
if (sweepAngle < -360) | ||
{ | ||
this.sweepAngle = -360; | ||
} | ||
|
||
this.linePoints = this.GetDrawingPoints(); | ||
this.EndPoint = this.linePoints[this.linePoints.Length - 1]; | ||
} | ||
|
||
/// <summary> | ||
/// Gets the end point. | ||
/// </summary> | ||
/// <value> | ||
/// The end point. | ||
/// </value> | ||
public PointF EndPoint { get; } | ||
|
||
/// <summary> | ||
/// Transforms the current LineSegment using specified matrix. | ||
/// </summary> | ||
/// <param name="matrix">The transformation matrix.</param> | ||
/// <returns>A line segment with the matrix applied to it.</returns> | ||
public EllipticalArcLineSegment Transform(Matrix3x2 matrix) | ||
{ | ||
if (matrix.IsIdentity) | ||
{ | ||
return this; | ||
} | ||
|
||
return new EllipticalArcLineSegment(this.x, this.y, this.radiusX, this.radiusY, this.rotation, this.startAngle, this.sweepAngle, Matrix3x2.Multiply(this.transformation, matrix)); | ||
} | ||
|
||
/// <summary> | ||
/// Transforms the current LineSegment using specified matrix. | ||
/// </summary> | ||
/// <param name="matrix">The matrix.</param> | ||
/// <returns>A line segment with the matrix applied to it.</returns> | ||
ILineSegment ILineSegment.Transform(Matrix3x2 matrix) => this.Transform(matrix); | ||
|
||
private PointF[] GetDrawingPoints() | ||
{ | ||
var points = new List<PointF>() | ||
{ | ||
this.CalculatePoint(this.startAngle) | ||
}; | ||
if (this.sweepAngle < 0) | ||
{ | ||
for (float i = this.startAngle; i > this.startAngle + this.sweepAngle; i--) | ||
{ | ||
float end = i - 1; | ||
if (end <= this.startAngle + this.sweepAngle) | ||
{ | ||
end = this.startAngle + this.sweepAngle; | ||
} | ||
|
||
points.AddRange(this.GetDrawingPoints(i, end, 0)); | ||
} | ||
} | ||
else | ||
{ | ||
for (float i = this.startAngle; i < this.startAngle + this.sweepAngle; i++) | ||
{ | ||
float end = i + 1; | ||
if (end >= this.startAngle + this.sweepAngle) | ||
{ | ||
end = this.startAngle + this.sweepAngle; | ||
} | ||
|
||
points.AddRange(this.GetDrawingPoints(i, end, 0)); | ||
} | ||
} | ||
|
||
return points.ToArray(); | ||
} | ||
|
||
private List<PointF> GetDrawingPoints(float start, float end, int depth) | ||
{ | ||
if (depth > 1000) | ||
{ | ||
return new List<PointF>(); | ||
} | ||
|
||
var points = new List<PointF>(); | ||
|
||
PointF startP = this.CalculatePoint(start); | ||
PointF endP = this.CalculatePoint(end); | ||
if ((new Vector2(endP.X, endP.Y) - new Vector2(startP.X, startP.Y)).LengthSquared() < MinimumSqrDistance) | ||
{ | ||
points.Add(endP); | ||
} | ||
else | ||
{ | ||
float mid = start + ((end - start) / 2); | ||
points.AddRange(this.GetDrawingPoints(start, mid, depth + 1)); | ||
points.AddRange(this.GetDrawingPoints(mid, end, depth + 1)); | ||
} | ||
|
||
return points; | ||
} | ||
|
||
private PointF CalculatePoint(float angle) | ||
{ | ||
float x = (this.radiusX * MathF.Sin(MathF.PI * angle / 180) * MathF.Cos(MathF.PI * this.rotation / 180)) - (this.radiusY * MathF.Cos(MathF.PI * angle / 180) * MathF.Sin(MathF.PI * this.rotation / 180)) + this.x; | ||
float y = (this.radiusX * MathF.Sin(MathF.PI * angle / 180) * MathF.Sin(MathF.PI * this.rotation / 180)) + (this.radiusY * MathF.Cos(MathF.PI * angle / 180) * MathF.Cos(MathF.PI * this.rotation / 180)) + this.y; | ||
var currPoint = new PointF(x, y); | ||
return PointF.Transform(currPoint, this.transformation); | ||
} | ||
|
||
/// <summary> | ||
/// Returns the current <see cref="ILineSegment" /> a simple linear path. | ||
/// </summary> | ||
/// <returns> | ||
/// Returns the current <see cref="ILineSegment" /> as simple linear path. | ||
/// </returns> | ||
public ReadOnlyMemory<PointF> Flatten() => this.linePoints; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
46 changes: 46 additions & 0 deletions
46
tests/ImageSharp.Drawing.Tests/Shapes/EllipticalArcLineSegmentTest.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
// Copyright (c) Six Labors. | ||
// Licensed under the Apache License, Version 2.0. | ||
|
||
using System.Collections.Generic; | ||
using System.Numerics; | ||
using Xunit; | ||
|
||
namespace SixLabors.ImageSharp.Drawing.Tests | ||
{ | ||
public class EllipticalArcLineSegmentTest | ||
{ | ||
[Fact] | ||
public void ContainsStartandEnd() | ||
{ | ||
var segment = new EllipticalArcLineSegment(10, 10, 10, 20, 0, 0, 90, Matrix3x2.Identity); | ||
IReadOnlyList<PointF> points = segment.Flatten().ToArray(); | ||
Assert.Equal(10, points[0].X, 5); | ||
Assert.Equal(30, points[0].Y, 5); | ||
Assert.Equal(20, segment.EndPoint.X, 5); | ||
Assert.Equal(10, segment.EndPoint.Y, 5); | ||
} | ||
|
||
[Fact] | ||
public void checkZeroRadii() | ||
{ | ||
IReadOnlyCollection<PointF> xRadiusZero = new EllipticalArcLineSegment(20, 10, 0, 20, 0, 0, 360, Matrix3x2.Identity).Flatten().ToArray(); | ||
IReadOnlyCollection<PointF> yRadiusZero = new EllipticalArcLineSegment(20, 10, 30, 0, 0, 0, 360, Matrix3x2.Identity).Flatten().ToArray(); | ||
IReadOnlyCollection<PointF> bothRadiiZero = new EllipticalArcLineSegment(20, 10, 0, 0, 0, 0, 360, Matrix3x2.Identity).Flatten().ToArray(); | ||
foreach (PointF point in xRadiusZero) | ||
{ | ||
Assert.Equal(20, point.X); | ||
} | ||
|
||
foreach (PointF point in yRadiusZero) | ||
{ | ||
Assert.Equal(10, point.Y); | ||
} | ||
|
||
foreach (PointF point in bothRadiiZero) | ||
{ | ||
Assert.Equal(20, point.X); | ||
Assert.Equal(10, point.Y); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file modified
BIN
-4.87 KB
(2.5%)
tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawPath_HotPink_A150_T5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
-8.29 KB
(1.5%)
tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawPath_HotPink_A255_T5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
-4.58 KB
(2.7%)
tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawPath_Red_A255_T3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
-4.44 KB
(2.8%)
tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawPath_White_A255_T1.5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
-4.73 KB
(2.6%)
tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawPath_White_A255_T15.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.