two pass convexity

This separates the existing convexity logic into
two passes. The first pass detects concavity by
counting the changes in direction.
The second pass computes the cross product to
see that all angles bend in the same direction, and
computes the dot product to see if the angle
doubles back on itself.

The second pass treats axis-aligned vectors
separately, and computes the dot and cross products
by comparing point values; it does not use arithmetic
to determine convexity, so it works with all finite
values.

A compile time switch enables returning concave
for co-linear diagonal points:
If successive points are not axis-aligned, and
those points are co-linear along a diagonal;
the path is treated as concave. This is conservative
but avoids paths that change convexity when the
are translated or scaled, since transforming the
path may cause the midpoint to shift to either
side of a line formed by the endpoints.

The compile time switch is set so that co-linear
diagonal points do not affect convexity. Note that
this permits shapes formerly considered concave, such
as stroked lines with round caps, to become convex;
this accounts for many of the GM differences.

A path may double back on itself and be convex;
for instance, a path containing a single line.

Path may have multiple initial moveTo verbs, or
trailing moveTo verbs, and still evaluate as convex.

A separate entry point, SkPathPriv::IsConvex()
allows passing an array of points instead of a path.

A legacy define has been checked into Chrome to
use the old code until layout tests have been
rebaselined.

R=reed@google.com,bsalomon@google.com
Bug:899689
Change-Id: I392bbe04836ffb19666ad92ab2a2404c56543019
Reviewed-on: https://skia-review.googlesource.com/c/173427
Reviewed-by: Mike Reed <reed@google.com>
Reviewed-by: Cary Clark <caryclark@google.com>
Commit-Queue: Cary Clark <caryclark@skia.org>
This commit is contained in:
Cary Clark 2018-12-12 14:50:23 -05:00 committed by Skia Commit-Bot
parent 5eb29448df
commit c9b7c720dd
5 changed files with 481 additions and 52 deletions

View File

