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:
parent
5eb29448df
commit
c9b7c720dd
@ -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 }
|
||||
};
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
|
@ -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; });
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user