Bug 1185636 - Part 3 - Refactor ArcToBezier so that its implementation can be more easily reused. r=jmuizelaar
authorLee Salzman <lsalzman@mozilla.com>
Mon, 27 Jul 2015 12:12:22 -0400
changeset 287518 09b212388126c4b24042f8a10502ff505f4558d2
parent 287517 dfe577ec4e569e36f4f79d9c9c731ab9d717c093
child 287519 942beac221e4b6a89ffe096da593871b4d7649b9
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjmuizelaar
bugs1185636
milestone42.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1185636 - Part 3 - Refactor ArcToBezier so that its implementation can be more easily reused. r=jmuizelaar
gfx/2d/PathHelpers.h
--- a/gfx/2d/PathHelpers.h
+++ b/gfx/2d/PathHelpers.h
@@ -8,16 +8,90 @@
 
 #include "2D.h"
 #include "mozilla/Constants.h"
 #include "UserData.h"
 
 namespace mozilla {
 namespace gfx {
 
+// Kappa constant for 90-degree angle
+const Float kKappaFactor = 0.55191497064665766025f;
+
+// Calculate kappa constant for partial curve. The sign of angle in the
+// tangent will actually ensure this is negative for a counter clockwise
+// sweep, so changing signs later isn't needed.
+inline Float ComputeKappaFactor(Float aAngle)
+{
+  return (4.0f / 3.0f) * tanf(aAngle / 4.0f);
+}
+
+/**
+ * Draws a partial arc <= 90 degrees given exact start and end points.
+ * Assumes that it is continuing from an already specified start point.
+ */
+template <typename T>
+inline void PartialArcToBezier(T* aSink,
+                               const Size& aRadius,
+                               const Point& aStartPoint, const Point& aEndPoint,
+                               const Point& aStartOffset, const Point& aEndOffset,
+                               Float aKappaFactor = kKappaFactor)
+{
+  Float kappaX = aKappaFactor * aRadius.width;
+  Float kappaY = aKappaFactor * aRadius.height;
+
+  Point cp1 =
+    aStartPoint + Point(-aStartOffset.y * kappaX, aStartOffset.x * kappaY);
+
+  Point cp2 =
+    aEndPoint + Point(aEndOffset.y * kappaX, -aEndOffset.x * kappaY);
+
+  aSink->BezierTo(cp1, cp2, aEndPoint);
+}
+
+/**
+ * Draws an acute arc (<= 90 degrees) given exact start and end points.
+ * Specialized version avoiding kappa calculation.
+ */
+template <typename T>
+inline void AcuteArcToBezier(T* aSink,
+                             const Point& aOrigin, const Size& aRadius,
+                             const Point& aStartPoint, const Point& aEndPoint,
+                             Float aKappaFactor = kKappaFactor)
+{
+  aSink->LineTo(aStartPoint);
+  if (!aRadius.IsEmpty()) {
+    Point startOffset = aStartPoint - aOrigin;
+    startOffset.x /= aRadius.width;
+    startOffset.y /= aRadius.height;
+    Point endOffset = aEndPoint - aOrigin;
+    endOffset.x /= aRadius.width;
+    endOffset.y /= aRadius.height;
+    PartialArcToBezier(aSink, aRadius,
+                       aStartPoint, aEndPoint,
+                       startOffset, endOffset,
+                       aKappaFactor);
+  } else if (aEndPoint != aStartPoint) {
+    aSink->LineTo(aEndPoint);
+  }
+}
+
+/**
+ * Draws an acute arc (<= 90 degrees) given exact start and end points.
+ */
+template <typename T>
+inline void AcuteArcToBezier(T* aSink,
+                             const Point& aOrigin, const Size& aRadius,
+                             const Point& aStartPoint, const Point& aEndPoint,
+                             Float aStartAngle, Float aEndAngle)
+{
+  AcuteArcToBezier(aSink, aOrigin, aRadius, aStartPoint, aEndPoint,
+                   ComputeKappaFactor(aEndAngle - aStartAngle));
+}
+
 template <typename T>
 void ArcToBezier(T* aSink, const Point &aOrigin, const Size &aRadius,
                  float aStartAngle, float aEndAngle, bool aAntiClockwise)
 {
   Float sweepDirection = aAntiClockwise ? -1.0f : 1.0f;
 
   // Calculate the total arc we're going to sweep.
   Float arcSweepLeft = (aEndAngle - aStartAngle) * sweepDirection;
@@ -30,95 +104,70 @@ void ArcToBezier(T* aSink, const Point &
     // Recalculate the start angle to land closer to end angle.
     aStartAngle = aEndAngle - arcSweepLeft * sweepDirection;
   } else if (arcSweepLeft > Float(2.0f * M_PI)) {
     // Sweeping more than 2 * pi is a full circle.
     arcSweepLeft = Float(2.0f * M_PI);
   }
 
   Float currentStartAngle = aStartAngle;
-  Point currentStartPoint(aOrigin.x + cosf(aStartAngle) * aRadius.width,
-                          aOrigin.y + sinf(aStartAngle) * aRadius.height);
+  Point currentStartOffset(cosf(aStartAngle), sinf(aStartAngle));
+  Point currentStartPoint(aOrigin.x + currentStartOffset.x * aRadius.width,
+                          aOrigin.y + currentStartOffset.y * aRadius.height);
 
   aSink->LineTo(currentStartPoint);
 
   while (arcSweepLeft > 0) {
-    // We guarantee here the current point is the start point of the next
-    // curve segment.
     Float currentEndAngle =
       currentStartAngle + std::min(arcSweepLeft, Float(M_PI / 2.0f)) * sweepDirection;
 
-    Point currentEndPoint(aOrigin.x + cosf(currentEndAngle) * aRadius.width,
-                          aOrigin.y + sinf(currentEndAngle) * aRadius.height);
-
-    // Calculate kappa constant for partial curve. The sign of angle in the
-    // tangent will actually ensure this is negative for a counter clockwise
-    // sweep, so changing signs later isn't needed.
-    Float kappaFactor = (4.0f / 3.0f) * tanf((currentEndAngle - currentStartAngle) / 4.0f);
-    Float kappaX = kappaFactor * aRadius.width;
-    Float kappaY = kappaFactor * aRadius.height;
+    Point currentEndOffset(cosf(currentEndAngle), sinf(currentEndAngle));
+    Point currentEndPoint(aOrigin.x + currentEndOffset.x * aRadius.width,
+                          aOrigin.y + currentEndOffset.y * aRadius.height);
 
-    Point tangentStart(-sinf(currentStartAngle), cosf(currentStartAngle));
-    Point cp1 = currentStartPoint;
-    cp1 += Point(tangentStart.x * kappaX, tangentStart.y * kappaY);
+    PartialArcToBezier(aSink, aRadius,
+                       currentStartPoint, currentEndPoint,
+                       currentStartOffset, currentEndOffset,
+                       ComputeKappaFactor(currentEndAngle - currentStartAngle));
 
-    Point revTangentEnd(sinf(currentEndAngle), -cosf(currentEndAngle));
-    Point cp2 = currentEndPoint;
-    cp2 += Point(revTangentEnd.x * kappaX, revTangentEnd.y * kappaY);
-
-    aSink->BezierTo(cp1, cp2, currentEndPoint);
-
+    // We guarantee here the current point is the start point of the next
+    // curve segment.
     arcSweepLeft -= Float(M_PI / 2.0f);
     currentStartAngle = currentEndAngle;
+    currentStartOffset = currentEndOffset;
     currentStartPoint = currentEndPoint;
   }
 }
 
 /* This is basically the ArcToBezier with the parameters for drawing a circle
  * inlined which vastly simplifies it and avoids a bunch of transcedental function
  * calls which should make it faster. */
 template <typename T>
 void EllipseToBezier(T* aSink, const Point &aOrigin, const Size &aRadius)
 {
-  Point startPoint(aOrigin.x + aRadius.width,
-                   aOrigin.y);
+  Point currentStartOffset(1, 0);
+  Point currentStartPoint(aOrigin.x + aRadius.width, aOrigin.y);
 
-  aSink->LineTo(startPoint);
+  aSink->LineTo(currentStartPoint);
 
-  // Calculate kappa constant for partial curve. The sign of angle in the
-  // tangent will actually ensure this is negative for a counter clockwise
-  // sweep, so changing signs later isn't needed.
-  Float kappaFactor = (4.0f / 3.0f) * tan((M_PI/2.0f) / 4.0f);
-  Float kappaX = kappaFactor * aRadius.width;
-  Float kappaY = kappaFactor * aRadius.height;
-  Float cosStartAngle = 1;
-  Float sinStartAngle = 0;
   for (int i = 0; i < 4; i++) {
+    // cos(x+pi/2) == -sin(x)
+    // sin(x+pi/2) == cos(x)
+    Point currentEndOffset(-currentStartOffset.y, currentStartOffset.x);
+    Point currentEndPoint(aOrigin.x + currentEndOffset.x * aRadius.width,
+                          aOrigin.y + currentEndOffset.y * aRadius.height);
+
+    PartialArcToBezier(aSink, aRadius,
+                       currentStartPoint, currentEndPoint,
+                       currentStartOffset, currentEndOffset);
+
     // We guarantee here the current point is the start point of the next
     // curve segment.
-    Point currentStartPoint(aOrigin.x + cosStartAngle * aRadius.width,
-                            aOrigin.y + sinStartAngle * aRadius.height);
-    Point currentEndPoint(aOrigin.x + -sinStartAngle * aRadius.width,
-                          aOrigin.y + cosStartAngle * aRadius.height);
-
-    Point tangentStart(-sinStartAngle, cosStartAngle);
-    Point cp1 = currentStartPoint;
-    cp1 += Point(tangentStart.x * kappaX, tangentStart.y * kappaY);
-
-    Point revTangentEnd(cosStartAngle, sinStartAngle);
-    Point cp2 = currentEndPoint;
-    cp2 += Point(revTangentEnd.x * kappaX, revTangentEnd.y * kappaY);
-
-    aSink->BezierTo(cp1, cp2, currentEndPoint);
-
-    // cos(x+pi/2) == -sin(x)
-    // sin(x+pi/2) == cos(x)
-    Float tmp = cosStartAngle;
-    cosStartAngle = -sinStartAngle;
-    sinStartAngle = tmp;
+    currentStartOffset = currentEndOffset;
+    currentStartPoint = currentEndPoint;
   }
 }
 
 /**
  * Appends a path represending a rectangle to the path being built by
  * aPathBuilder.
  *
  * aRect           The rectangle to append.