diff options
-rw-r--r-- | include/core/SkPath.h | 58 | ||||
-rw-r--r-- | src/core/SkPath.cpp | 118 | ||||
-rw-r--r-- | src/gpu/GrAAConvexPathRenderer.cpp | 6 | ||||
-rw-r--r-- | tests/PathTest.cpp | 213 |
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); } |