@ -56,9 +56,13 @@ const SkPoint gPoints3[] = {
const SkPoint gPoints4[] = {
{ -6.0f, -50.0f },
{ 4.0f, -50.0f },
{ 5.0f, -25.0f },
#if SK_TREAT_COLINEAR_DIAGONAL_POINTS_AS_CONCAVE == 0
{ 5.0f, -25.0f }, // remove if collinear diagonal points are not concave
#endif
{ 6.0f, 0.0f },
{ 5.0f, 25.0f },
#if SK_TREAT_COLINEAR_DIAGONAL_POINTS_AS_CONCAVE == 0
{ 5.0f, 25.0f }, // remove if collinear diagonal points are not concave
#endif
{ 4.0f, 50.0f },
{ -4.0f, 50.0f }
};

View File

@ -2295,6 +2295,8 @@ bool SkPath::isValidImpl() const {
///////////////////////////////////////////////////////////////////////////////
#ifdef SK_LEGACY_PATH_CONVEXITY // for rebaselining Chrome
static int sign(SkScalar x) { return x < 0; }
#define kValueNeverReturnedBySign 2
@ -2578,6 +2580,331 @@ SkPath::Convexity SkPath::internalGetConvexity() const {
return this->getConvexityOrUnknown();
}
#else
static int sign(SkScalar x1, SkScalar x2) { SkASSERT(x1 != x2); return x2 < x1; }
static int sign(SkScalar x) { return x < 0; }
#define kValueNeverReturnedBySign 2
enum DirChange {
kUnknown_DirChange,
kLeft_DirChange,
kRight_DirChange,
kStraight_DirChange,
kConcave_DirChange, // if cross on diagonal is too small, assume concave
kBackwards_DirChange, // if double back, allow simple lines to be convex
kInvalid_DirChange
};
static bool almost_equal(SkScalar compA, SkScalar compB) {
// The error epsilon was empirically derived; worse case round rects
// with a mid point outset by 2x float epsilon in tests had an error
// of 12.
const int epsilon = 16;
if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
return false;
}
// no need to check for small numbers because SkPath::Iter has removed degenerate values
int aBits = SkFloatAs2sCompliment(compA);
int bBits = SkFloatAs2sCompliment(compB);
return aBits < bBits + epsilon && bBits < aBits + epsilon;
}
static DirChange same_sign(SkScalar curr, SkScalar last, SkScalar prior) {
return sign(curr, last) == sign(last, prior) ? kStraight_DirChange : kBackwards_DirChange;
}
// only valid for a single contour
struct Convexicator {
/** The direction returned is only valid if the path is determined convex */
SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
void setMovePt(const SkPoint& pt) {
fPriorPt = fLastPt = fCurrPt = pt;
}
bool addPt(const SkPoint& pt) {
if (fCurrPt == pt) {
return true;
}
fCurrPt = pt;
if (fPriorPt == fLastPt) { // should only be true for first non-zero vector
fFirstPt = pt;
fCurrAligned = pt.fX == fLastPt.fX || pt.fY == fLastPt.fY;
} else if (!this->addVec()) {
return false;
}
fPriorPt = fLastPt;
fLastPt = fCurrPt;
fLastAligned = fCurrAligned;
return true;
}
static SkPath::Convexity BySign(const SkPoint points[], int count) {
const SkPoint* last = points + count;
SkPoint currPt = *points++;
SkPoint firstPt = currPt;
int dxes = 0;
int dyes = 0;
int lastSx = kValueNeverReturnedBySign;
int lastSy = kValueNeverReturnedBySign;
for (int outerLoop = 0; outerLoop < 2; ++outerLoop ) {
while (points != last) {
SkVector vec = *points - currPt;
if (!vec.isZero()) {
// give up if vector construction failed
if (!vec.isFinite()) {
return SkPath::kUnknown_Convexity;
}
int sx = sign(vec.fX);
int sy = sign(vec.fY);
dxes += (sx != lastSx);
dyes += (sy != lastSy);
if (dxes > 3 || dyes > 3) {
return SkPath::kConcave_Convexity;
}
lastSx = sx;
lastSy = sy;
}
currPt = *points++;
if (outerLoop) {
break;
}
}
points = &firstPt;
}
return SkPath::kConvex_Convexity; // that is, it may be convex, don't know yet
}
bool close() {
return this->addPt(fFirstPt);
}
bool isFinite() const {
return fIsFinite;
}
int reversals() const {
return fReversals;
}
private:
DirChange directionChange() {
// if both vectors are axis-aligned, don't do cross product
fCurrAligned = fCurrPt.fX == fLastPt.fX || fCurrPt.fY == fLastPt.fY;
if (fLastAligned && fCurrAligned) {
bool noYChange = fCurrPt.fY == fLastPt.fY && fLastPt.fY == fPriorPt.fY;
if (fCurrPt.fX == fLastPt.fX && fLastPt.fX == fPriorPt.fX) {
if (noYChange) {
return kStraight_DirChange;
}
return same_sign(fCurrPt.fY, fLastPt.fY, fPriorPt.fY);
}
if (!noYChange) { // must be turn to left or right
bool flip = fCurrPt.fX != fLastPt.fX;
SkASSERT(flip ? fCurrPt.fY == fLastPt.fY &&
fLastPt.fY != fPriorPt.fY && fLastPt.fX == fPriorPt.fX :
fCurrPt.fY != fLastPt.fY &&
fLastPt.fY == fPriorPt.fY && fLastPt.fX != fPriorPt.fX);
bool product = flip ? (fCurrPt.fX > fLastPt.fX) != (fLastPt.fY > fPriorPt.fY) :
(fCurrPt.fY > fLastPt.fY) == (fLastPt.fX > fPriorPt.fX);
SkDEBUGCODE(SkVector lastV = fLastPt - fPriorPt);
SkDEBUGCODE(SkVector curV = fCurrPt - fLastPt);
SkDEBUGCODE(SkScalar crossV = SkPoint::CrossProduct(lastV, curV));
SkDEBUGCODE(int signV = SkScalarSignAsInt(crossV));
SkASSERT(signV == (product ? 1 : -1));
return product ? kRight_DirChange : kLeft_DirChange;
}
return same_sign(fCurrPt.fX, fLastPt.fX, fPriorPt.fX);
}
// there are no subtractions above this line; axis aligned paths
// are robust and can handle arbitrary values
SkVector lastVec = fLastPt - fPriorPt;
SkVector curVec = fCurrPt - fLastPt;
SkScalar cross = SkPoint::CrossProduct(lastVec, curVec);
if (!SkScalarIsFinite(cross)) {
return kUnknown_DirChange;
}
SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
largest = SkTMax(largest, -smallest);
if (almost_equal(largest, largest + cross)) {
#if SK_TREAT_COLINEAR_DIAGONAL_POINTS_AS_CONCAVE
// colinear diagonals are not allowed; they aren't numerically stable
#define COLINEAR_POINT_DIR_CHANGE kConcave_DirChange
#else
// colinear diagonals are allowed; we can survive dealing with 'close enough'
#define COLINEAR_POINT_DIR_CHANGE kStraight_DirChange
#endif
SkScalar dot = lastVec.dot(curVec);
return dot < 0 ? kBackwards_DirChange : COLINEAR_POINT_DIR_CHANGE;
}
return 1 == SkScalarSignAsInt(cross) ? kRight_DirChange : kLeft_DirChange;
}
bool addVec() {
DirChange dir = this->directionChange();
switch (dir) {
case kLeft_DirChange: // fall through
case kRight_DirChange:
if (kInvalid_DirChange == fExpectedDir) {
fExpectedDir = dir;
fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
: SkPathPriv::kCCW_FirstDirection;
} else if (dir != fExpectedDir) {
fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
return false;
}
break;
case kStraight_DirChange:
break;
case kConcave_DirChange:
fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
return false;
case kBackwards_DirChange:
// allow path to reverse direction twice
// Given path.moveTo(0, 0); path.lineTo(1, 1);
// - 1st reversal: direction change formed by line (0,0 1,1), line (1,1 0,0)
// - 2nd reversal: direction change formed by line (1,1 0,0), line (0,0 1,1)
return ++fReversals < 3;
case kUnknown_DirChange:
return (fIsFinite = false);
case kInvalid_DirChange:
SK_ABORT("Use of invalid direction change flag");
break;
}
return true;
}
SkPoint fFirstPt {0, 0};
SkPoint fPriorPt {0, 0};
SkPoint fLastPt {0, 0};
SkPoint fCurrPt {0, 0};
DirChange fExpectedDir { kInvalid_DirChange };
SkPathPriv::FirstDirection fFirstDirection { SkPathPriv::kUnknown_FirstDirection };
int fReversals { 0 };
bool fIsFinite { true };
bool fLastAligned { true };
bool fCurrAligned { true };
};
SkPath::Convexity SkPath::internalGetConvexity() const {
SkPoint pts[4];
SkPath::Verb verb;
SkPath::Iter iter(*this, true);
auto setComputedConvexity = [=](Convexity convexity){
SkASSERT(kUnknown_Convexity != convexity);
this->setConvexity(convexity);
return convexity;
};
// Check to see if path changes direction more than three times as quick concave test
int pointCount = fLastMoveToIndex > 0 ? fLastMoveToIndex : this->countPoints();
if (pointCount > 3) {
const SkPoint* points = fPathRef->points();
const SkPoint* last = &points[pointCount];
// only consider the last of the initial move tos
while (SkPath::kMove_Verb == iter.next(pts, false, false)) {
++points;
}
--points;
SkPath::Convexity convexity = Convexicator::BySign(points, (int) (last - points));
if (SkPath::kConcave_Convexity == convexity) {
return setComputedConvexity(SkPath::kConcave_Convexity);
} else if (SkPath::kUnknown_Convexity == convexity) {
return SkPath::kUnknown_Convexity;
}
iter.setPath(*this, true);
} else if (!this->isFinite()) {
return kUnknown_Convexity;
}
int contourCount = 0;
int count;
Convexicator state;
auto setFail = [=](){
if (!state.isFinite()) {
return SkPath::kUnknown_Convexity;
}
return setComputedConvexity(SkPath::kConcave_Convexity);
};
while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) {
switch (verb) {
case kMove_Verb:
if (++contourCount > 1) {
return setComputedConvexity(kConcave_Convexity);
}
state.setMovePt(pts[0]);
count = 0;
break;
case kLine_Verb:
count = 1;
break;
case kQuad_Verb:
// fall through
case kConic_Verb:
count = 2;
break;
case kCubic_Verb:
count = 3;
break;
case kClose_Verb:
if (!state.close()) {
return setFail();
}
count = 0;
break;
default:
SkDEBUGFAIL("bad verb");
return setComputedConvexity(kConcave_Convexity);
}
for (int i = 1; i <= count; i++) {
if (!state.addPt(pts[i])) {
return setFail();
}
}
}
if (this->getFirstDirection() == SkPathPriv::kUnknown_FirstDirection) {
if (state.getFirstDirection() == SkPathPriv::kUnknown_FirstDirection
&& !this->getBounds().isEmpty()) {
return setComputedConvexity(state.reversals() < 3 ?
kConvex_Convexity : kConcave_Convexity);
}
this->setFirstDirection(state.getFirstDirection());
}
return setComputedConvexity(kConvex_Convexity);
}
bool SkPathPriv::IsConvex(const SkPoint points[], int count) {
SkPath::Convexity convexity = Convexicator::BySign(points, count);
if (SkPath::kConvex_Convexity != convexity) {
return false;
}
Convexicator state;
state.setMovePt(points[0]);
for (int i = 1; i < count; i++) {
if (!state.addPt(points[i])) {
return false;
}
}
if (!state.addPt(points[0])) {
return false;
}
if (!state.close()) {
return false;
}
return state.getFirstDirection() != SkPathPriv::kUnknown_FirstDirection
|| state.reversals() < 3;
}
#endif
///////////////////////////////////////////////////////////////////////////////
class ContourIter {

View File

@ -10,6 +10,14 @@
#include "SkPath.h"
#define SK_TREAT_COLINEAR_DIAGONAL_POINTS_AS_CONCAVE 0
#if SK_TREAT_COLINEAR_DIAGONAL_POINTS_AS_CONCAVE
#define COLINEAR_DIAGONAL_CONVEXITY kConcave_Convexity
#else
#define COLINEAR_DIAGONAL_CONVEXITY kConvex_Convexity
#endif
class SkPathPriv {
public:
#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
@ -159,6 +167,15 @@ public:
return path.fPathRef->conicWeights();
}
/** Returns true if path formed by pts is convex.
@param pts SkPoint array of path
@param count number of entries in array
@return true if pts represent a convex geometry
*/
static bool IsConvex(const SkPoint pts[], int count);
/** Returns true if the underlying SkPathRef has one single owner. */
static bool TestingOnly_unique(const SkPath& path) {
return path.fPathRef->unique();

View File

@ -27,6 +27,7 @@
#include <cmath>
#include <utility>
#include <vector>
static void set_radii(SkVector radii[4], int index, float rad) {
sk_bzero(radii, sizeof(SkVector) * 4);
@ -1319,6 +1320,21 @@ static void check_convexity(skiatest::Reporter* reporter, const SkPath& path,
SkPath copy(path); // we make a copy so that we don't cache the result on the passed in path.
SkPath::Convexity c = copy.getConvexity();
REPORTER_ASSERT(reporter, c == expected);
// test points-by-array interface
SkPath::Iter iter(path, true);
int initialMoves = 0;
SkPoint pts[4];
while (SkPath::kMove_Verb == iter.next(pts, false, false)) {
++initialMoves;
}
if (initialMoves > 0) {
std::vector<SkPoint> points;
points.resize(path.getPoints(nullptr, 0));
(void) path.getPoints(&points.front(), points.size());
int skip = initialMoves - 1;
bool isConvex = SkPathPriv::IsConvex(&points.front() + skip, points.size() - skip);
REPORTER_ASSERT(reporter, isConvex == (SkPath::kConvex_Convexity == expected));
}
}
static void test_path_crbug389050(skiatest::Reporter* reporter) {
@ -1329,8 +1345,16 @@ static void test_path_crbug389050(skiatest::Reporter* reporter) {
tinyConvexPolygon.lineTo(600.134891f, 800.137724f);
tinyConvexPolygon.close();
tinyConvexPolygon.getConvexity();
check_convexity(reporter, tinyConvexPolygon, SkPath::kConvex_Convexity);
check_convexity(reporter, tinyConvexPolygon, SkPath::COLINEAR_DIAGONAL_CONVEXITY);
#if SK_TREAT_COLINEAR_DIAGONAL_POINTS_AS_CONCAVE
// colinear diagonal points cause convexicator to give up, so CheapComputeFirstDirection
// makes its best guess
check_direction(reporter, tinyConvexPolygon, SkPathPriv::kCW_FirstDirection);
#else
// lines are close enough to straight that polygon collapses to line that does not
// enclose area, so has unknown first direction
check_direction(reporter, tinyConvexPolygon, SkPathPriv::kUnknown_FirstDirection);
#endif
SkPath platTriangle;
platTriangle.moveTo(0, 0);
@ -1458,7 +1482,7 @@ static void test_convexity2(skiatest::Reporter* reporter) {
SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle);
stroke.setStrokeStyle(2 * SK_Scalar1);
stroke.applyToPath(&strokedSin, strokedSin);
check_convexity(reporter, strokedSin, SkPath::kConcave_Convexity);
check_convexity(reporter, strokedSin, SkPath::COLINEAR_DIAGONAL_CONVEXITY);
check_direction(reporter, strokedSin, kDontCheckDir);
// http://crbug.com/412640
@ -1487,6 +1511,29 @@ static void test_convexity2(skiatest::Reporter* reporter) {
check_convexity(reporter, badFirstVector, SkPath::kConcave_Convexity);
}
static void test_convexity_doubleback(skiatest::Reporter* reporter) {
SkPath doubleback;
doubleback.lineTo(1, 1);
check_convexity(reporter, doubleback, SkPath::kConvex_Convexity);
doubleback.lineTo(2, 2);
check_convexity(reporter, doubleback, SkPath::COLINEAR_DIAGONAL_CONVEXITY);
doubleback.reset();
doubleback.lineTo(1, 0);
check_convexity(reporter, doubleback, SkPath::kConvex_Convexity);
doubleback.lineTo(2, 0);
check_convexity(reporter, doubleback, SkPath::kConvex_Convexity);
doubleback.lineTo(1, 0);
check_convexity(reporter, doubleback, SkPath::kConvex_Convexity);
doubleback.reset();
doubleback.quadTo(1, 1, 2, 2);
check_convexity(reporter, doubleback, SkPath::COLINEAR_DIAGONAL_CONVEXITY);
doubleback.reset();
doubleback.quadTo(1, 0, 2, 0);
check_convexity(reporter, doubleback, SkPath::kConvex_Convexity);
doubleback.quadTo(1, 0, 0, 0);
check_convexity(reporter, doubleback, SkPath::kConvex_Convexity);
}
static void check_convex_bounds(skiatest::Reporter* reporter, const SkPath& p,
const SkRect& bounds) {
REPORTER_ASSERT(reporter, p.isConvex());
@ -1541,8 +1588,8 @@ static void test_convexity(skiatest::Reporter* reporter) {
REPORTER_ASSERT(reporter, SkPathPriv::CheapIsFirstDirection(path, SkPathPriv::kCW_FirstDirection));
path.reset();
path.quadTo(100, 100, 50, 50); // This is a convex path from GM:convexpaths
check_convexity(reporter, path, SkPath::kConvex_Convexity);
path.quadTo(100, 100, 50, 50); // This from GM:convexpaths
check_convexity(reporter, path, SkPath::COLINEAR_DIAGONAL_CONVEXITY);
static const struct {
const char* fPathStr;
@ -1560,7 +1607,7 @@ static void test_convexity(skiatest::Reporter* reporter) {
};
for (size_t i = 0; i < SK_ARRAY_COUNT(gRec); ++i) {
SkPath path;
path.reset();
setFromString(&path, gRec[i].fPathStr);
check_convexity(reporter, path, gRec[i].fExpectedConvexity);
check_direction(reporter, path, gRec[i].fExpectedDirection);
@ -1594,62 +1641,102 @@ static void test_convexity(skiatest::Reporter* reporter) {
const size_t nonFinitePtsCount = sizeof(nonFinitePts) / sizeof(nonFinitePts[0]);
static const SkPoint finitePts[] = {
static const SkPoint axisAlignedPts[] = {
{ 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]);
const size_t axisAlignedPtsCount = sizeof(axisAlignedPts) / sizeof(axisAlignedPts[0]);
for (int index = 0; index < (int) (13 * nonFinitePtsCount * finitePtsCount); ++index) {
for (int index = 0; index < (int) (13 * nonFinitePtsCount * axisAlignedPtsCount); ++index) {
int i = (int) (index % nonFinitePtsCount);
int f = (int) (index % finitePtsCount);
int g = (int) ((f + 1) % finitePtsCount);
int f = (int) (index % axisAlignedPtsCount);
int g = (int) ((f + 1) % axisAlignedPtsCount);
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 2: path.quadTo(nonFinitePts[i], axisAlignedPts[f]); break;
case 3: path.quadTo(axisAlignedPts[f], nonFinitePts[i]); break;
case 4: path.cubicTo(nonFinitePts[i], axisAlignedPts[f], axisAlignedPts[f]); break;
case 5: path.cubicTo(axisAlignedPts[f], nonFinitePts[i], axisAlignedPts[f]); break;
case 6: path.cubicTo(axisAlignedPts[f], axisAlignedPts[f], nonFinitePts[i]); break;
case 7: path.cubicTo(nonFinitePts[i], nonFinitePts[i], axisAlignedPts[f]); break;
case 8: path.cubicTo(nonFinitePts[i], axisAlignedPts[f], nonFinitePts[i]); break;
case 9: path.cubicTo(axisAlignedPts[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 11: path.cubicTo(nonFinitePts[i], axisAlignedPts[f], axisAlignedPts[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);
for (int index = 0; index < (int) (11 * axisAlignedPtsCount); ++index) {
int f = (int) (index % axisAlignedPtsCount);
int g = (int) ((f + 1) % axisAlignedPtsCount);
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;
case 0: path.moveTo(axisAlignedPts[f]); break;
case 1: path.lineTo(axisAlignedPts[f]); break;
case 2: path.quadTo(axisAlignedPts[f], axisAlignedPts[f]); break;
case 3: path.quadTo(axisAlignedPts[f], axisAlignedPts[g]); break;
case 4: path.quadTo(axisAlignedPts[g], axisAlignedPts[f]); break;
case 5: path.cubicTo(axisAlignedPts[f], axisAlignedPts[f], axisAlignedPts[f]); break;
case 6: path.cubicTo(axisAlignedPts[f], axisAlignedPts[f], axisAlignedPts[g]); break;
case 7: path.cubicTo(axisAlignedPts[f], axisAlignedPts[g], axisAlignedPts[f]); break;
case 8: path.cubicTo(axisAlignedPts[f], axisAlignedPts[g], axisAlignedPts[g]); break;
case 9: path.cubicTo(axisAlignedPts[g], axisAlignedPts[f], axisAlignedPts[f]); break;
case 10: path.cubicTo(axisAlignedPts[g], axisAlignedPts[f], axisAlignedPts[g]); break;
}
if (curveSelect == 0 || curveSelect == 1 || curveSelect == 2 || curveSelect == 5) {
check_convexity(reporter, path, SkPath::kConvex_Convexity);
} else {
SkPath copy(path); // we make a copy so that we don't cache the result on the passed in path.
SkPath::Convexity c = copy.getConvexity();
REPORTER_ASSERT(reporter, SkPath::kUnknown_Convexity == c
|| SkPath::kConcave_Convexity == c);
}
check_convexity(reporter, path, curveSelect == 0 ? SkPath::kConvex_Convexity
: SkPath::kUnknown_Convexity);
}
static const SkPoint diagonalPts[] = {
{ SK_ScalarMax, SK_ScalarMax },
{ SK_ScalarMin, SK_ScalarMin },
};
const size_t diagonalPtsCount = sizeof(diagonalPts) / sizeof(diagonalPts[0]);
for (int index = 0; index < (int) (7 * diagonalPtsCount); ++index) {
int f = (int) (index % diagonalPtsCount);
int g = (int) ((f + 1) % diagonalPtsCount);
path.reset();
int curveSelect = index % 11;
switch (curveSelect) {
case 0: path.moveTo(diagonalPts[f]); break;
case 1: path.lineTo(diagonalPts[f]); break;
case 2: path.quadTo(diagonalPts[f], diagonalPts[f]); break;
case 3: path.quadTo(axisAlignedPts[f], diagonalPts[g]); break;
case 4: path.quadTo(diagonalPts[g], axisAlignedPts[f]); break;
case 5: path.cubicTo(diagonalPts[f], diagonalPts[f], diagonalPts[f]); break;
case 6: path.cubicTo(diagonalPts[f], diagonalPts[f], axisAlignedPts[g]); break;
case 7: path.cubicTo(diagonalPts[f], axisAlignedPts[g], diagonalPts[f]); break;
case 8: path.cubicTo(axisAlignedPts[f], diagonalPts[g], diagonalPts[g]); break;
case 9: path.cubicTo(diagonalPts[g], diagonalPts[f], axisAlignedPts[f]); break;
case 10: path.cubicTo(diagonalPts[g], axisAlignedPts[f], diagonalPts[g]); break;
}
if (curveSelect == 0) {
check_convexity(reporter, path, SkPath::kConvex_Convexity);
} else {
SkPath copy(path); // we make a copy so that we don't cache the result on the passed in path.
SkPath::Convexity c = copy.getConvexity();
REPORTER_ASSERT(reporter, SkPath::kUnknown_Convexity == c
|| SkPath::kConcave_Convexity == c);
}
}
path.reset();
path.moveTo(SkBits2Float(0xbe9171db), SkBits2Float(0xbd7eeb5d)); // -0.284072f, -0.0622362f
path.lineTo(SkBits2Float(0xbe9171db), SkBits2Float(0xbd7eea38)); // -0.284072f, -0.0622351f
@ -3523,7 +3610,7 @@ static void test_rrect_convexity_is_unknown(skiatest::Reporter* reporter, SkPath
REPORTER_ASSERT(reporter, path->isConvex());
REPORTER_ASSERT(reporter, SkPathPriv::CheapIsFirstDirection(*path, SkPathPriv::AsFirstDirection(dir)));
path->setConvexity(SkPath::kUnknown_Convexity);
REPORTER_ASSERT(reporter, path->getConvexity() == SkPath::kUnknown_Convexity);
REPORTER_ASSERT(reporter, path->getConvexity() == SkPath::kConvex_Convexity);
path->reset();
}
@ -3619,10 +3706,13 @@ static void test_arc(skiatest::Reporter* reporter) {
REPORTER_ASSERT(reporter, p == ccwOval);
p.reset();
p.addArc(oval, 1, 180);
REPORTER_ASSERT(reporter, p.isConvex());
// diagonal colinear points make arc convex
// TODO: one way to keep it concave would be to introduce interpolated on curve points
// between control points and computing the on curve point at scan conversion time
REPORTER_ASSERT(reporter, p.getConvexity() == SkPath::COLINEAR_DIAGONAL_CONVEXITY);
REPORTER_ASSERT(reporter, SkPathPriv::CheapIsFirstDirection(p, SkPathPriv::kCW_FirstDirection));
p.setConvexity(SkPath::kUnknown_Convexity);
REPORTER_ASSERT(reporter, p.isConvex());
REPORTER_ASSERT(reporter, p.getConvexity() == SkPath::COLINEAR_DIAGONAL_CONVEXITY);
}
static inline SkScalar oval_start_index_to_angle(unsigned start) {
@ -4623,6 +4713,7 @@ DEF_TEST(Paths, reporter) {
test_direction(reporter);
test_convexity(reporter);
test_convexity2(reporter);
test_convexity_doubleback(reporter);
test_conservativelyContains(reporter);
test_close(reporter);
test_segment_masks(reporter);
@ -5304,4 +5395,3 @@ DEF_TEST(Path_survive_transform, r) {
REPORTER_ASSERT(r, path.getConvexity() == SkPath::kConvex_Convexity);
survive(&path, x, false, r, [](const SkPath& p) { return true; });
}

View File

@ -340,14 +340,6 @@ static SkPath create_path_21() {
return path;
}
// A quad which becomes NaN when interpolated.
static SkPath create_path_22() {
SkPath path;
path.moveTo(-5.71889e+13f, 1.36759e+09f);
path.quadTo(2.45472e+19f, -3.12406e+15f, -2.19589e+18f, 2.79462e+14f);
return path;
}
// A path which contains out-of-range colinear intersections.
static SkPath create_path_23() {
SkPath path;
@ -713,7 +705,6 @@ DEF_GPUTEST_FOR_ALL_CONTEXTS(TessellatingPathRendererTests, reporter, ctxInfo) {
test_path(ctx, rtc.get(), create_path_19());
test_path(ctx, rtc.get(), create_path_20(), SkMatrix(), GrAAType::kCoverage);
test_path(ctx, rtc.get(), create_path_21(), SkMatrix(), GrAAType::kCoverage);
test_path(ctx, rtc.get(), create_path_22());
test_path(ctx, rtc.get(), create_path_23());
test_path(ctx, rtc.get(), create_path_24());
test_path(ctx, rtc.get(), create_path_25(), SkMatrix(), GrAAType::kCoverage);