From 4098e33bc4c1efbbf7fcc1de407885ff3cdeb1b2 Mon Sep 17 00:00:00 2001 From: Vladimir Vukicevic Date: Wed, 23 Jul 2008 10:25:00 -0700 Subject: [PATCH] b=424423; border rendering is slow: add APIs to thebes; r=joe --- gfx/thebes/public/gfxContext.h | 30 ++++++- gfx/thebes/public/gfxRect.h | 70 ++++++++++++++++ gfx/thebes/src/gfxContext.cpp | 146 ++++++++++++++++++++++++++------- 3 files changed, 215 insertions(+), 31 deletions(-) diff --git a/gfx/thebes/public/gfxContext.h b/gfx/thebes/public/gfxContext.h index c39d7e2199bc..f743b1f7784e 100644 --- a/gfx/thebes/public/gfxContext.h +++ b/gfx/thebes/public/gfxContext.h @@ -174,10 +174,15 @@ public: void LineTo(const gfxPoint& pt); /** - * Draws a quadratic Bézier curve with control points pt1, pt2 and pt3. + * 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 QuadraticCurveTo(const gfxPoint& pt1, const gfxPoint& pt2); + /** * Draws a clockwise arc (i.e. a circle segment). * @param center The center of the circle @@ -210,9 +215,32 @@ public: * @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 **/ diff --git a/gfx/thebes/public/gfxRect.h b/gfx/thebes/public/gfxRect.h index b408af29bc5e..6d147f4bc5c2 100644 --- a/gfx/thebes/public/gfxRect.h +++ b/gfx/thebes/public/gfxRect.h @@ -41,6 +41,19 @@ #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; @@ -142,6 +155,19 @@ struct THEBES_API gfxRect { 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. @@ -174,4 +200,48 @@ struct THEBES_API gfxRect { } }; +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 */ diff --git a/gfx/thebes/src/gfxContext.cpp b/gfx/thebes/src/gfxContext.cpp index ca4acc2d7716..6ab903b8f655 100644 --- a/gfx/thebes/src/gfxContext.cpp +++ b/gfx/thebes/src/gfxContext.cpp @@ -172,6 +172,20 @@ gfxContext::CurveTo(const gfxPoint& pt1, const gfxPoint& pt2, const gfxPoint& pt 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) @@ -220,37 +234,11 @@ gfxContext::Rectangle(const gfxRect& rect, PRBool snapToPixels) void gfxContext::Ellipse(const gfxPoint& center, const gfxSize& dimensions) { - // circle? - if (dimensions.width == dimensions.height) { - double radius = dimensions.width / 2.0; + gfxSize halfDim = dimensions / 2.0; + gfxRect r(center - halfDim, dimensions); + gfxCornerSizes c(halfDim, halfDim, halfDim, halfDim); - 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); - - 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 @@ -796,3 +784,101 @@ 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); +}