add convexity logic and tests for scalar max, Inf, and NaN

PathOps relies on isConvex() only returning true for trivially
convex paths. The old logic also returns true if the paths that
contain NaNs and Infinities. Return kUnknown_Convexity instead
in those cases and in cases where the convexity logic computes
intermediaries that overflow.

Review URL: https://codereview.chromium.org/784593002
This commit is contained in:
caryclark 2014-12-08 04:57:38 -08:00 committed by Commit bot
parent f4e0d9eb2d
commit d3d1a988b1
2 changed files with 102 additions and 5 deletions

View File

@ -2215,7 +2215,8 @@ struct Convexicator {
Convexicator()
: fPtCount(0)
, fConvexity(SkPath::kConvex_Convexity)
, fDirection(SkPath::kUnknown_Direction) {
, fDirection(SkPath::kUnknown_Direction)
, fIsFinite(true) {
fExpectedDir = kInvalid_DirChange;
// warnings
fLastPt.set(0, 0);
@ -2233,7 +2234,7 @@ struct Convexicator {
SkPath::Direction getDirection() const { return fDirection; }
void addPt(const SkPoint& pt) {
if (SkPath::kConcave_Convexity == fConvexity) {
if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
return;
}
@ -2242,7 +2243,10 @@ struct Convexicator {
++fPtCount;
} else {
SkVector vec = pt - fCurrPt;
if (!SkScalarNearlyZero(vec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero)) {
SkScalar lengthSqd = vec.lengthSqd();
if (!SkScalarIsFinite(lengthSqd)) {
fIsFinite = false;
} else if (!SkScalarNearlyZero(lengthSqd, SK_ScalarNearlyZero*SK_ScalarNearlyZero)) {
fLastPt = fCurrPt;
fCurrPt = pt;
if (++fPtCount == 2) {
@ -2272,6 +2276,10 @@ struct Convexicator {
}
}
bool isFinite() const {
return fIsFinite;
}
private:
void addVec(const SkVector& vec) {
SkASSERT(vec.fX || vec.fY);
@ -2310,6 +2318,7 @@ private:
SkPath::Convexity fConvexity;
SkPath::Direction fDirection;
int fDx, fDy, fSx, fSy;
bool fIsFinite;
};
SkPath::Convexity SkPath::internalGetConvexity() const {
@ -2322,6 +2331,9 @@ SkPath::Convexity SkPath::internalGetConvexity() const {
int count;
Convexicator state;
if (!isFinite()) {
return kUnknown_Convexity;
}
while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
switch (verb) {
case kMove_Verb:
@ -2350,6 +2362,9 @@ SkPath::Convexity SkPath::internalGetConvexity() const {
state.addPt(pts[i]);
}
// early exit
if (!state.isFinite()) {
return kUnknown_Convexity;
}
if (kConcave_Convexity == state.getConvexity()) {
fConvexity = kConcave_Convexity;
return kConcave_Convexity;

View File

@ -1308,6 +1308,79 @@ static void test_convexity(skiatest::Reporter* reporter) {
REPORTER_ASSERT(reporter, gRec[i].fExpectedConvexity == path.getConvexity());
check_direction(reporter, path, gRec[i].fExpectedDirection);
}
static const SkPoint nonFinitePts[] = {
{ SK_ScalarInfinity, 0 },
{ 0, SK_ScalarInfinity },
{ SK_ScalarInfinity, SK_ScalarInfinity },
{ SK_ScalarNegativeInfinity, 0},
{ 0, SK_ScalarNegativeInfinity },
{ SK_ScalarNegativeInfinity, SK_ScalarNegativeInfinity },
{ SK_ScalarNegativeInfinity, SK_ScalarInfinity },
{ SK_ScalarInfinity, SK_ScalarNegativeInfinity },
{ SK_ScalarNaN, 0 },
{ 0, SK_ScalarNaN },
{ SK_ScalarNaN, SK_ScalarNaN },
};
const size_t nonFinitePtsCount = sizeof(nonFinitePts) / sizeof(nonFinitePts[0]);
static const SkPoint finitePts[] = {
{ SK_ScalarMax, 0 },
{ 0, SK_ScalarMax },
{ SK_ScalarMax, SK_ScalarMax },
{ SK_ScalarMin, 0 },
{ 0, SK_ScalarMin },
{ SK_ScalarMin, SK_ScalarMin },
};
const size_t finitePtsCount = sizeof(finitePts) / sizeof(finitePts[0]);
for (int index = 0; index < (int) (13 * nonFinitePtsCount * finitePtsCount); ++index) {
int i = (int) (index % nonFinitePtsCount);
int f = (int) (index % finitePtsCount);
int g = (int) ((f + 1) % finitePtsCount);
path.reset();
switch (index % 13) {
case 0: path.lineTo(nonFinitePts[i]); break;
case 1: path.quadTo(nonFinitePts[i], nonFinitePts[i]); break;
case 2: path.quadTo(nonFinitePts[i], finitePts[f]); break;
case 3: path.quadTo(finitePts[f], nonFinitePts[i]); break;
case 4: path.cubicTo(nonFinitePts[i], finitePts[f], finitePts[f]); break;
case 5: path.cubicTo(finitePts[f], nonFinitePts[i], finitePts[f]); break;
case 6: path.cubicTo(finitePts[f], finitePts[f], nonFinitePts[i]); break;
case 7: path.cubicTo(nonFinitePts[i], nonFinitePts[i], finitePts[f]); break;
case 8: path.cubicTo(nonFinitePts[i], finitePts[f], nonFinitePts[i]); break;
case 9: path.cubicTo(finitePts[f], nonFinitePts[i], nonFinitePts[i]); break;
case 10: path.cubicTo(nonFinitePts[i], nonFinitePts[i], nonFinitePts[i]); break;
case 11: path.cubicTo(nonFinitePts[i], finitePts[f], finitePts[g]); break;
case 12: path.moveTo(nonFinitePts[i]); break;
}
check_convexity(reporter, path, SkPath::kUnknown_Convexity);
}
for (int index = 0; index < (int) (11 * finitePtsCount); ++index) {
int f = (int) (index % finitePtsCount);
int g = (int) ((f + 1) % finitePtsCount);
path.reset();
int curveSelect = index % 11;
switch (curveSelect) {
case 0: path.moveTo(finitePts[f]); break;
case 1: path.lineTo(finitePts[f]); break;
case 2: path.quadTo(finitePts[f], finitePts[f]); break;
case 3: path.quadTo(finitePts[f], finitePts[g]); break;
case 4: path.quadTo(finitePts[g], finitePts[f]); break;
case 5: path.cubicTo(finitePts[f], finitePts[f], finitePts[f]); break;
case 6: path.cubicTo(finitePts[f], finitePts[f], finitePts[g]); break;
case 7: path.cubicTo(finitePts[f], finitePts[g], finitePts[f]); break;
case 8: path.cubicTo(finitePts[f], finitePts[g], finitePts[g]); break;
case 9: path.cubicTo(finitePts[g], finitePts[f], finitePts[f]); break;
case 10: path.cubicTo(finitePts[g], finitePts[f], finitePts[g]); break;
}
check_convexity(reporter, path, curveSelect == 0 ? SkPath::kConvex_Convexity
: SkPath::kUnknown_Convexity);
}
}
static void test_isLine(skiatest::Reporter* reporter) {
@ -2959,6 +3032,15 @@ static void test_rrect_is_convex(skiatest::Reporter* reporter, SkPath* path,
path->reset();
}
static void test_rrect_convexity_is_unknown(skiatest::Reporter* reporter, SkPath* path,
SkPath::Direction dir) {
REPORTER_ASSERT(reporter, path->isConvex());
REPORTER_ASSERT(reporter, path->cheapIsDirection(dir));
path->setConvexity(SkPath::kUnknown_Convexity);
REPORTER_ASSERT(reporter, path->getConvexity() == SkPath::kUnknown_Convexity);
path->reset();
}
static void test_rrect(skiatest::Reporter* reporter) {
SkPath p;
SkRRect rr;
@ -3014,11 +3096,11 @@ static void test_rrect(skiatest::Reporter* reporter) {
SkRect largeR = {0, 0, SK_ScalarMax, SK_ScalarMax};
rr.setRectRadii(largeR, radii);
p.addRRect(rr);
test_rrect_is_convex(reporter, &p, SkPath::kCW_Direction);
test_rrect_convexity_is_unknown(reporter, &p, SkPath::kCW_Direction);
SkRect infR = {0, 0, SK_ScalarMax, SK_ScalarInfinity};
rr.setRectRadii(infR, radii);
p.addRRect(rr);
test_rrect_is_convex(reporter, &p, SkPath::kCW_Direction);
test_rrect_convexity_is_unknown(reporter, &p, SkPath::kCW_Direction);
SkRect tinyR = {0, 0, 1e-9f, 1e-9f};
p.addRoundRect(tinyR, 5e-11f, 5e-11f);
test_rrect_is_convex(reporter, &p, SkPath::kCW_Direction);