55using System . Collections . Generic ;
66using System . Linq ;
77using System . Numerics ;
8+ using System . Runtime . CompilerServices ;
89
910namespace SixLabors . ImageSharp . Drawing
1011{
@@ -278,7 +279,7 @@ public PathBuilder ArcTo(float radiusX, float radiusY, float rotation, bool larg
278279 // Compute center
279280 matrix . M11 = 1 / radiusX ;
280281 matrix . M22 = 1 / radiusY ;
281- matrix = Matrix3x2Extensions . CreateRotationDegrees ( - rotation ) * matrix ;
282+ matrix * = Matrix3x2Extensions . CreateRotationDegrees ( - rotation ) ;
282283
283284 var unit1 = Vector2 . Transform ( start , matrix ) ;
284285 var unit2 = Vector2 . Transform ( point , matrix ) ;
@@ -294,15 +295,19 @@ public PathBuilder ArcTo(float radiusX, float radiusY, float rotation, bool larg
294295 }
295296
296297 delta *= scaleFactor ;
298+ var deltaXY = new Vector2 ( - delta . Y , delta . X ) ;
297299 Vector2 scaledCenter = unit1 + unit2 ;
298300 scaledCenter *= .5F ;
299- scaledCenter += new Vector2 ( - delta . Y , delta . X ) ;
301+ scaledCenter += deltaXY ;
300302 unit1 -= scaledCenter ;
301303 unit2 -= scaledCenter ;
302304
303305 // Compute θ and Δθ
304306 float theta1 = MathF . Atan2 ( unit1 . Y , unit1 . X ) ;
305307 float theta2 = MathF . Atan2 ( unit2 . Y , unit2 . X ) ;
308+
309+ // startAngle copied from https://github.com/UkooLabs/SVGSharpie/blob/5f7be977d487d416c4cf62578d6342b799a5c507/src/UkooLabs.SVGSharpie.ImageSharp/Shapes/ArcLineSegment.cs#L160
310+ float startAngle = - GeometryUtilities . RadianToDegree ( VectorAngle ( Vector2 . UnitX , ( xy - deltaXY ) / new Vector2 ( radiusX , radiusY ) ) ) ;
306311 float sweepAngle = GeometryUtilities . RadianToDegree ( theta2 - theta1 ) ;
307312
308313 // Fix the range to −360° < Δθ < 360°
@@ -324,14 +329,95 @@ public PathBuilder ArcTo(float radiusX, float radiusY, float rotation, bool larg
324329 }
325330
326331 var center = Vector2 . Lerp ( start , point , .5F ) ;
332+ foreach ( ILineSegment item in EllipticArcToBezierCurveInner ( start , center , new ( radiusX , radiusY ) , rotation , startAngle , sweepAngle ) )
333+ {
334+ this . AddSegment ( item ) ;
335+ }
336+
337+ return this ;
327338
328- // TODO: This is wrong and is the source of our problems when we use uneven radii.
329- // The rotation parameter does not appear to serve as the correct option either.
330- // Maybe passing both in the correct form is required?
331- float startAngle = GeometryUtilities . RadianToDegree ( MathF . Atan2 ( start . Y - point . Y , start . X - point . X ) ) ;
332- return this . AddEllipticalArc ( center , radiusX , radiusY , 0 , startAngle , sweepAngle ) ;
339+ // TODO: Fix this.
340+ // return this.AddEllipticalArc(center, radiusX, radiusY, rotation, startAngle, sweepAngle);
333341 }
334342
343+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
344+ private static float Clamp ( float value , float min , float max )
345+ {
346+ if ( value > max )
347+ {
348+ return max ;
349+ }
350+
351+ if ( value < min )
352+ {
353+ return min ;
354+ }
355+
356+ return value ;
357+ }
358+
359+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
360+ private static float VectorAngle ( Vector2 u , Vector2 v )
361+ {
362+ float dot = Vector2 . Dot ( u , v ) ;
363+ float length = u . Length ( ) * v . Length ( ) ;
364+ float angle = ( float ) Math . Acos ( Clamp ( dot / length , - 1 , 1 ) ) ; // floating point precision, slightly over values appear
365+ if ( ( ( u . X * v . Y ) - ( u . X * v . Y ) ) < 0 )
366+ {
367+ angle = - angle ;
368+ }
369+
370+ return angle ;
371+ }
372+
373+ private static IEnumerable < ILineSegment > EllipticArcToBezierCurveInner ( Vector2 from , Vector2 center , Vector2 radius , float xAngle , float startAngle , float deltaAngle )
374+ {
375+ xAngle = GeometryUtilities . DegreeToRadian ( xAngle ) ;
376+ startAngle = GeometryUtilities . DegreeToRadian ( startAngle ) ;
377+ deltaAngle = GeometryUtilities . DegreeToRadian ( deltaAngle ) ;
378+
379+ float s = startAngle ;
380+ float e = s + deltaAngle ;
381+ bool neg = e < s ;
382+ float sign = neg ? - 1 : 1 ;
383+ float remain = Math . Abs ( e - s ) ;
384+
385+ Vector2 prev = EllipticArcPoint ( center , radius , xAngle , s ) ;
386+
387+ while ( remain > 1e-05f )
388+ {
389+ float step = ( float ) Math . Min ( remain , Math . PI / 4 ) ;
390+ float signStep = step * sign ;
391+
392+ Vector2 p1 = prev ;
393+ Vector2 p2 = EllipticArcPoint ( center , radius , xAngle , s + signStep ) ;
394+
395+ float alphaT = ( float ) Math . Tan ( signStep / 2 ) ;
396+ float alpha = ( float ) ( Math . Sin ( signStep ) * ( Math . Sqrt ( 4 + ( 3 * alphaT * alphaT ) ) - 1 ) / 3 ) ;
397+ Vector2 q1 = p1 + ( alpha * EllipticArcDerivative ( radius , xAngle , s ) ) ;
398+ Vector2 q2 = p2 - ( alpha * EllipticArcDerivative ( radius , xAngle , s + signStep ) ) ;
399+
400+ yield return new CubicBezierLineSegment ( from , q1 , q2 , p2 ) ;
401+ from = p2 ;
402+
403+ s += signStep ;
404+ remain -= step ;
405+ prev = p2 ;
406+ }
407+ }
408+
409+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
410+ private static Vector2 EllipticArcDerivative ( Vector2 r , float xAngle , float t )
411+ => new (
412+ ( - r . X * MathF . Cos ( xAngle ) * MathF . Sin ( t ) ) - ( r . Y * MathF . Sin ( xAngle ) * MathF . Cos ( t ) ) ,
413+ ( - r . X * MathF . Sin ( xAngle ) * MathF . Sin ( t ) ) + ( r . Y * MathF . Cos ( xAngle ) * MathF . Cos ( t ) ) ) ;
414+
415+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
416+ private static Vector2 EllipticArcPoint ( Vector2 c , Vector2 r , float xAngle , float t )
417+ => new (
418+ c . X + ( r . X * MathF . Cos ( xAngle ) * MathF . Cos ( t ) ) - ( r . Y * MathF . Sin ( xAngle ) * MathF . Sin ( t ) ) ,
419+ c . Y + ( r . X * MathF . Sin ( xAngle ) * MathF . Cos ( t ) ) + ( r . Y * MathF . Cos ( xAngle ) * MathF . Sin ( t ) ) ) ;
420+
335421 /// <summary>
336422 /// Adds an elliptical arc to the current figure.
337423 /// </summary>
0 commit comments