use quads for mixed radius rrects

Create a specialized version of adding a pair of corner quads
that avoids the overhead of the full arc machinery.

This is on the way to changing Chrome to calling Skia directly to create fully general round rects rather than rolling their own.

R=robertphillips@google.com, reed@google.com

Author: caryclark@google.com

Review URL: https://codereview.chromium.org/60203002

git-svn-id: http://skia.googlecode.com/svn/trunk@12190 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
commit-bot@chromium.org 2013-11-08 15:51:12 +00:00
parent c78b8f2f73
commit 42feaaf0a5
2 changed files with 179 additions and 114 deletions

View File

@ -950,41 +950,6 @@ static int build_arc_points(const SkRect& oval, SkScalar startAngle,
&matrix, pts);
}
static void add_corner_arc(SkPath* path, const SkRect& rect,
SkScalar rx, SkScalar ry, int startAngle,
SkPath::Direction dir, bool forceMoveTo) {
// These two asserts are not sufficient, since really we want to know
// that the pair of radii (e.g. left and right, or top and bottom) sum
// to <= dimension, but we don't have that data here, so we just have
// these conservative asserts.
SkASSERT(0 <= rx && rx <= rect.width());
SkASSERT(0 <= ry && ry <= rect.height());
SkRect r;
r.set(-rx, -ry, rx, ry);
switch (startAngle) {
case 0:
r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
break;
case 90:
r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
break;
case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
}
SkScalar start = SkIntToScalar(startAngle);
SkScalar sweep = SkIntToScalar(90);
if (SkPath::kCCW_Direction == dir) {
start += sweep;
sweep = -sweep;
}
path->arcTo(r, start, sweep, forceMoveTo);
}
void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
Direction dir) {
SkRRect rrect;
@ -992,6 +957,131 @@ void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
this->addRRect(rrect, dir);
}
/* The inline clockwise and counterclockwise round rect quad approximations
make it easier to see the symmetry patterns used by add corner quads.
Clockwise corner value
path->lineTo(rect.fLeft, rect.fTop + ry); 0 upper left
path->quadTo(rect.fLeft, rect.fTop + offPtY,
rect.fLeft + midPtX, rect.fTop + midPtY);
path->quadTo(rect.fLeft + offPtX, rect.fTop,
rect.fLeft + rx, rect.fTop);
path->lineTo(rect.fRight - rx, rect.fTop); 1 upper right
path->quadTo(rect.fRight - offPtX, rect.fTop,
rect.fRight - midPtX, rect.fTop + midPtY);
path->quadTo(rect.fRight, rect.fTop + offPtY,
rect.fRight, rect.fTop + ry);
path->lineTo(rect.fRight, rect.fBottom - ry); 2 lower right
path->quadTo(rect.fRight, rect.fBottom - offPtY,
rect.fRight - midPtX, rect.fBottom - midPtY);
path->quadTo(rect.fRight - offPtX, rect.fBottom,
rect.fRight - rx, rect.fBottom);
path->lineTo(rect.fLeft + rx, rect.fBottom); 3 lower left
path->quadTo(rect.fLeft + offPtX, rect.fBottom,
rect.fLeft + midPtX, rect.fBottom - midPtY);
path->quadTo(rect.fLeft, rect.fBottom - offPtY,
rect.fLeft, rect.fBottom - ry);
Counterclockwise
path->lineTo(rect.fLeft, rect.fBottom - ry); 3 lower left
path->quadTo(rect.fLeft, rect.fBottom - offPtY,
rect.fLeft + midPtX, rect.fBottom - midPtY);
path->quadTo(rect.fLeft + offPtX, rect.fBottom,
rect.fLeft + rx, rect.fBottom);
path->lineTo(rect.fRight - rx, rect.fBottom); 2 lower right
path->quadTo(rect.fRight - offPtX, rect.fBottom,
rect.fRight - midPtX, rect.fBottom - midPtY);
path->quadTo(rect.fRight, rect.fBottom - offPtY,
rect.fRight, rect.fBottom - ry);
path->lineTo(rect.fRight, rect.fTop + ry); 1 upper right
path->quadTo(rect.fRight, rect.fTop + offPtY,
rect.fRight - midPtX, rect.fTop + midPtY);
path->quadTo(rect.fRight - offPtX, rect.fTop,
rect.fRight - rx, rect.fTop);
path->lineTo(rect.fLeft + rx, rect.fTop); 0 upper left
path->quadTo(rect.fLeft + offPtX, rect.fTop,
rect.fLeft + midPtX, rect.fTop + midPtY);
path->quadTo(rect.fLeft, rect.fTop + offPtY,
rect.fLeft, rect.fTop + ry);
*/
static void add_corner_quads(SkPath* path, const SkRRect& rrect,
SkRRect::Corner corner, SkPath::Direction dir) {
const SkRect& rect = rrect.rect();
const SkVector& radii = rrect.radii(corner);
SkScalar rx = radii.fX;
SkScalar ry = radii.fY;
// The mid point of the quadratic arc approximation is half way between the two
// control points.
SkScalar midPtX = rx - rx * (SK_Scalar1 + SK_ScalarTanPIOver8) / 2;
SkScalar midPtY = ry - ry * (SK_Scalar1 + SK_ScalarTanPIOver8) / 2;
SkScalar offPtX = rx - rx * SK_ScalarTanPIOver8;
SkScalar offPtY = ry - ry * SK_ScalarTanPIOver8;
static const int kCornerPts = 5;
SkScalar xOff[kCornerPts];
SkScalar yOff[kCornerPts];
if ((corner & 1) == (dir == SkPath::kCCW_Direction)) { // corners always alternate direction
SkASSERT(dir == SkPath::kCCW_Direction
? corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperRight_Corner
: corner == SkRRect::kUpperLeft_Corner || corner == SkRRect::kLowerRight_Corner);
xOff[0] = xOff[1] = 0;
xOff[2] = midPtX;
xOff[3] = offPtX;
xOff[4] = rx;
yOff[0] = ry;
yOff[1] = offPtY;
yOff[2] = midPtY;
yOff[3] = yOff[4] = 0;
} else {
xOff[0] = rx;
xOff[1] = offPtX;
xOff[2] = midPtX;
xOff[3] = xOff[4] = 0;
yOff[0] = yOff[1] = 0;
yOff[2] = midPtY;
yOff[3] = offPtY;
yOff[4] = ry;
}
if ((corner - 1) & 2) {
SkASSERT(corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperLeft_Corner);
for (int i = 0; i < kCornerPts; ++i) {
xOff[i] = rect.fLeft + xOff[i];
}
} else {
SkASSERT(corner == SkRRect::kLowerRight_Corner || corner == SkRRect::kUpperRight_Corner);
for (int i = 0; i < kCornerPts; ++i) {
xOff[i] = rect.fRight - xOff[i];
}
}
if (corner < SkRRect::kLowerRight_Corner) {
for (int i = 0; i < kCornerPts; ++i) {
yOff[i] = rect.fTop + yOff[i];
}
} else {
for (int i = 0; i < kCornerPts; ++i) {
yOff[i] = rect.fBottom - yOff[i];
}
}
SkPoint lastPt;
SkAssertResult(path->getLastPt(&lastPt));
if (lastPt.fX != xOff[0] || lastPt.fY != yOff[0]) {
path->lineTo(xOff[0], yOff[0]);
}
if (rx || ry) {
path->quadTo(xOff[1], yOff[1], xOff[2], yOff[2]);
path->quadTo(xOff[3], yOff[3], xOff[4], yOff[4]);
} else {
path->lineTo(xOff[2], yOff[2]);
path->lineTo(xOff[4], yOff[4]);
}
}
void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
assert_known_direction(dir);
@ -1005,22 +1095,32 @@ void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
this->addRect(bounds, dir);
} else if (rrect.isOval()) {
this->addOval(bounds, dir);
#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
} else if (rrect.isSimple()) {
const SkVector& rad = rrect.getSimpleRadii();
this->addRoundRect(bounds, rad.x(), rad.y(), dir);
#endif
} else {
SkAutoPathBoundsUpdate apbu(this, bounds);
fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
SkAutoPathBoundsUpdate apbu(this, bounds);
SkAutoDisableDirectionCheck(this);
this->incReserve(21);
if (kCW_Direction == dir) {
add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
this->moveTo(bounds.fLeft,
bounds.fBottom - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
} else {
add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
this->moveTo(bounds.fLeft,
bounds.fTop + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
}
this->close();
}
@ -1056,6 +1156,7 @@ void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
return;
}
#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
SkScalar w = rect.width();
SkScalar halfW = SkScalarHalf(w);
SkScalar h = rect.height();
@ -1083,133 +1184,68 @@ void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
} else if (skip_vert) {
ry = halfH;
}
#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
this->incReserve(17);
#else
// The mid point of the quadratic arc approximation is half way between the two
// control points. The float epsilon adjustment moves the on curve point out by
// two bits, distributing the convex test error between the round rect approximation
// and the convex cross product sign equality test.
SkScalar midPtX = rx * (SK_Scalar1 + SK_ScalarTanPIOver8 + FLT_EPSILON * 4) / 2;
SkScalar midPtY = ry * (SK_Scalar1 + SK_ScalarTanPIOver8 + FLT_EPSILON * 4) / 2;
SkScalar offPtX = rx * SK_ScalarTanPIOver8;
SkScalar offPtY = ry * SK_ScalarTanPIOver8;
this->incReserve(21);
#endif
this->moveTo(rect.fRight - rx, rect.fTop); // top-right
if (dir == kCCW_Direction) {
if (!skip_hori) {
this->lineTo(rect.fLeft + rx, rect.fTop); // top
}
#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
rect.fLeft, rect.fTop + ry - sy,
rect.fLeft, rect.fTop + ry); // top-left
#else
this->quadTo(rect.fLeft + rx - offPtX, rect.fTop,
rect.fLeft + rx - midPtX, rect.fTop + ry - midPtY);
this->quadTo(rect.fLeft, rect.fTop + ry - offPtY,
rect.fLeft, rect.fTop + ry);
#endif
if (!skip_vert) {
this->lineTo(rect.fLeft, rect.fBottom - ry); // left
}
#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
rect.fLeft + rx - sx, rect.fBottom,
rect.fLeft + rx, rect.fBottom); // bot-left
#else
this->quadTo(rect.fLeft, rect.fBottom - ry + offPtY,
rect.fLeft + rx - midPtX, rect.fBottom - ry + midPtY);
this->quadTo(rect.fLeft + rx - offPtX, rect.fBottom,
rect.fLeft + rx, rect.fBottom);
#endif
if (!skip_hori) {
this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
}
#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
rect.fRight, rect.fBottom - ry + sy,
rect.fRight, rect.fBottom - ry); // bot-right
#else
this->quadTo(rect.fRight - rx + offPtX, rect.fBottom,
rect.fRight - rx + midPtX, rect.fBottom - ry + midPtY);
this->quadTo(rect.fRight, rect.fBottom - ry + offPtY,
rect.fRight, rect.fBottom - ry);
#endif
if (!skip_vert) {
this->lineTo(rect.fRight, rect.fTop + ry); // right
}
#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
this->cubicTo(rect.fRight, rect.fTop + ry - sy,
rect.fRight - rx + sx, rect.fTop,
rect.fRight - rx, rect.fTop); // top-right
#else
this->quadTo(rect.fRight, rect.fTop + ry - offPtY,
rect.fRight - rx + midPtX, rect.fTop + ry - midPtY);
this->quadTo(rect.fRight - rx + offPtX, rect.fTop,
rect.fRight - rx, rect.fTop);
#endif
} else {
#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
this->cubicTo(rect.fRight - rx + sx, rect.fTop,
rect.fRight, rect.fTop + ry - sy,
rect.fRight, rect.fTop + ry); // top-right
#else
this->quadTo(rect.fRight - rx + offPtX, rect.fTop,
rect.fRight - rx + midPtX, rect.fTop + ry - midPtY);
this->quadTo(rect.fRight, rect.fTop + ry - offPtY,
rect.fRight, rect.fTop + ry);
#endif
if (!skip_vert) {
this->lineTo(rect.fRight, rect.fBottom - ry); // right
}
#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
rect.fRight - rx + sx, rect.fBottom,
rect.fRight - rx, rect.fBottom); // bot-right
#else
this->quadTo(rect.fRight, rect.fBottom - ry + offPtY,
rect.fRight - rx + midPtX, rect.fBottom - ry + midPtY);
this->quadTo(rect.fRight - rx + offPtX, rect.fBottom,
rect.fRight - rx, rect.fBottom);
#endif
if (!skip_hori) {
this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
}
#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
rect.fLeft, rect.fBottom - ry + sy,
rect.fLeft, rect.fBottom - ry); // bot-left
#else
this->quadTo(rect.fLeft + rx - offPtX, rect.fBottom,
rect.fLeft + rx - midPtX, rect.fBottom - ry + midPtY);
this->quadTo(rect.fLeft, rect.fBottom - ry + offPtY,
rect.fLeft, rect.fBottom - ry);
#endif
if (!skip_vert) {
this->lineTo(rect.fLeft, rect.fTop + ry); // left
}
#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
rect.fLeft + rx - sx, rect.fTop,
rect.fLeft + rx, rect.fTop); // top-left
#else
this->quadTo(rect.fLeft, rect.fTop + ry - offPtY,
rect.fLeft + rx - midPtX, rect.fTop + ry - midPtY);
this->quadTo(rect.fLeft + rx - offPtX, rect.fTop,
rect.fLeft + rx, rect.fTop);
#endif
if (!skip_hori) {
this->lineTo(rect.fRight - rx, rect.fTop); // top
}
}
this->close();
#else
SkRRect rrect;
rrect.setRectXY(rect, rx, ry);
this->addRRect(rrect, dir);
#endif
}
void SkPath::addOval(const SkRect& oval, Direction dir) {

View File

@ -14,6 +14,7 @@
#include "SkPathEffect.h"
#include "SkRandom.h"
#include "SkReader32.h"
#include "SkRRect.h"
#include "SkSize.h"
#include "SkSurface.h"
#include "SkTypes.h"
@ -2572,6 +2573,33 @@ static void test_empty(skiatest::Reporter* reporter, const SkPath& p) {
REPORTER_ASSERT(reporter, !(p != empty));
}
static void test_rrect_is_convex(skiatest::Reporter* reporter, SkPath* path) {
REPORTER_ASSERT(reporter, path->isConvex());
path->setConvexity(SkPath::kUnknown_Convexity);
REPORTER_ASSERT(reporter, path->isConvex());
path->reset();
}
static void test_rrect(skiatest::Reporter* reporter) {
SkPath p;
SkRRect rr;
SkVector radii[] = {{1, 2}, {3, 4}, {5, 6}, {7, 8}};
SkRect r = {10, 20, 30, 40};
rr.setRectRadii(r, radii);
p.addRRect(rr);
test_rrect_is_convex(reporter, &p);
p.addRRect(rr, SkPath::kCCW_Direction);
test_rrect_is_convex(reporter, &p);
p.addRoundRect(r, &radii[0].fX);
test_rrect_is_convex(reporter, &p);
p.addRoundRect(r, &radii[0].fX, SkPath::kCCW_Direction);
test_rrect_is_convex(reporter, &p);
p.addRoundRect(r, radii[1].fX, radii[1].fY);
test_rrect_is_convex(reporter, &p);
p.addRoundRect(r, radii[1].fX, radii[1].fY, SkPath::kCCW_Direction);
test_rrect_is_convex(reporter, &p);
}
static void TestPath(skiatest::Reporter* reporter) {
SkTSize<SkScalar>::Make(3,4);
@ -2673,6 +2701,7 @@ static void TestPath(skiatest::Reporter* reporter) {
test_gen_id(reporter);
test_path_close_issue1474(reporter);
test_path_to_region(reporter);
test_rrect(reporter);
}
#include "TestClassDef.h"