b=424423; border rendering is slow: add APIs to thebes; r=joe
authorVladimir Vukicevic <vladimir@pobox.com>
Wed, 23 Jul 2008 10:25:00 -0700
changeset 16149 7afdded71629633aafc53e73766a6a98c34172cd
parent 16148 403582828a52ea820cc2128b023efa2c163ae77d
child 16150 f34a9f851cd157f61e7e2c642a2b6c5610741800
push idunknown
push userunknown
push dateunknown
reviewersjoe
bugs424423
milestone1.9.1a2pre
b=424423; border rendering is slow: add APIs to thebes; r=joe
gfx/thebes/public/gfxContext.h
gfx/thebes/public/gfxRect.h
gfx/thebes/src/gfxContext.cpp
--- a/gfx/thebes/public/gfxContext.h
+++ b/gfx/thebes/public/gfxContext.h
@@ -169,19 +169,24 @@ public:
     /**
      * Draws a line from the current point to pt.
      *
      * @see MoveTo
      */
     void LineTo(const gfxPoint& pt);
 
     /**
+     * Draws a cubic Bézier curve with control points pt1, pt2 and pt3.
+     */
+    void CurveTo(const gfxPoint& pt1, const gfxPoint& pt2, const gfxPoint& pt3);
+
+    /**
      * Draws a quadratic Bézier curve with control points pt1, pt2 and pt3.
      */
-    void CurveTo(const gfxPoint& pt1, const gfxPoint& pt2, const gfxPoint& pt3);
+    void QuadraticCurveTo(const gfxPoint& pt1, const gfxPoint& pt2);
 
     /**
      * Draws a clockwise arc (i.e. a circle segment).
      * @param center The center of the circle
      * @param radius The radius of the circle
      * @param angle1 Starting angle for the segment
      * @param angle2 Ending angle
      */
@@ -205,19 +210,42 @@ public:
      */
     void Line(const gfxPoint& start, const gfxPoint& end); // XXX snapToPixels option?
 
     /**
      * Draws the rectangle given by rect.
      * @param snapToPixels ?
      */
     void Rectangle(const gfxRect& rect, PRBool snapToPixels = PR_FALSE);
+
+    /**
+     * Draw an ellipse at the center corner with the given dimensions.
+     * It extends dimensions.width / 2.0 in the horizontal direction
+     * from the center, and dimensions.height / 2.0 in the vertical
+     * direction.
+     */
     void Ellipse(const gfxPoint& center, const gfxSize& dimensions);
+
+    /**
+     * Draw a polygon from the given points
+     */
     void Polygon(const gfxPoint *points, PRUint32 numPoints);
 
+    /*
+     * Draw a rounded rectangle, with the given outer rect and
+     * corners.  The corners specify the radii of the two axes of an
+     * ellipse (the horizontal and vertical directions given by the
+     * width and height, respectively).  By default the ellipse is
+     * drawn in a clockwise direction; if draw_clockwise is PR_FALSE,
+     * then it's drawn counterclockwise.
+     */
+    void RoundedRectangle(const gfxRect& rect,
+                          const gfxCornerSizes& corners,
+                          PRBool draw_clockwise = PR_TRUE);
+
     /**
      ** Transformation Matrix manipulation
      **/
 
     /**
      * Adds a translation to the current matrix. This translation takes place
      * before the previously set transformations.
      */
--- a/gfx/thebes/public/gfxRect.h
+++ b/gfx/thebes/public/gfxRect.h
@@ -36,16 +36,29 @@
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef GFX_RECT_H
 #define GFX_RECT_H
 
 #include "gfxTypes.h"
 #include "gfxPoint.h"
 
