aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--include/core/SkPath.h58
-rw-r--r--src/core/SkPath.cpp118
-rw-r--r--src/gpu/GrAAConvexPathRenderer.cpp6
-rw-r--r--tests/PathTest.cpp213
4 files changed, 281 insertions, 114 deletions
diff --git a/include/core/SkPath.h b/include/core/SkPath.h
index be5f612ef2..ee02c6546b 100644
--- a/include/core/SkPath.h
+++ b/include/core/SkPath.h
@@ -106,15 +106,15 @@ public:
};
/**
- * Return the path's convexity, as stored in the path. If it is currently
- * unknown, and the computeIfUnknown bool is true, then this will first
- * call ComputeConvexity() and then return that (cached) value.
+ * Return the path's convexity, as stored in the path. If it is currently unknown,
+ * then this function will attempt to compute the convexity (and cache the result).
*/
Convexity getConvexity() const {
- if (kUnknown_Convexity == fConvexity) {
- fConvexity = (uint8_t)ComputeConvexity(*this);
+ if (kUnknown_Convexity != fConvexity) {
+ return static_cast<Convexity>(fConvexity);
+ } else {
+ return this->internalGetConvexity();
}
- return (Convexity)fConvexity;
}
/**
@@ -127,8 +127,8 @@ public:
/**
* Store a convexity setting in the path. There is no automatic check to
- * see if this value actually agress with the return value from
- * ComputeConvexity().
+ * see if this value actually agrees with the return value that would be
+ * computed by getConvexity().
*
* Note: even if this is set to a "known" value, if the path is later
* changed (e.g. lineTo(), addRect(), etc.) then the cached value will be
@@ -137,20 +137,6 @@ public:
void setConvexity(Convexity);
/**
- * Compute the convexity of the specified path. This does not look at the
- * value stored in the path, but computes it directly from the path's data.
- *
- * This never returns kUnknown_Convexity.
- *
- * If there is more than one contour, this returns kConcave_Convexity.
- * If the contour is degenerate (e.g. there are fewer than 3 non-degenerate
- * segments), then this returns kConvex_Convexity.
- * The contour is treated as if it were closed, even if there is no kClose
- * verb.
- */
- static Convexity ComputeConvexity(const SkPath&);
-
- /**
* DEPRECATED: use getConvexity()
* Returns true if the path is flagged as being convex. This is not a
* confirmed by any analysis, it is just the value set earlier.
@@ -498,17 +484,25 @@ public:
void close();
enum Direction {
+ /** Direction either has not been or could not be computed */
+ kUnknown_Direction,
/** clockwise direction for adding closed contours */
kCW_Direction,
/** counter-clockwise direction for adding closed contours */
- kCCW_Direction
+ kCCW_Direction,
};
+ static Direction OppositeDirection(Direction dir) {
+ static const Direction kOppositeDir[] = {kUnknown_Direction, kCCW_Direction, kCW_Direction};
+ return kOppositeDir[dir];
+ }
+
/**
* Tries to quickly compute the direction of the first non-degenerate
* contour. If it can be computed, return true and set dir to that
* direction. If it cannot be (quickly) determined, return false and ignore
- * the dir parameter.
+ * the dir parameter. If the direction was determined, it is cached to make
+ * subsequent calls return quickly.
*/
bool cheapComputeDirection(Direction* dir) const;
@@ -518,6 +512,7 @@ public:
* specified direction.
*/
bool cheapIsDirection(Direction dir) const {
+ SkASSERT(kCW_Direction == dir || kCCW_Direction == dir);
Direction computedDir;
return this->cheapComputeDirection(&computedDir) && computedDir == dir;
}
@@ -827,11 +822,12 @@ public:
private:
enum SerializationOffsets {
- kIsFinite_SerializationShift = 25,
- kIsOval_SerializationShift = 24,
- kConvexity_SerializationShift = 16,
- kFillType_SerializationShift = 8,
- kSegmentMask_SerializationShift = 0
+ kDirection_SerializationShift = 26, // requires 2 bits
+ kIsFinite_SerializationShift = 25, // requires 1 bit
+ kIsOval_SerializationShift = 24, // requires 1 bit
+ kConvexity_SerializationShift = 16, // requires 2 bits
+ kFillType_SerializationShift = 8, // requires 2 bits
+ kSegmentMask_SerializationShift = 0 // requires 3 bits
};
#if SK_DEBUG_PATH_REF
@@ -865,6 +861,7 @@ private:
uint8_t fSegmentMask;
mutable uint8_t fBoundsIsDirty;
mutable uint8_t fConvexity;
+ mutable uint8_t fDirection;
mutable SkBool8 fIsFinite; // only meaningful if bounds are valid
mutable SkBool8 fIsOval;
#ifdef SK_BUILD_FOR_ANDROID
@@ -900,8 +897,11 @@ private:
inline bool hasOnlyMoveTos() const;
+ Convexity internalGetConvexity() const;
+
friend class SkAutoPathBoundsUpdate;
friend class SkAutoDisableOvalCheck;
+ friend class SkAutoDisableDirectionCheck;
friend class SkBench_AddPathTest; // perf test pathTo/reversePathTo
};
diff --git a/src/core/SkPath.cpp b/src/core/SkPath.cpp
index 6d055f5b7f..d4ef1a6044 100644
--- a/src/core/SkPath.cpp
+++ b/src/core/SkPath.cpp
@@ -110,6 +110,21 @@ private:
bool fSaved;
};
+class SkAutoDisableDirectionCheck {
+public:
+ SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
+ fSaved = static_cast<SkPath::Direction>(fPath->fDirection);
+ }
+
+ ~SkAutoDisableDirectionCheck() {
+ fPath->fDirection = fSaved;
+ }
+
+private:
+ SkPath* fPath;
+ SkPath::Direction fSaved;
+};
+
/* This guy's constructor/destructor bracket a path editing operation. It is
used when we know the bounds of the amount we are going to add to the path
(usually a new contour, but not required).
@@ -206,6 +221,7 @@ SkPath::SkPath()
, fFillType(kWinding_FillType)
, fBoundsIsDirty(true) {
fConvexity = kUnknown_Convexity;
+ fDirection = kUnknown_Direction;
fSegmentMask = 0;
fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
fIsOval = false;
@@ -228,6 +244,7 @@ SkPath::SkPath(const SkPath& src)
fFillType = src.fFillType;
fBoundsIsDirty = src.fBoundsIsDirty;
fConvexity = src.fConvexity;
+ fDirection = src.fDirection;
fIsFinite = src.fIsFinite;
fSegmentMask = src.fSegmentMask;
fLastMoveToIndex = src.fLastMoveToIndex;
@@ -252,6 +269,7 @@ SkPath& SkPath::operator=(const SkPath& src) {
fFillType = src.fFillType;
fBoundsIsDirty = src.fBoundsIsDirty;
fConvexity = src.fConvexity;
+ fDirection = src.fDirection;
fIsFinite = src.fIsFinite;
fSegmentMask = src.fSegmentMask;
fLastMoveToIndex = src.fLastMoveToIndex;
@@ -284,6 +302,7 @@ void SkPath::swap(SkPath& other) {
SkTSwap<uint8_t>(fFillType, other.fFillType);
SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty);
SkTSwap<uint8_t>(fConvexity, other.fConvexity);
+ SkTSwap<uint8_t>(fDirection, other.fDirection);
SkTSwap<uint8_t>(fSegmentMask, other.fSegmentMask);
SkTSwap<int>(fLastMoveToIndex, other.fLastMoveToIndex);
SkTSwap<SkBool8>(fIsOval, other.fIsOval);
@@ -313,6 +332,7 @@ void SkPath::reset() {
GEN_ID_INC;
fBoundsIsDirty = true;
fConvexity = kUnknown_Convexity;
+ fDirection = kUnknown_Direction;
fSegmentMask = 0;
fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
fIsOval = false;
@@ -566,12 +586,13 @@ void SkPath::setConvexity(Convexity c) {
do { \
fBoundsIsDirty = true; \
fConvexity = kUnknown_Convexity; \
+ fDirection = kUnknown_Direction; \
fIsOval = false; \
} while (0)
-#define DIRTY_AFTER_EDIT_NO_CONVEXITY_CHANGE \
- do { \
- fBoundsIsDirty = true; \
+#define DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE \
+ do { \
+ fBoundsIsDirty = true; \
} while (0)
void SkPath::incReserve(U16CPU inc) {
@@ -591,7 +612,7 @@ void SkPath::moveTo(SkScalar x, SkScalar y) {
ed.growForVerb(kMove_Verb)->set(x, y);
GEN_ID_INC;
- DIRTY_AFTER_EDIT_NO_CONVEXITY_CHANGE;
+ DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE;
}
void SkPath::rMoveTo(SkScalar x, SkScalar y) {
@@ -718,6 +739,9 @@ void SkPath::addRect(const SkRect& rect, Direction dir) {
void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
SkScalar bottom, Direction dir) {
+ fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
+ SkAutoDisableDirectionCheck addc(this);
+
SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
this->incReserve(5);
@@ -792,7 +816,10 @@ void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
return;
}
+ fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
+
SkAutoPathBoundsUpdate apbu(this, rect);
+ SkAutoDisableDirectionCheck(this);
if (skip_hori) {
rx = halfW;
@@ -935,8 +962,14 @@ void SkPath::addOval(const SkRect& oval, Direction dir) {
moveTo() would mark the path non empty.
*/
fIsOval = hasOnlyMoveTos();
+ if (fIsOval) {
+ fDirection = dir;
+ } else {
+ fDirection = kUnknown_Direction;
+ }
SkAutoDisableOvalCheck adoc(this);
+ SkAutoDisableDirectionCheck addc(this);
SkAutoPathBoundsUpdate apbu(this, oval);
@@ -1456,6 +1489,7 @@ void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
dst->swap(tmp);
SkPathRef::Editor ed(&dst->fPathRef);
matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
+ dst->fDirection = kUnknown_Direction;
} else {
/*
* If we're not in perspective, we can transform all of the points at
@@ -1494,6 +1528,21 @@ void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
dst->fConvexity = fConvexity;
}
+ if (kUnknown_Direction == fDirection) {
+ dst->fDirection = kUnknown_Direction;
+ } else {
+ SkScalar det2x2 =
+ SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
+ SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
+ if (det2x2 < 0) {
+ dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
+ } else if (det2x2 > 0) {
+ dst->fDirection = fDirection;
+ } else {
+ dst->fDirection = kUnknown_Direction;
+ }
+ }
+
// It's an oval only if it stays a rect.
dst->fIsOval = fIsOval && matrix.rectStaysRect();
@@ -1858,7 +1907,8 @@ uint32_t SkPath::writeToMemory(void* storage) const {
((fIsOval & 1) << kIsOval_SerializationShift) |
(fConvexity << kConvexity_SerializationShift) |
(fFillType << kFillType_SerializationShift) |
- (fSegmentMask << kSegmentMask_SerializationShift);
+ (fSegmentMask << kSegmentMask_SerializationShift) |
+ (fDirection << kDirection_SerializationShift);
buffer.write32(packed);
@@ -1882,7 +1932,8 @@ uint32_t SkPath::readFromMemory(const void* storage) {
fIsOval = (packed >> kIsOval_SerializationShift) & 1;
fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
- fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0xFF;
+ fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0x7;
+ fDirection = (packed >> kDirection_SerializationShift) & 0x3;
#if NEW_PICTURE_FORMAT
fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer));
@@ -2017,7 +2068,10 @@ static int CrossProductSign(const SkVector& a, const SkVector& b) {
// only valid for a single contour
struct Convexicator {
- Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
+ Convexicator()
+ : fPtCount(0)
+ , fConvexity(SkPath::kConvex_Convexity)
+ , fDirection(SkPath::kUnknown_Direction) {
fSign = 0;
// warnings
fCurrPt.set(0, 0);
@@ -2031,6 +2085,9 @@ struct Convexicator {
SkPath::Convexity getConvexity() const { return fConvexity; }
+ /** The direction returned is only valid if the path is determined convex */
+ SkPath::Direction getDirection() const { return fDirection; }
+
void addPt(const SkPoint& pt) {
if (SkPath::kConcave_Convexity == fConvexity) {
return;
@@ -2078,9 +2135,15 @@ private:
int sign = CrossProductSign(fVec0, fVec1);
if (0 == fSign) {
fSign = sign;
+ if (1 == sign) {
+ fDirection = SkPath::kCW_Direction;
+ } else if (-1 == sign) {
+ fDirection = SkPath::kCCW_Direction;
+ }
} else if (sign) {
if (fSign != sign) {
fConvexity = SkPath::kConcave_Convexity;
+ fDirection = SkPath::kUnknown_Direction;
}
}
}
@@ -2090,13 +2153,15 @@ private:
int fPtCount; // non-degenerate points
int fSign;
SkPath::Convexity fConvexity;
+ SkPath::Direction fDirection;
int fDx, fDy, fSx, fSy;
};
-SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
+SkPath::Convexity SkPath::internalGetConvexity() const {
+ SkASSERT(kUnknown_Convexity == fConvexity);
SkPoint pts[4];
SkPath::Verb verb;
- SkPath::Iter iter(path, true);
+ SkPath::Iter iter(*this, true);
int contourCount = 0;
int count;
@@ -2106,6 +2171,7 @@ SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
switch (verb) {
case kMove_Verb:
if (++contourCount > 1) {
+ fConvexity = kConcave_Convexity;
return kConcave_Convexity;
}
pts[1] = pts[0];
@@ -2120,6 +2186,7 @@ SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
break;
default:
SkDEBUGFAIL("bad verb");
+ fConvexity = kConcave_Convexity;
return kConcave_Convexity;
}
@@ -2128,10 +2195,15 @@ SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
}
// early exit
if (kConcave_Convexity == state.getConvexity()) {
+ fConvexity = kConcave_Convexity;
return kConcave_Convexity;
}
}
- return state.getConvexity();
+ fConvexity = state.getConvexity();
+ if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
+ fDirection = state.getDirection();
+ }
+ return static_cast<Convexity>(fConvexity);
}
///////////////////////////////////////////////////////////////////////////////
@@ -2282,11 +2354,10 @@ static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
return minIndex;
}
-static bool crossToDir(SkScalar cross, SkPath::Direction* dir) {
+static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
if (dir) {
*dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
}
- return true;
}
#if 0
@@ -2356,6 +2427,11 @@ bool SkPath::cheapComputeDirection(Direction* dir) const {
// dumpPath(*this);
// don't want to pay the cost for computing this if it
// is unknown, so we don't call isConvex()
+
+ if (kUnknown_Direction != fDirection) {
+ *dir = static_cast<Direction>(fDirection);
+ return true;
+ }
const Convexity conv = this->getConvexityOrUnknown();
ContourIter iter(*fPathRef.get());
@@ -2377,9 +2453,11 @@ bool SkPath::cheapComputeDirection(Direction* dir) const {
// precision. This is because the vectors computed between distant
// points may lose too much precision.
if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
+ fDirection = *dir;
return true;
}
if (convex_dir_test<double, toDouble>(n, pts, dir)) {
+ fDirection = *dir;
return true;
} else {
return false;
@@ -2423,9 +2501,10 @@ bool SkPath::cheapComputeDirection(Direction* dir) const {
int next = find_diff_pt(pts, index, n, 1);
SkASSERT(next != index);
cross = cross_prod(pts[prev], pts[index], pts[next]);
- // if we get a zero, but the pts aren't on top of each other, then
- // we can just look at the direction
- if (0 == cross) {
+ // if we get a zero and the points are horizontal, then we look at the spread in
+ // x-direction. We really should continue to walk away from the degeneracy until
+ // there is a divergence.
+ if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
// construct the subtract so we get the correct Direction below
cross = pts[index].fX - pts[next].fX;
}
@@ -2438,8 +2517,13 @@ bool SkPath::cheapComputeDirection(Direction* dir) const {
}
}
}
-
- return ymaxCross ? crossToDir(ymaxCross, dir) : false;
+ if (ymaxCross) {
+ crossToDir(ymaxCross, dir);
+ fDirection = *dir;
+ return true;
+ } else {
+ return false;
+ }
}
///////////////////////////////////////////////////////////////////////////////
diff --git a/src/gpu/GrAAConvexPathRenderer.cpp b/src/gpu/GrAAConvexPathRenderer.cpp
index 4ac61639c0..ebfcd7bdc3 100644
--- a/src/gpu/GrAAConvexPathRenderer.cpp
+++ b/src/gpu/GrAAConvexPathRenderer.cpp
@@ -209,9 +209,7 @@ inline bool get_direction(const SkPath& path, const SkMatrix& m, SkPath::Directi
SkScalar det2x2 = SkScalarMul(m.get(SkMatrix::kMScaleX), m.get(SkMatrix::kMScaleY)) -
SkScalarMul(m.get(SkMatrix::kMSkewX), m.get(SkMatrix::kMSkewY));
if (det2x2 < 0) {
- GR_STATIC_ASSERT(0 == SkPath::kCW_Direction || 1 == SkPath::kCW_Direction);
- GR_STATIC_ASSERT(0 == SkPath::kCCW_Direction || 1 == SkPath::kCCW_Direction);
- *dir = static_cast<SkPath::Direction>(*dir ^ 0x1);
+ *dir = SkPath::OppositeDirection(*dir);
}
return true;
}
@@ -223,7 +221,7 @@ bool get_segments(const SkPath& path,
int* vCount,
int* iCount) {
SkPath::Iter iter(path, true);
- // This renderer overemphasises very thin path regions. We use the distance
+ // This renderer over-emphasizes very thin path regions. We use the distance
// to the path from the sample to compute coverage. Every pixel intersected
// by the path will be hit and the maximum distance is sqrt(2)/2. We don't
// notice that the sample may be close to a very thin area of the path and
diff --git a/tests/PathTest.cpp b/tests/PathTest.cpp
index df2c7e4bd4..add66a240d 100644
--- a/tests/PathTest.cpp
+++ b/tests/PathTest.cpp
@@ -344,19 +344,23 @@ static void test_strokerec(skiatest::Reporter* reporter) {
REPORTER_ASSERT(reporter, SkStrokeRec::kFill_Style == rec.getStyle());
}
-/**
- * cheapIsDirection can take a shortcut when a path is marked convex.
- * This function ensures that we always test cheapIsDirection when the path
- * is flagged with unknown convexity status.
- */
-static void check_direction(SkPath* path,
- SkPath::Direction expectedDir,
- skiatest::Reporter* reporter) {
- if (SkPath::kConvex_Convexity == path->getConvexity()) {
- REPORTER_ASSERT(reporter, path->cheapIsDirection(expectedDir));
- path->setConvexity(SkPath::kUnknown_Convexity);
+// Set this for paths that don't have a consistent direction such as a bowtie.
+// (cheapComputeDirection is not expected to catch these.)
+static const SkPath::Direction kDontCheckDir = static_cast<SkPath::Direction>(-1);
+
+static void check_direction(skiatest::Reporter* reporter, const SkPath& path,
+ SkPath::Direction expected) {
+ if (expected == kDontCheckDir) {
+ return;
+ }
+ SkPath copy(path); // we make a copy so that we don't cache the result on the passed in path.
+
+ SkPath::Direction dir;
+ if (copy.cheapComputeDirection(&dir)) {
+ REPORTER_ASSERT(reporter, dir == expected);
+ } else {
+ REPORTER_ASSERT(reporter, SkPath::kUnknown_Direction == expected);
}
- REPORTER_ASSERT(reporter, path->cheapIsDirection(expectedDir));
}
static void test_direction(skiatest::Reporter* reporter) {
@@ -394,7 +398,7 @@ static void test_direction(skiatest::Reporter* reporter) {
path.reset();
bool valid = SkParsePath::FromSVGString(gCW[i], &path);
REPORTER_ASSERT(reporter, valid);
- check_direction(&path, SkPath::kCW_Direction, reporter);
+ check_direction(reporter, path, SkPath::kCW_Direction);
}
static const char* gCCW[] = {
@@ -410,7 +414,7 @@ static void test_direction(skiatest::Reporter* reporter) {
path.reset();
bool valid = SkParsePath::FromSVGString(gCCW[i], &path);
REPORTER_ASSERT(reporter, valid);
- check_direction(&path, SkPath::kCCW_Direction, reporter);
+ check_direction(reporter, path, SkPath::kCCW_Direction);
}
// Test two donuts, each wound a different direction. Only the outer contour
@@ -418,12 +422,12 @@ static void test_direction(skiatest::Reporter* reporter) {
path.reset();
path.addCircle(0, 0, SkIntToScalar(2), SkPath::kCW_Direction);
path.addCircle(0, 0, SkIntToScalar(1), SkPath::kCCW_Direction);
- check_direction(&path, SkPath::kCW_Direction, reporter);
+ check_direction(reporter, path, SkPath::kCW_Direction);
path.reset();
path.addCircle(0, 0, SkIntToScalar(1), SkPath::kCW_Direction);
path.addCircle(0, 0, SkIntToScalar(2), SkPath::kCCW_Direction);
- check_direction(&path, SkPath::kCCW_Direction, reporter);
+ check_direction(reporter, path, SkPath::kCCW_Direction);
#ifdef SK_SCALAR_IS_FLOAT
// triangle with one point really far from the origin.
@@ -432,7 +436,7 @@ static void test_direction(skiatest::Reporter* reporter) {
path.moveTo(SkFloatToScalar(SkBits2Float(0x501c7652)), SkFloatToScalar(SkBits2Float(0x501c7652)));
path.lineTo(110 * SK_Scalar1, -10 * SK_Scalar1);
path.lineTo(-10 * SK_Scalar1, 60 * SK_Scalar1);
- check_direction(&path, SkPath::kCCW_Direction, reporter);
+ check_direction(reporter, path, SkPath::kCCW_Direction);
#endif
}
@@ -595,7 +599,8 @@ static void test_close(skiatest::Reporter* reporter) {
static void check_convexity(skiatest::Reporter* reporter, const SkPath& path,
SkPath::Convexity expected) {
- SkPath::Convexity c = SkPath::ComputeConvexity(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);
}
@@ -604,12 +609,14 @@ static void test_convexity2(skiatest::Reporter* reporter) {
pt.moveTo(0, 0);
pt.close();
check_convexity(reporter, pt, SkPath::kConvex_Convexity);
+ check_direction(reporter, pt, SkPath::kUnknown_Direction);
SkPath line;
line.moveTo(12*SK_Scalar1, 20*SK_Scalar1);
line.lineTo(-12*SK_Scalar1, -20*SK_Scalar1);
line.close();
- check_convexity(reporter, pt, SkPath::kConvex_Convexity);
+ check_convexity(reporter, line, SkPath::kConvex_Convexity);
+ check_direction(reporter, line, SkPath::kUnknown_Direction);
SkPath triLeft;
triLeft.moveTo(0, 0);
@@ -617,6 +624,7 @@ static void test_convexity2(skiatest::Reporter* reporter) {
triLeft.lineTo(SK_Scalar1, SK_Scalar1);
triLeft.close();
check_convexity(reporter, triLeft, SkPath::kConvex_Convexity);
+ check_direction(reporter, triLeft, SkPath::kCW_Direction);
SkPath triRight;
triRight.moveTo(0, 0);
@@ -624,6 +632,7 @@ static void test_convexity2(skiatest::Reporter* reporter) {
triRight.lineTo(SK_Scalar1, SK_Scalar1);
triRight.close();
check_convexity(reporter, triRight, SkPath::kConvex_Convexity);
+ check_direction(reporter, triRight, SkPath::kCCW_Direction);
SkPath square;
square.moveTo(0, 0);
@@ -632,6 +641,7 @@ static void test_convexity2(skiatest::Reporter* reporter) {
square.lineTo(0, SK_Scalar1);
square.close();
check_convexity(reporter, square, SkPath::kConvex_Convexity);
+ check_direction(reporter, square, SkPath::kCW_Direction);
SkPath redundantSquare;
redundantSquare.moveTo(0, 0);
@@ -648,6 +658,7 @@ static void test_convexity2(skiatest::Reporter* reporter) {
redundantSquare.lineTo(0, SK_Scalar1);
redundantSquare.close();
check_convexity(reporter, redundantSquare, SkPath::kConvex_Convexity);
+ check_direction(reporter, redundantSquare, SkPath::kCW_Direction);
SkPath bowTie;
bowTie.moveTo(0, 0);
@@ -664,6 +675,7 @@ static void test_convexity2(skiatest::Reporter* reporter) {
bowTie.lineTo(0, SK_Scalar1);
bowTie.close();
check_convexity(reporter, bowTie, SkPath::kConcave_Convexity);
+ check_direction(reporter, bowTie, kDontCheckDir);
SkPath spiral;
spiral.moveTo(0, 0);
@@ -675,6 +687,7 @@ static void test_convexity2(skiatest::Reporter* reporter) {
spiral.lineTo(50*SK_Scalar1, 75*SK_Scalar1);
spiral.close();
check_convexity(reporter, spiral, SkPath::kConcave_Convexity);
+ check_direction(reporter, spiral, kDontCheckDir);
SkPath dent;
dent.moveTo(0, 0);
@@ -684,6 +697,7 @@ static void test_convexity2(skiatest::Reporter* reporter) {
dent.lineTo(-200*SK_Scalar1, 100*SK_Scalar1);
dent.close();
check_convexity(reporter, dent, SkPath::kConcave_Convexity);
+ check_direction(reporter, dent, SkPath::kCW_Direction);
}
static void check_convex_bounds(skiatest::Reporter* reporter, const SkPath& p,
@@ -721,44 +735,44 @@ static void setFromString(SkPath* path, const char str[]) {
}
static void test_convexity(skiatest::Reporter* reporter) {
- static const SkPath::Convexity C = SkPath::kConcave_Convexity;
- static const SkPath::Convexity V = SkPath::kConvex_Convexity;
-
SkPath path;
- REPORTER_ASSERT(reporter, V == SkPath::ComputeConvexity(path));
+ check_convexity(reporter, path, SkPath::kConvex_Convexity);
path.addCircle(0, 0, SkIntToScalar(10));
- REPORTER_ASSERT(reporter, V == SkPath::ComputeConvexity(path));
+ check_convexity(reporter, path, SkPath::kConvex_Convexity);
path.addCircle(0, 0, SkIntToScalar(10)); // 2nd circle
- REPORTER_ASSERT(reporter, C == SkPath::ComputeConvexity(path));
+ check_convexity(reporter, path, SkPath::kConcave_Convexity);
+
path.reset();
path.addRect(0, 0, SkIntToScalar(10), SkIntToScalar(10), SkPath::kCCW_Direction);
- REPORTER_ASSERT(reporter, V == SkPath::ComputeConvexity(path));
+ check_convexity(reporter, path, SkPath::kConvex_Convexity);
REPORTER_ASSERT(reporter, path.cheapIsDirection(SkPath::kCCW_Direction));
+
path.reset();
path.addRect(0, 0, SkIntToScalar(10), SkIntToScalar(10), SkPath::kCW_Direction);
- REPORTER_ASSERT(reporter, V == SkPath::ComputeConvexity(path));
+ check_convexity(reporter, path, SkPath::kConvex_Convexity);
REPORTER_ASSERT(reporter, path.cheapIsDirection(SkPath::kCW_Direction));
static const struct {
const char* fPathStr;
SkPath::Convexity fExpectedConvexity;
+ SkPath::Direction fExpectedDirection;
} gRec[] = {
- { "", SkPath::kConvex_Convexity },
- { "0 0", SkPath::kConvex_Convexity },
- { "0 0 10 10", SkPath::kConvex_Convexity },
- { "0 0 10 10 20 20 0 0 10 10", SkPath::kConcave_Convexity },
- { "0 0 10 10 10 20", SkPath::kConvex_Convexity },
- { "0 0 10 10 10 0", SkPath::kConvex_Convexity },
- { "0 0 10 10 10 0 0 10", SkPath::kConcave_Convexity },
- { "0 0 10 0 0 10 -10 -10", SkPath::kConcave_Convexity },
+ { "", SkPath::kConvex_Convexity, SkPath::kUnknown_Direction },
+ { "0 0", SkPath::kConvex_Convexity, SkPath::kUnknown_Direction },
+ { "0 0 10 10", SkPath::kConvex_Convexity, SkPath::kUnknown_Direction },
+ { "0 0 10 10 20 20 0 0 10 10", SkPath::kConcave_Convexity, SkPath::kUnknown_Direction },
+ { "0 0 10 10 10 20", SkPath::kConvex_Convexity, SkPath::kCW_Direction },
+ { "0 0 10 10 10 0", SkPath::kConvex_Convexity, SkPath::kCCW_Direction },
+ { "0 0 10 10 10 0 0 10", SkPath::kConcave_Convexity, kDontCheckDir },
+ { "0 0 10 0 0 10 -10 -10", SkPath::kConcave_Convexity, SkPath::kCW_Direction },
};
for (size_t i = 0; i < SK_ARRAY_COUNT(gRec); ++i) {
SkPath path;
setFromString(&path, gRec[i].fPathStr);
- SkPath::Convexity c = SkPath::ComputeConvexity(path);
- REPORTER_ASSERT(reporter, c == gRec[i].fExpectedConvexity);
+ check_convexity(reporter, path, gRec[i].fExpectedConvexity);
+ check_direction(reporter, path, gRec[i].fExpectedDirection);
}
}
@@ -1454,33 +1468,46 @@ static void test_raw_iter(skiatest::Reporter* reporter) {
}
static void check_for_circle(skiatest::Reporter* reporter,
- const SkPath& path, bool expected) {
+ const SkPath& path,
+ bool expectedCircle,
+ SkPath::Direction expectedDir) {
SkRect rect;
- REPORTER_ASSERT(reporter, path.isOval(&rect) == expected);
- if (expected) {
+ REPORTER_ASSERT(reporter, path.isOval(&rect) == expectedCircle);
+ REPORTER_ASSERT(reporter, path.cheapIsDirection(expectedDir));
+
+ if (expectedCircle) {
REPORTER_ASSERT(reporter, rect.height() == rect.width());
}
}
static void test_circle_skew(skiatest::Reporter* reporter,
- const SkPath& path) {
+ const SkPath& path,
+ SkPath::Direction dir) {
SkPath tmp;
SkMatrix m;
m.setSkew(SkIntToScalar(3), SkIntToScalar(5));
path.transform(m, &tmp);
- check_for_circle(reporter, tmp, false);
+ // this matrix reverses the direction.
+ if (SkPath::kCCW_Direction == dir) {
+ dir = SkPath::kCW_Direction;
+ } else {
+ SkASSERT(SkPath::kCW_Direction == dir);
+ dir = SkPath::kCCW_Direction;
+ }
+ check_for_circle(reporter, tmp, false, dir);
}
static void test_circle_translate(skiatest::Reporter* reporter,
- const SkPath& path) {
+ const SkPath& path,
+ SkPath::Direction dir) {
SkPath tmp;
// translate at small offset
SkMatrix m;
m.setTranslate(SkIntToScalar(15), SkIntToScalar(15));
path.transform(m, &tmp);
- check_for_circle(reporter, tmp, true);
+ check_for_circle(reporter, tmp, true, dir);
tmp.reset();
m.reset();
@@ -1488,47 +1515,102 @@ static void test_circle_translate(skiatest::Reporter* reporter,
// translate at a relatively big offset
m.setTranslate(SkIntToScalar(1000), SkIntToScalar(1000));
path.transform(m, &tmp);
- check_for_circle(reporter, tmp, true);
+ check_for_circle(reporter, tmp, true, dir);
}
static void test_circle_rotate(skiatest::Reporter* reporter,
- const SkPath& path) {
+ const SkPath& path,
+ SkPath::Direction dir) {
for (int angle = 0; angle < 360; ++angle) {
SkPath tmp;
SkMatrix m;
m.setRotate(SkIntToScalar(angle));
path.transform(m, &tmp);
- // TODO: a rotated circle whose rotated angle is not a mutiple of 90
+ // TODO: a rotated circle whose rotated angle is not a multiple of 90
// degrees is not an oval anymore, this can be improved. we made this
// for the simplicity of our implementation.
if (angle % 90 == 0) {
- check_for_circle(reporter, tmp, true);
+ check_for_circle(reporter, tmp, true, dir);
} else {
- check_for_circle(reporter, tmp, false);
+ check_for_circle(reporter, tmp, false, dir);
}
}
}
+static void test_circle_mirror_x(skiatest::Reporter* reporter,
+ const SkPath& path,
+ SkPath::Direction dir) {
+ SkPath tmp;
+ SkMatrix m;
+ m.reset();
+ m.setScaleX(-SK_Scalar1);
+ path.transform(m, &tmp);
+
+ if (SkPath::kCW_Direction == dir) {
+ dir = SkPath::kCCW_Direction;
+ } else {
+ SkASSERT(SkPath::kCCW_Direction == dir);
+ dir = SkPath::kCW_Direction;
+ }
+
+ check_for_circle(reporter, tmp, true, dir);
+}
+
+static void test_circle_mirror_y(skiatest::Reporter* reporter,
+ const SkPath& path,
+ SkPath::Direction dir) {
+ SkPath tmp;
+ SkMatrix m;
+ m.reset();
+ m.setScaleY(-SK_Scalar1);
+ path.transform(m, &tmp);
+
+ if (SkPath::kCW_Direction == dir) {
+ dir = SkPath::kCCW_Direction;
+ } else {
+ SkASSERT(SkPath::kCCW_Direction == dir);
+ dir = SkPath::kCW_Direction;
+ }
+
+ check_for_circle(reporter, tmp, true, dir);
+}
+
+static void test_circle_mirror_xy(skiatest::Reporter* reporter,
+ const SkPath& path,
+ SkPath::Direction dir) {
+ SkPath tmp;
+ SkMatrix m;
+ m.reset();
+ m.setScaleX(-SK_Scalar1);
+ m.setScaleY(-SK_Scalar1);
+ path.transform(m, &tmp);
+
+ check_for_circle(reporter, tmp, true, dir);
+}
+
static void test_circle_with_direction(skiatest::Reporter* reporter,
SkPath::Direction dir) {
SkPath path;
// circle at origin
path.addCircle(0, 0, SkIntToScalar(20), dir);
- check_for_circle(reporter, path, true);
- test_circle_rotate(reporter, path);
- test_circle_translate(reporter, path);
- test_circle_skew(reporter, path);
+ check_for_circle(reporter, path, true, dir);
+ test_circle_rotate(reporter, path, dir);
+ test_circle_translate(reporter, path, dir);
+ test_circle_skew(reporter, path, dir);
// circle at an offset at (10, 10)
path.reset();
path.addCircle(SkIntToScalar(10), SkIntToScalar(10),
SkIntToScalar(20), dir);
- check_for_circle(reporter, path, true);
- test_circle_rotate(reporter, path);
- test_circle_translate(reporter, path);
- test_circle_skew(reporter, path);
+ check_for_circle(reporter, path, true, dir);
+ test_circle_rotate(reporter, path, dir);
+ test_circle_translate(reporter, path, dir);
+ test_circle_skew(reporter, path, dir);
+ test_circle_mirror_x(reporter, path, dir);
+ test_circle_mirror_y(reporter, path, dir);
+ test_circle_mirror_xy(reporter, path, dir);
}
static void test_circle_with_add_paths(skiatest::Reporter* reporter) {
@@ -1537,7 +1619,10 @@ static void test_circle_with_add_paths(skiatest::Reporter* reporter) {
SkPath rect;
SkPath empty;
- circle.addCircle(0, 0, SkIntToScalar(10), SkPath::kCW_Direction);
+ static const SkPath::Direction kCircleDir = SkPath::kCW_Direction;
+ static const SkPath::Direction kCircleDirOpposite = SkPath::kCCW_Direction;
+
+ circle.addCircle(0, 0, SkIntToScalar(10), kCircleDir);
rect.addRect(SkIntToScalar(5), SkIntToScalar(5),
SkIntToScalar(20), SkIntToScalar(20), SkPath::kCW_Direction);
@@ -1550,17 +1635,17 @@ static void test_circle_with_add_paths(skiatest::Reporter* reporter) {
// empty + circle (translate)
path = empty;
path.addPath(circle, translate);
- check_for_circle(reporter, path, false);
+ check_for_circle(reporter, path, false, kCircleDir);
// circle + empty (translate)
path = circle;
path.addPath(empty, translate);
- check_for_circle(reporter, path, false);
+ check_for_circle(reporter, path, false, kCircleDir);
// test reverseAddPath
path = circle;
path.reverseAddPath(rect);
- check_for_circle(reporter, path, false);
+ check_for_circle(reporter, path, false, kCircleDirOpposite);
}
static void test_circle(skiatest::Reporter* reporter) {
@@ -1571,19 +1656,19 @@ static void test_circle(skiatest::Reporter* reporter) {
SkPath path;
path.addCircle(0, 0, SkIntToScalar(10), SkPath::kCW_Direction);
path.addCircle(0, 0, SkIntToScalar(20), SkPath::kCW_Direction);
- check_for_circle(reporter, path, false);
+ check_for_circle(reporter, path, false, SkPath::kCW_Direction);
// some extra lineTo() would make isOval() fail
path.reset();
path.addCircle(0, 0, SkIntToScalar(10), SkPath::kCW_Direction);
path.lineTo(0, 0);
- check_for_circle(reporter, path, false);
+ check_for_circle(reporter, path, false, SkPath::kCW_Direction);
// not back to the original point
path.reset();
path.addCircle(0, 0, SkIntToScalar(10), SkPath::kCW_Direction);
path.setLastPt(SkIntToScalar(5), SkIntToScalar(5));
- check_for_circle(reporter, path, false);
+ check_for_circle(reporter, path, false, SkPath::kCW_Direction);
test_circle_with_add_paths(reporter);
}