+struct THEBES_API gfxCorner {
+    typedef int Corner;
+    enum {
+        // this order is important!
+        TOP_LEFT = 0,
+        TOP_RIGHT = 1,
+        BOTTOM_RIGHT = 2,
+        BOTTOM_LEFT = 3,
+        NUM_CORNERS = 4
+    };
+};
+
+
 struct THEBES_API gfxRect {
     // pt? point?
     gfxPoint pos;
     gfxSize size;
 
     gfxRect() {}
     gfxRect(const gfxRect& s) : pos(s.pos), size(s.size) {}
     gfxRect(const gfxPoint& _pos, const gfxSize& _size) : pos(_pos), size(_size) {}
@@ -137,16 +150,29 @@ struct THEBES_API gfxRect {
     void RoundOut();
 
     // grabbing specific points
     gfxPoint TopLeft() const { return gfxPoint(pos); }
     gfxPoint TopRight() const { return pos + gfxSize(size.width, 0.0); }
     gfxPoint BottomLeft() const { return pos + gfxSize(0.0, size.height); }
     gfxPoint BottomRight() const { return pos + size; }
 
+    gfxPoint Corner(gfxCorner::Corner corner) const {
+        switch (corner) {
+            case gfxCorner::TOP_LEFT: return TopLeft();
+            case gfxCorner::TOP_RIGHT: return TopRight();
+            case gfxCorner::BOTTOM_LEFT: return BottomLeft();
+            case gfxCorner::BOTTOM_RIGHT: return BottomRight();
+            default:
+                NS_ERROR("Invalid corner!");
+                break;
+        }
+        return gfxPoint(0.0, 0.0);
+    }
+
     /* Conditions this border to Cairo's max coordinate space.
      * The caller can check IsEmpty() after Condition() -- if it's TRUE,
      * the caller can possibly avoid doing any extra rendering.
      */
     void Condition();
 
     void Scale(gfxFloat k) {
         NS_ASSERTION(k >= 0.0, "Invalid (negative) scale factor");
@@ -169,9 +195,53 @@ struct THEBES_API gfxRect {
         NS_ASSERTION(k > 0.0, "Invalid (negative) scale factor");
         pos.x /= k;
         pos.y /= k;
         size.width /= k;
         size.height /= k;
     }
 };
 
+struct THEBES_API gfxCornerSizes {
+    gfxSize sizes[gfxCorner::NUM_CORNERS];
+
+    gfxCornerSizes () { }
+
+    gfxCornerSizes (gfxFloat v) {
+        for (int i = 0; i < gfxCorner::NUM_CORNERS; i++)
+            sizes[i].SizeTo(v, v);
+    }
+
+    gfxCornerSizes (gfxFloat tl, gfxFloat tr, gfxFloat br, gfxFloat bl) {
+        sizes[gfxCorner::TOP_LEFT].SizeTo(tl, tl);
+        sizes[gfxCorner::TOP_RIGHT].SizeTo(tr, tr);
+        sizes[gfxCorner::BOTTOM_RIGHT].SizeTo(br, br);
+        sizes[gfxCorner::BOTTOM_LEFT].SizeTo(bl, bl);
+    }
+
+    gfxCornerSizes (const gfxSize& tl, const gfxSize& tr, const gfxSize& br, const gfxSize& bl) {
+        sizes[gfxCorner::TOP_LEFT] = tl;
+        sizes[gfxCorner::TOP_RIGHT] = tr;
+        sizes[gfxCorner::BOTTOM_RIGHT] = br;
+        sizes[gfxCorner::BOTTOM_LEFT] = bl;
+    }
+
+    const gfxSize& operator[] (gfxCorner::Corner index) const {
+        return sizes[index];
+    }
+
+    gfxSize& operator[] (gfxCorner::Corner index) {
+        return sizes[index];
+    }
+
+    const gfxSize TopLeft() const { return sizes[gfxCorner::TOP_LEFT]; }
+    gfxSize& TopLeft() { return sizes[gfxCorner::TOP_LEFT]; }
+
+    const gfxSize TopRight() const { return sizes[gfxCorner::TOP_RIGHT]; }
+    gfxSize& TopRight() { return sizes[gfxCorner::TOP_RIGHT]; }
+
+    const gfxSize BottomLeft() const { return sizes[gfxCorner::BOTTOM_LEFT]; }
+    gfxSize& BottomLeft() { return sizes[gfxCorner::BOTTOM_LEFT]; }
+
+    const gfxSize BottomRight() const { return sizes[gfxCorner::BOTTOM_RIGHT]; }
+    gfxSize& BottomRight() { return sizes[gfxCorner::BOTTOM_RIGHT]; }
+};
 #endif /* GFX_RECT_H */
--- a/gfx/thebes/src/gfxContext.cpp
+++ b/gfx/thebes/src/gfxContext.cpp
@@ -168,16 +168,30 @@ gfxContext::LineTo(const gfxPoint& pt)
 
 void
 gfxContext::CurveTo(const gfxPoint& pt1, const gfxPoint& pt2, const gfxPoint& pt3)
 {
     cairo_curve_to(mCairo, pt1.x, pt1.y, pt2.x, pt2.y, pt3.x, pt3.y);
 }
 
 void
+gfxContext::QuadraticCurveTo(const gfxPoint& pt1, const gfxPoint& pt2)
+{
+    double cx, cy;
+    cairo_get_current_point(mCairo, &cx, &cy);
+    cairo_curve_to(mCairo,
+                   (cx + pt1.x * 2.0) / 3.0,
+                   (cy + pt1.y * 2.0) / 3.0,
+                   (pt1.x * 2.0 + pt2.x) / 3.0,
+                   (pt1.y * 2.0 + pt2.y) / 3.0,
+                   pt2.x,
+                   pt2.y);
+}
+
+void
 gfxContext::Arc(const gfxPoint& center, gfxFloat radius,
                 gfxFloat angle1, gfxFloat angle2)
 {
     cairo_arc(mCairo, center.x, center.y, radius, angle1, angle2);
 }
 
 void
 gfxContext::NegativeArc(const gfxPoint& center, gfxFloat radius,
@@ -215,47 +229,21 @@ gfxContext::Rectangle(const gfxRect& rec
     }
 
     cairo_rectangle(mCairo, rect.pos.x, rect.pos.y, rect.size.width, rect.size.height);
 }
 
 void
 gfxContext::Ellipse(const gfxPoint& center, const gfxSize& dimensions)
 {
-    // circle?
-    if (dimensions.width == dimensions.height) {
-        double radius = dimensions.width / 2.0;
-
-        cairo_arc(mCairo, center.x, center.y, radius, 0, 2.0 * M_PI);
-    } else {
-        double x = center.x;
-        double y = center.y;
-        double w = dimensions.width;
-        double h = dimensions.height;
-
-        cairo_new_path(mCairo);
-        cairo_move_to(mCairo, x + w/2.0, y);
+    gfxSize halfDim = dimensions / 2.0;
+    gfxRect r(center - halfDim, dimensions);
+    gfxCornerSizes c(halfDim, halfDim, halfDim, halfDim);
 
-        cairo_rel_curve_to(mCairo,
-                           0, 0,
-                           w / 2.0, 0,
-                           w / 2.0, h / 2.0);
-        cairo_rel_curve_to(mCairo,
-                           0, 0,
-                           0, h / 2.0,
-                           - w / 2.0, h / 2.0);
-        cairo_rel_curve_to(mCairo,
-                           0, 0,
-                           - w / 2.0, 0,
-                           - w / 2.0, - h / 2.0);
-        cairo_rel_curve_to(mCairo,
-                           0, 0,
-                           0, - h / 2.0,
-                           w / 2.0, - h / 2.0);
-    }
+    RoundedRectangle (r, c);
 }
 
 void
 gfxContext::Polygon(const gfxPoint *points, PRUint32 numPoints)
 {
     if (numPoints == 0)
         return;
 
@@ -791,8 +779,106 @@ gfxContext::GetFlattenedPath()
     return path;
 }
 
 PRBool
 gfxContext::HasError()
 {
      return cairo_status(mCairo) != CAIRO_STATUS_SUCCESS;
 }
+
+void
+gfxContext::RoundedRectangle(const gfxRect& rect,
+                             const gfxCornerSizes& corners,
+                             PRBool draw_clockwise)
+{
+    //
+    // For CW drawing, this looks like:
+    // 
+    //  ...******0**      1    C
+    //              ****
+    //                  ***    2
+    //                     **
+    //                       *
+    //                        *
+    //                         3
+    //                         *
+    //                         *
+    //
+    // Where 0, 1, 2, 3 are the control points of the Bezier curve for the corner,
+    // and C is the actual corner point.
+    //
+    // For details about representing an elliptical arc as a cubic Bezier curve,
+    // see http://www.spaceroots.org/documents/ellipse/elliptical-arc.pdf
+    //
+    // At the start of the loop, the current point is assumed to be
+    // the point adjacent to the top left corner on the top
+    // horizontal.  Note that corner indices start at the top left and
+    // continue clockwise, whereas in our loop i = 0 refers to the top
+    // right corner.
+    //
+    // When going CCW, the control points are swapped, and the first corner
+    // that's drawn is the top left (along with the top segment).
+
+    // This is (sqrt(7) - 1) / 3; this ends up falling out of the equations
+    // given in the above paper -- it's the value of alpha at the end of section
+    // 3.4.1 when n2 and n1 are 90 degrees apart.  For the various corners, the
+    // axes the sign of this value changes, or it might be 0 -- it's multiplied by
+    // the appropriate multiplier from the list before using.
+    const gfxFloat alpha = 0.54858377035486361;
+
+    typedef struct { gfxFloat a, b; } twoFloats;
+
+    twoFloats cwCornerMults[4] = { { -1,  0 },
+                                   {  0, -1 },
+                                   { +1,  0 },
+                                   {  0, +1 } };
+    twoFloats ccwCornerMults[4] = { { +1,  0 },
+                                    {  0, -1 },
+                                    { -1,  0 },
+                                    {  0, +1 } };
+
+    twoFloats *cornerMults = draw_clockwise ? cwCornerMults : ccwCornerMults;
+
+    gfxPoint pc, p0, p1, p2, p3;
+
+    if (draw_clockwise)
+        cairo_move_to(mCairo, rect.pos.x + corners[gfxCorner::TOP_LEFT].width, rect.pos.y);
+    else
+        cairo_move_to(mCairo, rect.pos.x + rect.size.width - corners[gfxCorner::TOP_RIGHT].width, rect.pos.y);
+
+    for (int i = 0; i < gfxCorner::NUM_CORNERS; i++) {
+        // the corner index -- either 1 2 3 0 (cw) or 0 3 2 1 (ccw)
+        int c = draw_clockwise ? ((i+1) % 4) : ((4-i) % 4);
+
+        // i+2 and i+3 respectively.  These are used to index into the corner
+        // multiplier table, and were deduced by calculating out the long form
+        // of each corner and finding a pattern in the signs and values.
+        int i2 = (i+2) % 4;
+        int i3 = (i+3) % 4;
+
+        pc = rect.Corner(c);
+
+        if (corners[c].width > 0.0 && corners[c].height > 0.0) {
+            p0.x = pc.x + cornerMults[i].a * corners[c].width;
+            p0.y = pc.y + cornerMults[i].b * corners[c].height;
+
+            p3.x = pc.x + cornerMults[i3].a * corners[c].width;
+            p3.y = pc.y + cornerMults[i3].b * corners[c].height;
+
+            p1.x = p0.x + alpha * cornerMults[i2].a * corners[c].width;
+            p1.y = p0.y + alpha * cornerMults[i2].b * corners[c].height;
+
+            p2.x = p3.x - alpha * cornerMults[i3].a * corners[c].width;
+            p2.y = p3.y - alpha * cornerMults[i3].b * corners[c].height;
+
+            cairo_line_to (mCairo, p0.x, p0.y);
+            cairo_curve_to (mCairo,
+                            p1.x, p1.y,
+                            p2.x, p2.y,
+                            p3.x, p3.y);
+        } else {
+            cairo_line_to (mCairo, pc.x, pc.y);
+        }
+    }
+
+    cairo_close_path (mCairo);
+}