diff options
-rw-r--r-- | bench/PathBench.cpp | 16 | ||||
-rw-r--r-- | include/core/SkPath.h | 8 | ||||
-rw-r--r-- | src/core/SkPath.cpp | 242 | ||||
-rw-r--r-- | src/core/SkPathRef.cpp | 7 | ||||
-rw-r--r-- | tests/PathTest.cpp | 632 | ||||
-rwxr-xr-x | tools/coverage.sh | 2 |
6 files changed, 664 insertions, 243 deletions
diff --git a/bench/PathBench.cpp b/bench/PathBench.cpp index d8a2ca9b68..11151d9d45 100644 --- a/bench/PathBench.cpp +++ b/bench/PathBench.cpp @@ -495,7 +495,6 @@ public: kAdd_AddType, kAddTrans_AddType, kAddMatrix_AddType, - kPathTo_AddType, kReverseAdd_AddType, kReversePathTo_AddType, }; @@ -513,8 +512,6 @@ protected: return "path_add_path_trans"; case kAddMatrix_AddType: return "path_add_path_matrix"; - case kPathTo_AddType: - return "path_path_to"; case kReverseAdd_AddType: return "path_reverse_add_path"; case kReversePathTo_AddType: @@ -526,9 +523,8 @@ protected: } virtual void onPreDraw() SK_OVERRIDE { - // pathTo and reversePathTo assume a single contour path. - bool allowMoves = kPathTo_AddType != fType && - kReversePathTo_AddType != fType; + // reversePathTo assumes a single contour path. + bool allowMoves = kReversePathTo_AddType != fType; this->createData(10, 100, allowMoves); fPaths0.reset(kPathCnt); fPaths1.reset(kPathCnt); @@ -562,13 +558,6 @@ protected: result.addPath(fPaths1[idx], fMatrix); } break; - case kPathTo_AddType: - for (int i = 0; i < this->getLoops(); ++i) { - int idx = i & (kPathCnt - 1); - SkPath result = fPaths0[idx]; - result.pathTo(fPaths1[idx]); - } - break; case kReverseAdd_AddType: for (int i = 0; i < this->getLoops(); ++i) { int idx = i & (kPathCnt - 1); @@ -1036,7 +1025,6 @@ DEF_BENCH( return new PathEqualityBench(); ) DEF_BENCH( return new SkBench_AddPathTest(SkBench_AddPathTest::kAdd_AddType); ) DEF_BENCH( return new SkBench_AddPathTest(SkBench_AddPathTest::kAddTrans_AddType); ) DEF_BENCH( return new SkBench_AddPathTest(SkBench_AddPathTest::kAddMatrix_AddType); ) -DEF_BENCH( return new SkBench_AddPathTest(SkBench_AddPathTest::kPathTo_AddType); ) DEF_BENCH( return new SkBench_AddPathTest(SkBench_AddPathTest::kReverseAdd_AddType); ) DEF_BENCH( return new SkBench_AddPathTest(SkBench_AddPathTest::kReversePathTo_AddType); ) diff --git a/include/core/SkPath.h b/include/core/SkPath.h index 2b111fe505..ac4dd3bf56 100644 --- a/include/core/SkPath.h +++ b/include/core/SkPath.h @@ -972,11 +972,6 @@ private: friend class Iter; friend class SkPathStroker; - /* Append the first contour of path, ignoring path's initial point. If no - moveTo() call has been made for this contour, the first point is - automatically set to (0,0). - */ - void pathTo(const SkPath& path); /* Append, in reverse order, the first contour of path, ignoring path's last point. If no moveTo() call has been made for this contour, the @@ -1019,7 +1014,8 @@ private: friend class SkAutoPathBoundsUpdate; friend class SkAutoDisableOvalCheck; friend class SkAutoDisableDirectionCheck; - friend class SkBench_AddPathTest; // perf test pathTo/reversePathTo + friend class SkBench_AddPathTest; // perf test reversePathTo + friend class PathTest_Private; // unit test reversePathTo }; #endif diff --git a/src/core/SkPath.cpp b/src/core/SkPath.cpp index f772717e2f..5f53ce8915 100644 --- a/src/core/SkPath.cpp +++ b/src/core/SkPath.cpp @@ -662,7 +662,7 @@ void SkPath::moveTo(SkScalar x, SkScalar y) { SkPathRef::Editor ed(&fPathRef); // remember our index - fLastMoveToIndex = ed.pathRef()->countPoints(); + fLastMoveToIndex = fPathRef->countPoints(); ed.growForVerb(kMove_Verb)->set(x, y); } @@ -1106,7 +1106,7 @@ void SkPath::addRRect(const SkRRect& rrect, Direction dir) { fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction; SkAutoPathBoundsUpdate apbu(this, bounds); - SkAutoDisableDirectionCheck(this); + SkAutoDisableDirectionCheck addc(this); this->incReserve(21); if (kCW_Direction == dir) { @@ -1134,6 +1134,7 @@ bool SkPath::hasOnlyMoveTos() const { for (int i = 0; i < count; ++i) { if (*verbs == kLine_Verb || *verbs == kQuad_Verb || + *verbs == kConic_Verb || *verbs == kCubic_Verb) { return false; } @@ -1179,7 +1180,7 @@ void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry, fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction; SkAutoPathBoundsUpdate apbu(this, rect); - SkAutoDisableDirectionCheck(this); + SkAutoDisableDirectionCheck addc(this); if (skip_hori) { rx = halfW; @@ -1511,45 +1512,6 @@ static int pts_in_verb(unsigned verb) { return gPtsInVerb[verb]; } -// ignore the initial moveto, and stop when the 1st contour ends -void SkPath::pathTo(const SkPath& path) { - int i, vcount = path.fPathRef->countVerbs(); - // exit early if the path is empty, or just has a moveTo. - if (vcount < 2) { - return; - } - - SkPathRef::Editor(&fPathRef, vcount, path.countPoints()); - - fIsOval = false; - - const uint8_t* verbs = path.fPathRef->verbs(); - // skip the initial moveTo - const SkPoint* pts = path.fPathRef->points() + 1; - const SkScalar* conicWeight = path.fPathRef->conicWeights(); - - SkASSERT(verbs[~0] == kMove_Verb); - for (i = 1; i < vcount; i++) { - switch (verbs[~i]) { - case kLine_Verb: - this->lineTo(pts[0].fX, pts[0].fY); - break; - case kQuad_Verb: - this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY); - break; - case kConic_Verb: - this->conicTo(pts[0], pts[1], *conicWeight++); - break; - case kCubic_Verb: - this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY); - break; - case kClose_Verb: - return; - } - pts += pts_in_verb(verbs[~i]); - } -} - // ignore the last point of the 1st contour void SkPath::reversePathTo(const SkPath& path) { int i, vcount = path.fPathRef->countVerbs(); @@ -1755,6 +1717,7 @@ void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const { } else if (det2x2 > 0) { dst->fDirection = fDirection; } else { + dst->fConvexity = kUnknown_Convexity; dst->fDirection = kUnknown_Direction; } } @@ -2319,9 +2282,7 @@ static bool AlmostEqual(SkScalar compA, SkScalar compB) { if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) { return false; } - if (sk_float_abs(compA) <= FLT_EPSILON && sk_float_abs(compB) <= FLT_EPSILON) { - return true; - } + // 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; @@ -2632,64 +2593,7 @@ static int find_min_max_x_at_y(const SkPoint pts[], int index, int n, } static void crossToDir(SkScalar cross, SkPath::Direction* dir) { - if (dir) { - *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction; - } -} - -#if 0 -#include "SkString.h" -#include "../utils/SkParsePath.h" -static void dumpPath(const SkPath& path) { - SkString str; - SkParsePath::ToSVGString(path, &str); - SkDebugf("%s\n", str.c_str()); -} -#endif - -namespace { -// for use with convex_dir_test -double mul(double a, double b) { return a * b; } -SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); } -double toDouble(SkScalar a) { return SkScalarToDouble(a); } -SkScalar toScalar(SkScalar a) { return a; } - -// determines the winding direction of a convex polygon with the precision -// of T. CAST_SCALAR casts an SkScalar to T. -template <typename T, T (CAST_SCALAR)(SkScalar)> -bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) { - // we find the first three points that form a non-degenerate - // triangle. If there are no such points then the path is - // degenerate. The first is always point 0. Now we find the second - // point. - int i = 0; - enum { kX = 0, kY = 1 }; - T v0[2]; - while (1) { - v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX); - v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY); - if (v0[kX] || v0[kY]) { - break; - } - if (++i == n - 1) { - return false; - } - } - // now find a third point that is not colinear with the first two - // points and check the orientation of the triangle (which will be - // the same as the orientation of the path). - for (++i; i < n; ++i) { - T v1[2]; - v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX); - v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY); - T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]); - if (0 != cross) { - *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction; - return true; - } - } - return false; -} + *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction; } /* @@ -2701,15 +2605,18 @@ bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) { * its cross product. */ 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(); + + // don't want to pay the cost for computing this if it + // is unknown, so we don't call isConvex() + if (kConvex_Convexity == this->getConvexityOrUnknown()) { + SkASSERT(kUnknown_Direction == fDirection); + *dir = static_cast<Direction>(fDirection); + return false; + } ContourIter iter(*fPathRef.get()); @@ -2725,73 +2632,57 @@ bool SkPath::cheapComputeDirection(Direction* dir) const { const SkPoint* pts = iter.pts(); SkScalar cross = 0; - if (kConvex_Convexity == conv) { - // We try first at scalar precision, and then again at double - // 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; + int index = find_max_y(pts, n); + if (pts[index].fY < ymax) { + continue; + } + + // If there is more than 1 distinct point at the y-max, we take the + // x-min and x-max of them and just subtract to compute the dir. + if (pts[(index + 1) % n].fY == pts[index].fY) { + int maxIndex; + int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex); + if (minIndex == maxIndex) { + goto TRY_CROSSPROD; } + SkASSERT(pts[minIndex].fY == pts[index].fY); + SkASSERT(pts[maxIndex].fY == pts[index].fY); + SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX); + // we just subtract the indices, and let that auto-convert to + // SkScalar, since we just want - or + to signal the direction. + cross = minIndex - maxIndex; } else { - int index = find_max_y(pts, n); - if (pts[index].fY < ymax) { + TRY_CROSSPROD: + // Find a next and prev index to use for the cross-product test, + // but we try to find pts that form non-zero vectors from pts[index] + // + // Its possible that we can't find two non-degenerate vectors, so + // we have to guard our search (e.g. all the pts could be in the + // same place). + + // we pass n - 1 instead of -1 so we don't foul up % operator by + // passing it a negative LH argument. + int prev = find_diff_pt(pts, index, n, n - 1); + if (prev == index) { + // completely degenerate, skip to next contour continue; } - - // If there is more than 1 distinct point at the y-max, we take the - // x-min and x-max of them and just subtract to compute the dir. - if (pts[(index + 1) % n].fY == pts[index].fY) { - int maxIndex; - int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex); - if (minIndex == maxIndex) { - goto TRY_CROSSPROD; - } - SkASSERT(pts[minIndex].fY == pts[index].fY); - SkASSERT(pts[maxIndex].fY == pts[index].fY); - SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX); - // we just subtract the indices, and let that auto-convert to - // SkScalar, since we just want - or + to signal the direction. - cross = minIndex - maxIndex; - } else { - TRY_CROSSPROD: - // Find a next and prev index to use for the cross-product test, - // but we try to find pts that form non-zero vectors from pts[index] - // - // Its possible that we can't find two non-degenerate vectors, so - // we have to guard our search (e.g. all the pts could be in the - // same place). - - // we pass n - 1 instead of -1 so we don't foul up % operator by - // passing it a negative LH argument. - int prev = find_diff_pt(pts, index, n, n - 1); - if (prev == index) { - // completely degenerate, skip to next contour - continue; - } - 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 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; - } + 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 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; } + } - if (cross) { - // record our best guess so far - ymax = pts[index].fY; - ymaxCross = cross; - } + if (cross) { + // record our best guess so far + ymax = pts[index].fY; + ymaxCross = cross; } } if (ymaxCross) { @@ -2822,7 +2713,7 @@ static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c /* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the t value such that cubic(t) = target */ -static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3, +static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3, SkScalar target, SkScalar* t) { // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3); SkASSERT(c0 < target && target < c3); @@ -2851,7 +2742,6 @@ static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3, } } *t = mid; - return true; } template <size_t N> static void find_minmax(const SkPoint pts[], @@ -2893,13 +2783,9 @@ static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) { } // compute the actual x(t) value - SkScalar t, xt; - if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) { - xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t); - } else { - SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY); - xt = y < mid ? pts[0].fX : pts[3].fX; - } + SkScalar t; + chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t); + SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t); return xt < x ? dir : 0; } diff --git a/src/core/SkPathRef.cpp b/src/core/SkPathRef.cpp index 355700265c..c66d75b3b2 100644 --- a/src/core/SkPathRef.cpp +++ b/src/core/SkPathRef.cpp @@ -332,20 +332,19 @@ void SkPathRef::validate() const { SkASSERT(this->currSize() == fFreeSpace + sizeof(SkPoint) * fPointCnt + sizeof(uint8_t) * fVerbCnt); -#ifdef SK_DEBUG if (!fBoundsIsDirty && !fBounds.isEmpty()) { bool isFinite = true; for (int i = 0; i < fPointCnt; ++i) { - SkASSERT(fBounds.fLeft - fPoints[i].fX < SK_ScalarNearlyZero && + SkASSERT(!fPoints[i].isFinite() || ( + fBounds.fLeft - fPoints[i].fX < SK_ScalarNearlyZero && fPoints[i].fX - fBounds.fRight < SK_ScalarNearlyZero && fBounds.fTop - fPoints[i].fY < SK_ScalarNearlyZero && - fPoints[i].fY - fBounds.fBottom < SK_ScalarNearlyZero); + fPoints[i].fY - fBounds.fBottom < SK_ScalarNearlyZero)); if (!fPoints[i].isFinite()) { isFinite = false; } } SkASSERT(SkToBool(fIsFinite) == isFinite); } -#endif } #endif diff --git a/tests/PathTest.cpp b/tests/PathTest.cpp index 2dbf5c695e..18d50d005d 100644 --- a/tests/PathTest.cpp +++ b/tests/PathTest.cpp @@ -357,6 +357,33 @@ static void test_crbug_170666() { surface->getCanvas()->drawPath(path, paint); } +static void test_addrect(skiatest::Reporter* reporter) { + SkPath path; + path.lineTo(0, 0); + path.addRect(SkRect::MakeWH(50, 100)); + REPORTER_ASSERT(reporter, path.isRect(NULL)); + + path.reset(); + path.lineTo(FLT_EPSILON, FLT_EPSILON); + path.addRect(SkRect::MakeWH(50, 100)); + REPORTER_ASSERT(reporter, !path.isRect(NULL)); + + path.reset(); + path.quadTo(0, 0, 0, 0); + path.addRect(SkRect::MakeWH(50, 100)); + REPORTER_ASSERT(reporter, !path.isRect(NULL)); + + path.reset(); + path.conicTo(0, 0, 0, 0, 0.5f); + path.addRect(SkRect::MakeWH(50, 100)); + REPORTER_ASSERT(reporter, !path.isRect(NULL)); + + path.reset(); + path.cubicTo(0, 0, 0, 0, 0, 0); + path.addRect(SkRect::MakeWH(50, 100)); + REPORTER_ASSERT(reporter, !path.isRect(NULL)); +} + // Make sure we stay non-finite once we get there (unless we reset or rewind). static void test_addrect_isfinite(skiatest::Reporter* reporter) { SkPath path; @@ -811,6 +838,18 @@ static void test_direction(skiatest::Reporter* reporter) { path.lineTo(-10 * SK_Scalar1, 60 * SK_Scalar1); check_direction(reporter, path, SkPath::kCCW_Direction); #endif + + path.reset(); + path.conicTo(20, 0, 20, 20, 0.5f); + path.close(); + check_direction(reporter, path, SkPath::kCW_Direction); + + path.reset(); + path.lineTo(1, 1e7f); + path.lineTo(1e7f, 2e7f); + path.close(); + REPORTER_ASSERT(reporter, SkPath::kConvex_Convexity == path.getConvexity()); + check_direction(reporter, path, SkPath::kCCW_Direction); } static void add_rect(SkPath* path, const SkRect& r) { @@ -1146,6 +1185,18 @@ static void test_convexity(skiatest::Reporter* reporter) { setFromString(&path, gRec[i].fPathStr); check_convexity(reporter, path, gRec[i].fExpectedConvexity); check_direction(reporter, path, gRec[i].fExpectedDirection); + // check after setting the initial convex and direction + if (kDontCheckDir != gRec[i].fExpectedDirection) { + SkPath copy(path); + SkPath::Direction dir; + bool foundDir = copy.cheapComputeDirection(&dir); + REPORTER_ASSERT(reporter, (gRec[i].fExpectedDirection == SkPath::kUnknown_Direction) + ^ foundDir); + REPORTER_ASSERT(reporter, !foundDir || gRec[i].fExpectedDirection == dir); + check_convexity(reporter, copy, gRec[i].fExpectedConvexity); + } + REPORTER_ASSERT(reporter, gRec[i].fExpectedConvexity == path.getConvexity()); + check_direction(reporter, path, gRec[i].fExpectedDirection); } } @@ -1166,7 +1217,7 @@ static void test_isLine(skiatest::Reporter* reporter) { const SkScalar moveX = SkIntToScalar(1); const SkScalar moveY = SkIntToScalar(2); - SkASSERT(value != moveX && value != moveY); + REPORTER_ASSERT(reporter, value != moveX && value != moveY); path.moveTo(moveX, moveY); REPORTER_ASSERT(reporter, !path.isLine(NULL)); @@ -1177,7 +1228,7 @@ static void test_isLine(skiatest::Reporter* reporter) { const SkScalar lineX = SkIntToScalar(2); const SkScalar lineY = SkIntToScalar(2); - SkASSERT(value != lineX && value != lineY); + REPORTER_ASSERT(reporter, value != lineX && value != lineY); path.lineTo(lineX, lineY); REPORTER_ASSERT(reporter, path.isLine(NULL)); @@ -1193,6 +1244,10 @@ static void test_isLine(skiatest::Reporter* reporter) { REPORTER_ASSERT(reporter, !path.isLine(pts)); REPORTER_ASSERT(reporter, pts[0].equals(moveX, moveY)); REPORTER_ASSERT(reporter, pts[1].equals(lineX, lineY)); + + path.reset(); + path.quadTo(1, 1, 2, 2); + REPORTER_ASSERT(reporter, !path.isLine(NULL)); } static void test_conservativelyContains(skiatest::Reporter* reporter) { @@ -1203,7 +1258,7 @@ static void test_conservativelyContains(skiatest::Reporter* reporter) { // A circle that bounds kBaseRect (with a significant amount of slop) SkScalar circleR = SkMaxScalar(kBaseRect.width(), kBaseRect.height()); - circleR = SkScalarMul(circleR, SkFloatToScalar(1.75f)) / 2; + circleR = SkScalarMul(circleR, 1.75f) / 2; static const SkPoint kCircleC = {kBaseRect.centerX(), kBaseRect.centerY()}; // round-rect radii @@ -1214,74 +1269,75 @@ static void test_conservativelyContains(skiatest::Reporter* reporter) { bool fInRect; bool fInCircle; bool fInRR; + bool fInCubicRR; } kQueries[] = { - {kBaseRect, true, true, false}, + {kBaseRect, true, true, false, false}, // rect well inside of kBaseRect {SkRect::MakeLTRB(kBaseRect.fLeft + SkFloatToScalar(0.25f)*kBaseRect.width(), kBaseRect.fTop + SkFloatToScalar(0.25f)*kBaseRect.height(), kBaseRect.fRight - SkFloatToScalar(0.25f)*kBaseRect.width(), kBaseRect.fBottom - SkFloatToScalar(0.25f)*kBaseRect.height()), - true, true, true}, + true, true, true, true}, // rects with edges off by one from kBaseRect's edges {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop, kBaseRect.width(), kBaseRect.height() + 1), - false, true, false}, + false, true, false, false}, {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop, kBaseRect.width() + 1, kBaseRect.height()), - false, true, false}, + false, true, false, false}, {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop, kBaseRect.width() + 1, kBaseRect.height() + 1), - false, true, false}, + false, true, false, false}, {SkRect::MakeXYWH(kBaseRect.fLeft - 1, kBaseRect.fTop, kBaseRect.width(), kBaseRect.height()), - false, true, false}, + false, true, false, false}, {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop - 1, kBaseRect.width(), kBaseRect.height()), - false, true, false}, + false, true, false, false}, {SkRect::MakeXYWH(kBaseRect.fLeft - 1, kBaseRect.fTop, kBaseRect.width() + 2, kBaseRect.height()), - false, true, false}, + false, true, false, false}, {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop - 1, kBaseRect.width() + 2, kBaseRect.height()), - false, true, false}, + false, true, false, false}, // zero-w/h rects at each corner of kBaseRect - {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop, 0, 0), true, true, false}, - {SkRect::MakeXYWH(kBaseRect.fRight, kBaseRect.fTop, 0, 0), true, true, false}, - {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fBottom, 0, 0), true, true, false}, - {SkRect::MakeXYWH(kBaseRect.fRight, kBaseRect.fBottom, 0, 0), true, true, false}, + {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop, 0, 0), true, true, false, false}, + {SkRect::MakeXYWH(kBaseRect.fRight, kBaseRect.fTop, 0, 0), true, true, false, true}, + {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fBottom, 0, 0), true, true, false, true}, + {SkRect::MakeXYWH(kBaseRect.fRight, kBaseRect.fBottom, 0, 0), true, true, false, true}, // far away rect {SkRect::MakeXYWH(10 * kBaseRect.fRight, 10 * kBaseRect.fBottom, SkIntToScalar(10), SkIntToScalar(10)), - false, false, false}, + false, false, false, false}, // very large rect containing kBaseRect {SkRect::MakeXYWH(kBaseRect.fLeft - 5 * kBaseRect.width(), kBaseRect.fTop - 5 * kBaseRect.height(), 11 * kBaseRect.width(), 11 * kBaseRect.height()), - false, false, false}, + false, false, false, false}, // skinny rect that spans same y-range as kBaseRect {SkRect::MakeXYWH(kBaseRect.centerX(), kBaseRect.fTop, SkIntToScalar(1), kBaseRect.height()), - true, true, true}, + true, true, true, true}, // short rect that spans same x-range as kBaseRect {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.centerY(), kBaseRect.width(), SkScalar(1)), - true, true, true}, + true, true, true, true}, // skinny rect that spans slightly larger y-range than kBaseRect {SkRect::MakeXYWH(kBaseRect.centerX(), kBaseRect.fTop, SkIntToScalar(1), kBaseRect.height() + 1), - false, true, false}, + false, true, false, false}, // short rect that spans slightly larger x-range than kBaseRect {SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.centerY(), kBaseRect.width() + 1, SkScalar(1)), - false, true, false}, + false, true, false, false}, }; for (int inv = 0; inv < 4; ++inv) { @@ -1309,6 +1365,19 @@ static void test_conservativelyContains(skiatest::Reporter* reporter) { path.addRoundRect(kBaseRect, kRRRadii[0], kRRRadii[1], dir); REPORTER_ASSERT(reporter, kQueries[q].fInRR == path.conservativelyContainsRect(qRect)); + + path.reset(); + path.moveTo(kBaseRect.fLeft + kRRRadii[0], kBaseRect.fTop); + path.cubicTo(kBaseRect.fLeft + kRRRadii[0] / 2, kBaseRect.fTop, + kBaseRect.fLeft, kBaseRect.fTop + kRRRadii[1] / 2, + kBaseRect.fLeft, kBaseRect.fTop + kRRRadii[1]); + path.lineTo(kBaseRect.fLeft, kBaseRect.fBottom); + path.lineTo(kBaseRect.fRight, kBaseRect.fBottom); + path.lineTo(kBaseRect.fRight, kBaseRect.fTop); + path.close(); + REPORTER_ASSERT(reporter, kQueries[q].fInCubicRR == + path.conservativelyContainsRect(qRect)); + } // Slightly non-convex shape, shouldn't contain any rects. path.reset(); @@ -1361,6 +1430,9 @@ static void test_conservativelyContains(skiatest::Reporter* reporter) { SkIntToScalar(10), SkIntToScalar(10)))); + path.reset(); + path.lineTo(100, 100); + REPORTER_ASSERT(reporter, !path.conservativelyContainsRect(SkRect::MakeXYWH(0, 0, 1, 1))); } static void test_isRect_open_close(skiatest::Reporter* reporter) { @@ -1818,6 +1890,13 @@ static void test_flattening(skiatest::Reporter* reporter) { REPORTER_ASSERT(reporter, size1 == size3); REPORTER_ASSERT(reporter, p == p2); + size3 = p2.readFromMemory(buffer, 0); + REPORTER_ASSERT(reporter, !size3); + + SkPath tooShort; + size3 = tooShort.readFromMemory(buffer, size1 - 1); + REPORTER_ASSERT(reporter, tooShort.isEmpty()); + char buffer2[1024]; size3 = p2.writeToMemory(buffer2); REPORTER_ASSERT(reporter, size1 == size3); @@ -1836,17 +1915,25 @@ static void test_flattening(skiatest::Reporter* reporter) { static void test_transform(skiatest::Reporter* reporter) { SkPath p, p1; +#define CONIC_PERSPECTIVE_BUG_FIXED 0 static const SkPoint pts[] = { - { 0, 0 }, - { SkIntToScalar(10), SkIntToScalar(10) }, - { SkIntToScalar(20), SkIntToScalar(10) }, { SkIntToScalar(20), 0 }, - { 0, 0 }, { 0, SkIntToScalar(10) }, { SkIntToScalar(1), SkIntToScalar(10) } + { 0, 0 }, // move + { SkIntToScalar(10), SkIntToScalar(10) }, // line + { SkIntToScalar(20), SkIntToScalar(10) }, { SkIntToScalar(20), 0 }, // quad + { 0, 0 }, { 0, SkIntToScalar(10) }, { SkIntToScalar(1), SkIntToScalar(10) }, // cubic +#if CONIC_PERSPECTIVE_BUG_FIXED + { 0, 0 }, { SkIntToScalar(20), SkIntToScalar(10) }, // conic +#endif }; + const int kPtCount = SK_ARRAY_COUNT(pts); p.moveTo(pts[0]); p.lineTo(pts[1]); p.quadTo(pts[2], pts[3]); p.cubicTo(pts[4], pts[5], pts[6]); - +#if CONIC_PERSPECTIVE_BUG_FIXED + p.conicTo(pts[4], pts[5], 0.5f); +#endif + p.close(); SkMatrix matrix; matrix.reset(); p.transform(matrix, &p1); @@ -1854,13 +1941,36 @@ static void test_transform(skiatest::Reporter* reporter) { matrix.setScale(SK_Scalar1 * 2, SK_Scalar1 * 3); p.transform(matrix, &p1); - SkPoint pts1[7]; - int count = p1.getPoints(pts1, 7); - REPORTER_ASSERT(reporter, 7 == count); + SkPoint pts1[kPtCount]; + int count = p1.getPoints(pts1, kPtCount); + REPORTER_ASSERT(reporter, kPtCount == count); for (int i = 0; i < count; ++i) { SkPoint newPt = SkPoint::Make(pts[i].fX * 2, pts[i].fY * 3); REPORTER_ASSERT(reporter, newPt == pts1[i]); } + matrix.reset(); + matrix.setPerspX(SkScalarToPersp(4)); + p.transform(matrix, &p1); + REPORTER_ASSERT(reporter, matrix.invert(&matrix)); + p1.transform(matrix, NULL); + SkRect pBounds = p.getBounds(); + SkRect p1Bounds = p1.getBounds(); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(pBounds.fLeft, p1Bounds.fLeft)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(pBounds.fTop, p1Bounds.fTop)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(pBounds.fRight, p1Bounds.fRight)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(pBounds.fBottom, p1Bounds.fBottom)); + + matrix.reset(); + p.reset(); + p.addCircle(0, 0, 1, SkPath::kCW_Direction); + p.transform(matrix, &p1); + REPORTER_ASSERT(reporter, p1.cheapIsDirection(SkPath::kCW_Direction)); + matrix.setScaleX(-1); + p.transform(matrix, &p1); + REPORTER_ASSERT(reporter, p1.cheapIsDirection(SkPath::kCCW_Direction)); + matrix.setAll(1, 1, 0, 1, 1, 0, 0, 0, 1); + p.transform(matrix, &p1); + REPORTER_ASSERT(reporter, p1.cheapIsDirection(SkPath::kUnknown_Direction)); } static void test_zero_length_paths(skiatest::Reporter* reporter) { @@ -2064,6 +2174,71 @@ static void test_iter(skiatest::Reporter* reporter) { REPORTER_ASSERT(reporter, j == (int)gIterTests[i].numResultVerbs); } + p.reset(); + iter.setPath(p, false); + REPORTER_ASSERT(reporter, !iter.isClosedContour()); + p.lineTo(1, 1); + p.close(); + iter.setPath(p, false); + REPORTER_ASSERT(reporter, iter.isClosedContour()); + p.reset(); + iter.setPath(p, true); + REPORTER_ASSERT(reporter, !iter.isClosedContour()); + p.lineTo(1, 1); + iter.setPath(p, true); + REPORTER_ASSERT(reporter, iter.isClosedContour()); + p.moveTo(0, 0); + p.lineTo(2, 2); + iter.setPath(p, false); + REPORTER_ASSERT(reporter, !iter.isClosedContour()); + + // this checks to see if the NaN logic is executed in SkPath::autoClose(), but does not + // check to see if the result is correct. + for (int setNaN = 0; setNaN < 4; ++setNaN) { + p.reset(); + p.moveTo(setNaN == 0 ? SK_ScalarNaN : 0, setNaN == 1 ? SK_ScalarNaN : 0); + p.lineTo(setNaN == 2 ? SK_ScalarNaN : 1, setNaN == 3 ? SK_ScalarNaN : 1); + iter.setPath(p, true); + iter.next(pts, false); + iter.next(pts, false); + REPORTER_ASSERT(reporter, SkPath::kClose_Verb == iter.next(pts, false)); + } + + p.reset(); + p.quadTo(0, 0, 0, 0); + iter.setPath(p, false); + iter.next(pts, false); + REPORTER_ASSERT(reporter, SkPath::kQuad_Verb == iter.next(pts, false)); + iter.setPath(p, false); + iter.next(pts, false); + REPORTER_ASSERT(reporter, SkPath::kDone_Verb == iter.next(pts, true)); + + p.reset(); + p.conicTo(0, 0, 0, 0, 0.5f); + iter.setPath(p, false); + iter.next(pts, false); + REPORTER_ASSERT(reporter, SkPath::kConic_Verb == iter.next(pts, false)); + iter.setPath(p, false); + iter.next(pts, false); + REPORTER_ASSERT(reporter, SkPath::kDone_Verb == iter.next(pts, true)); + + p.reset(); + p.cubicTo(0, 0, 0, 0, 0, 0); + iter.setPath(p, false); + iter.next(pts, false); + REPORTER_ASSERT(reporter, SkPath::kCubic_Verb == iter.next(pts, false)); + iter.setPath(p, false); + iter.next(pts, false); + REPORTER_ASSERT(reporter, SkPath::kDone_Verb == iter.next(pts, true)); + + p.moveTo(1, 1); // add a trailing moveto + iter.setPath(p, false); + iter.next(pts, false); + REPORTER_ASSERT(reporter, SkPath::kCubic_Verb == iter.next(pts, false)); + iter.setPath(p, false); + iter.next(pts, false); + REPORTER_ASSERT(reporter, SkPath::kDone_Verb == iter.next(pts, true)); + // The GM degeneratesegments.cpp test is more extensive } @@ -2323,7 +2498,7 @@ static void test_circle_skew(skiatest::Reporter* reporter, if (SkPath::kCCW_Direction == dir) { dir = SkPath::kCW_Direction; } else { - SkASSERT(SkPath::kCW_Direction == dir); + REPORTER_ASSERT(reporter, SkPath::kCW_Direction == dir); dir = SkPath::kCCW_Direction; } check_for_circle(reporter, tmp, false, dir); @@ -2381,7 +2556,7 @@ static void test_circle_mirror_x(skiatest::Reporter* reporter, if (SkPath::kCW_Direction == dir) { dir = SkPath::kCCW_Direction; } else { - SkASSERT(SkPath::kCCW_Direction == dir); + REPORTER_ASSERT(reporter, SkPath::kCCW_Direction == dir); dir = SkPath::kCW_Direction; } @@ -2400,7 +2575,7 @@ static void test_circle_mirror_y(skiatest::Reporter* reporter, if (SkPath::kCW_Direction == dir) { dir = SkPath::kCCW_Direction; } else { - SkASSERT(SkPath::kCCW_Direction == dir); + REPORTER_ASSERT(reporter, SkPath::kCCW_Direction == dir); dir = SkPath::kCW_Direction; } @@ -2502,6 +2677,11 @@ static void test_circle(skiatest::Reporter* reporter) { check_for_circle(reporter, path, false, SkPath::kCW_Direction); test_circle_with_add_paths(reporter); + + // test negative radius + path.reset(); + path.addCircle(0, 0, -1, SkPath::kCW_Direction); + REPORTER_ASSERT(reporter, path.isEmpty()); } static void test_oval(skiatest::Reporter* reporter) { @@ -2573,8 +2753,10 @@ static void test_empty(skiatest::Reporter* reporter, const SkPath& p) { REPORTER_ASSERT(reporter, !(p != empty)); } -static void test_rrect_is_convex(skiatest::Reporter* reporter, SkPath* path) { +static void test_rrect_is_convex(skiatest::Reporter* reporter, SkPath* path, + SkPath::Direction dir) { REPORTER_ASSERT(reporter, path->isConvex()); + REPORTER_ASSERT(reporter, path->cheapIsDirection(dir)); path->setConvexity(SkPath::kUnknown_Convexity); REPORTER_ASSERT(reporter, path->isConvex()); path->reset(); @@ -2587,19 +2769,373 @@ static void test_rrect(skiatest::Reporter* reporter) { SkRect r = {10, 20, 30, 40}; rr.setRectRadii(r, radii); p.addRRect(rr); - test_rrect_is_convex(reporter, &p); + test_rrect_is_convex(reporter, &p, SkPath::kCW_Direction); p.addRRect(rr, SkPath::kCCW_Direction); - test_rrect_is_convex(reporter, &p); + test_rrect_is_convex(reporter, &p, SkPath::kCCW_Direction); p.addRoundRect(r, &radii[0].fX); - test_rrect_is_convex(reporter, &p); + test_rrect_is_convex(reporter, &p, SkPath::kCW_Direction); p.addRoundRect(r, &radii[0].fX, SkPath::kCCW_Direction); - test_rrect_is_convex(reporter, &p); + test_rrect_is_convex(reporter, &p, SkPath::kCCW_Direction); p.addRoundRect(r, radii[1].fX, radii[1].fY); - test_rrect_is_convex(reporter, &p); + test_rrect_is_convex(reporter, &p, SkPath::kCW_Direction); p.addRoundRect(r, radii[1].fX, radii[1].fY, SkPath::kCCW_Direction); - test_rrect_is_convex(reporter, &p); + test_rrect_is_convex(reporter, &p, SkPath::kCCW_Direction); + for (size_t i = 0; i < SK_ARRAY_COUNT(radii); ++i) { + SkVector save = radii[i]; + radii[i].set(0, 0); + rr.setRectRadii(r, radii); + p.addRRect(rr); + test_rrect_is_convex(reporter, &p, SkPath::kCW_Direction); + radii[i] = save; + } + p.addRoundRect(r, 0, 0); + SkRect returnedRect; + REPORTER_ASSERT(reporter, p.isRect(&returnedRect)); + REPORTER_ASSERT(reporter, returnedRect == r); + test_rrect_is_convex(reporter, &p, SkPath::kCW_Direction); + SkVector zeroRadii[] = {{0, 0}, {0, 0}, {0, 0}, {0, 0}}; + rr.setRectRadii(r, zeroRadii); + p.addRRect(rr); + bool closed; + SkPath::Direction dir; + REPORTER_ASSERT(reporter, p.isRect(&closed, &dir)); + REPORTER_ASSERT(reporter, closed); + REPORTER_ASSERT(reporter, SkPath::kCW_Direction == dir); + test_rrect_is_convex(reporter, &p, SkPath::kCW_Direction); + p.addRRect(rr, SkPath::kCW_Direction); + p.addRRect(rr, SkPath::kCW_Direction); + REPORTER_ASSERT(reporter, !p.isConvex()); + p.reset(); + p.addRRect(rr, SkPath::kCCW_Direction); + p.addRRect(rr, SkPath::kCCW_Direction); + REPORTER_ASSERT(reporter, !p.isConvex()); + p.reset(); + SkRect emptyR = {10, 20, 10, 30}; + rr.setRectRadii(emptyR, radii); + p.addRRect(rr); + REPORTER_ASSERT(reporter, p.isEmpty()); + SkRect largeR = {0, 0, SK_ScalarMax, SK_ScalarMax}; + rr.setRectRadii(largeR, radii); + p.addRRect(rr); + test_rrect_is_convex(reporter, &p, SkPath::kCW_Direction); + SkRect infR = {0, 0, SK_ScalarMax, SK_ScalarInfinity}; + rr.setRectRadii(infR, radii); + p.addRRect(rr); + test_rrect_is_convex(reporter, &p, SkPath::kCW_Direction); + SkRect tinyR = {0, 0, 1e-9f, 1e-9f}; + p.addRoundRect(tinyR, 5e-11f, 5e-11f); + test_rrect_is_convex(reporter, &p, SkPath::kCW_Direction); +} + +static void test_arc(skiatest::Reporter* reporter) { + SkPath p; + SkRect emptyOval = {10, 20, 30, 20}; + REPORTER_ASSERT(reporter, emptyOval.isEmpty()); + p.addArc(emptyOval, 1, 2); + REPORTER_ASSERT(reporter, p.isEmpty()); + p.reset(); + SkRect oval = {10, 20, 30, 40}; + p.addArc(oval, 1, 0); + REPORTER_ASSERT(reporter, p.isEmpty()); + p.reset(); + SkPath cwOval; + cwOval.addOval(oval); + p.addArc(oval, 1, 360); + REPORTER_ASSERT(reporter, p == cwOval); + p.reset(); + SkPath ccwOval; + ccwOval.addOval(oval, SkPath::kCCW_Direction); + p.addArc(oval, 1, -360); + REPORTER_ASSERT(reporter, p == ccwOval); + p.reset(); + p.addArc(oval, 1, 180); + REPORTER_ASSERT(reporter, p.isConvex()); + REPORTER_ASSERT(reporter, p.cheapIsDirection(SkPath::kCW_Direction)); + p.setConvexity(SkPath::kUnknown_Convexity); + REPORTER_ASSERT(reporter, p.isConvex()); +} + +static void check_move(skiatest::Reporter* reporter, SkPath::RawIter* iter, + SkScalar x0, SkScalar y0) { + SkPoint pts[4]; + SkPath::Verb v = iter->next(pts); + REPORTER_ASSERT(reporter, v == SkPath::kMove_Verb); + REPORTER_ASSERT(reporter, pts[0].fX == x0); + REPORTER_ASSERT(reporter, pts[0].fY == y0); +} + +static void check_line(skiatest::Reporter* reporter, SkPath::RawIter* iter, + SkScalar x1, SkScalar y1) { + SkPoint pts[4]; + SkPath::Verb v = iter->next(pts); + REPORTER_ASSERT(reporter, v == SkPath::kLine_Verb); + REPORTER_ASSERT(reporter, pts[1].fX == x1); + REPORTER_ASSERT(reporter, pts[1].fY == y1); +} + +static void check_quad(skiatest::Reporter* reporter, SkPath::RawIter* iter, + SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) { + SkPoint pts[4]; + SkPath::Verb v = iter->next(pts); + REPORTER_ASSERT(reporter, v == SkPath::kQuad_Verb); + REPORTER_ASSERT(reporter, pts[1].fX == x1); + REPORTER_ASSERT(reporter, pts[1].fY == y1); + REPORTER_ASSERT(reporter, pts[2].fX == x2); + REPORTER_ASSERT(reporter, pts[2].fY == y2); +} + +static void check_done(skiatest::Reporter* reporter, SkPath* p, SkPath::RawIter* iter) { + SkPoint pts[4]; + SkPath::Verb v = iter->next(pts); + REPORTER_ASSERT(reporter, v == SkPath::kDone_Verb); +} + +static void check_done_and_reset(skiatest::Reporter* reporter, SkPath* p, SkPath::RawIter* iter) { + check_done(reporter, p, iter); + p->reset(); +} + +static void check_path_is_move_and_reset(skiatest::Reporter* reporter, SkPath* p, + SkScalar x0, SkScalar y0) { + SkPath::RawIter iter(*p); + check_move(reporter, &iter, x0, y0); + check_done_and_reset(reporter, p, &iter); +} + +static void check_path_is_line_and_reset(skiatest::Reporter* reporter, SkPath* p, + SkScalar x1, SkScalar y1) { + SkPath::RawIter iter(*p); + check_move(reporter, &iter, 0, 0); + check_line(reporter, &iter, x1, y1); + check_done_and_reset(reporter, p, &iter); +} + +static void check_path_is_line(skiatest::Reporter* reporter, SkPath* p, + SkScalar x1, SkScalar y1) { + SkPath::RawIter iter(*p); + check_move(reporter, &iter, 0, 0); + check_line(reporter, &iter, x1, y1); + check_done(reporter, p, &iter); +} + +static void check_path_is_line_pair_and_reset(skiatest::Reporter* reporter, SkPath* p, + SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) { + SkPath::RawIter iter(*p); + check_move(reporter, &iter, 0, 0); + check_line(reporter, &iter, x1, y1); + check_line(reporter, &iter, x2, y2); + check_done_and_reset(reporter, p, &iter); +} + +static void check_path_is_quad_and_reset(skiatest::Reporter* reporter, SkPath* p, + SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) { + SkPath::RawIter iter(*p); + check_move(reporter, &iter, 0, 0); + check_quad(reporter, &iter, x1, y1, x2, y2); + check_done_and_reset(reporter, p, &iter); } +static void test_arcTo(skiatest::Reporter* reporter) { + SkPath p; + p.arcTo(0, 0, 1, 2, 1); + check_path_is_line_and_reset(reporter, &p, 0, 0); + p.arcTo(1, 2, 1, 2, 1); + check_path_is_line_and_reset(reporter, &p, 1, 2); + p.arcTo(1, 2, 3, 4, 0); + check_path_is_line_and_reset(reporter, &p, 1, 2); + p.arcTo(1, 2, 0, 0, 1); + check_path_is_line_and_reset(reporter, &p, 1, 2); + p.arcTo(1, 0, 1, 1, 1); + SkPoint pt; + REPORTER_ASSERT(reporter, p.getLastPt(&pt) && pt.fX == 1 && pt.fY == 1); + p.reset(); + p.arcTo(1, 0, 1, -1, 1); + REPORTER_ASSERT(reporter, p.getLastPt(&pt) && pt.fX == 1 && pt.fY == -1); + p.reset(); + SkRect oval = {1, 2, 3, 4}; + p.arcTo(oval, 0, 0, true); + check_path_is_move_and_reset(reporter, &p, oval.fRight, oval.centerY()); + p.arcTo(oval, 0, 0, false); + check_path_is_move_and_reset(reporter, &p, oval.fRight, oval.centerY()); + p.arcTo(oval, 360, 0, true); + check_path_is_move_and_reset(reporter, &p, oval.fRight, oval.centerY()); + p.arcTo(oval, 360, 0, false); + check_path_is_move_and_reset(reporter, &p, oval.fRight, oval.centerY()); + for (float sweep = 359, delta = 0.5f; sweep != (float) (sweep + delta); ) { + p.arcTo(oval, 0, SkFloatToScalar(sweep), false); + REPORTER_ASSERT(reporter, p.getBounds() == oval); + sweep += delta; + delta /= 2; + } + for (float sweep = 361, delta = 0.5f; sweep != (float) (sweep - delta);) { + p.arcTo(oval, 0, SkFloatToScalar(sweep), false); + REPORTER_ASSERT(reporter, p.getBounds() == oval); + sweep -= delta; + delta /= 2; + } + SkRect noOvalWidth = {1, 2, 0, 3}; + p.reset(); + p.arcTo(noOvalWidth, 0, 360, false); + REPORTER_ASSERT(reporter, p.isEmpty()); + + SkRect noOvalHeight = {1, 2, 3, 1}; + p.reset(); + p.arcTo(noOvalHeight, 0, 360, false); + REPORTER_ASSERT(reporter, p.isEmpty()); +} + +static void test_addPath(skiatest::Reporter* reporter) { + SkPath p, q; + p.lineTo(1, 2); + q.moveTo(4, 4); + q.lineTo(7, 8); + q.conicTo(8, 7, 6, 5, 0.5f); + q.quadTo(6, 7, 8, 6); + q.cubicTo(5, 6, 7, 8, 7, 5); + q.close(); + p.addPath(q, -4, -4); + SkRect expected = {0, 0, 4, 4}; + REPORTER_ASSERT(reporter, p.getBounds() == expected); + p.reset(); + p.reverseAddPath(q); + SkRect reverseExpected = {4, 4, 8, 8}; + REPORTER_ASSERT(reporter, p.getBounds() == reverseExpected); +} + +static void test_conicTo_special_case(skiatest::Reporter* reporter) { + SkPath p; + p.conicTo(1, 2, 3, 4, -1); + check_path_is_line_and_reset(reporter, &p, 3, 4); + p.conicTo(1, 2, 3, 4, SK_ScalarInfinity); + check_path_is_line_pair_and_reset(reporter, &p, 1, 2, 3, 4); + p.conicTo(1, 2, 3, 4, 1); + check_path_is_quad_and_reset(reporter, &p, 1, 2, 3, 4); +} + +static void test_get_point(skiatest::Reporter* reporter) { + SkPath p; + SkPoint pt = p.getPoint(0); + REPORTER_ASSERT(reporter, pt == SkPoint::Make(0, 0)); + REPORTER_ASSERT(reporter, !p.getLastPt(NULL)); + REPORTER_ASSERT(reporter, !p.getLastPt(&pt) && pt == SkPoint::Make(0, 0)); + p.setLastPt(10, 10); + pt = p.getPoint(0); + REPORTER_ASSERT(reporter, pt == SkPoint::Make(10, 10)); + REPORTER_ASSERT(reporter, p.getLastPt(NULL)); + p.rMoveTo(10, 10); + REPORTER_ASSERT(reporter, p.getLastPt(&pt) && pt == SkPoint::Make(20, 20)); +} + +static void test_contains(skiatest::Reporter* reporter) { + SkPath p; + p.setFillType(SkPath::kInverseWinding_FillType); + REPORTER_ASSERT(reporter, p.contains(0, 0)); + p.setFillType(SkPath::kWinding_FillType); + REPORTER_ASSERT(reporter, !p.contains(0, 0)); + p.moveTo(4, 4); + p.lineTo(6, 8); + p.lineTo(8, 4); + // test quick reject + REPORTER_ASSERT(reporter, !p.contains(4, 0)); + REPORTER_ASSERT(reporter, !p.contains(0, 4)); + REPORTER_ASSERT(reporter, !p.contains(4, 10)); + REPORTER_ASSERT(reporter, !p.contains(10, 4)); + // test various crossings in x + REPORTER_ASSERT(reporter, !p.contains(5, 7)); + REPORTER_ASSERT(reporter, p.contains(6, 7)); + REPORTER_ASSERT(reporter, !p.contains(7, 7)); + p.reset(); + p.moveTo(4, 4); + p.lineTo(8, 6); + p.lineTo(4, 8); + // test various crossings in y + REPORTER_ASSERT(reporter, !p.contains(7, 5)); + REPORTER_ASSERT(reporter, p.contains(7, 6)); + REPORTER_ASSERT(reporter, !p.contains(7, 7)); + // test quads + p.reset(); + p.moveTo(4, 4); + p.quadTo(6, 6, 8, 8); + p.quadTo(6, 8, 4, 8); + p.quadTo(4, 6, 4, 4); + REPORTER_ASSERT(reporter, p.contains(5, 6)); + REPORTER_ASSERT(reporter, !p.contains(6, 5)); + + p.reset(); + p.moveTo(6, 6); + p.quadTo(8, 8, 6, 8); + p.quadTo(4, 8, 4, 6); + p.quadTo(4, 4, 6, 6); + REPORTER_ASSERT(reporter, p.contains(5, 6)); + REPORTER_ASSERT(reporter, !p.contains(6, 5)); + +#define CONIC_CONTAINS_BUG_FIXED 0 +#if CONIC_CONTAINS_BUG_FIXED + p.reset(); + p.moveTo(4, 4); + p.conicTo(6, 6, 8, 8, 0.5f); + p.conicTo(6, 8, 4, 8, 0.5f); + p.conicTo(4, 6, 4, 4, 0.5f); + REPORTER_ASSERT(reporter, p.contains(5, 6)); + REPORTER_ASSERT(reporter, !p.contains(6, 5)); +#endif + + // test cubics + SkPoint pts[] = {{5, 4}, {6, 5}, {7, 6}, {6, 6}, {4, 6}, {5, 7}, {5, 5}, {5, 4}, {6, 5}, {7, 6}}; + for (int i = 0; i < 3; ++i) { + p.reset(); + p.setFillType(SkPath::kEvenOdd_FillType); + p.moveTo(pts[i].fX, pts[i].fY); + p.cubicTo(pts[i + 1].fX, pts[i + 1].fY, pts[i + 2].fX, pts[i + 2].fY, pts[i + 3].fX, pts[i + 3].fY); + p.cubicTo(pts[i + 4].fX, pts[i + 4].fY, pts[i + 5].fX, pts[i + 5].fY, pts[i + 6].fX, pts[i + 6].fY); + p.close(); + REPORTER_ASSERT(reporter, p.contains(5.5f, 5.5f)); + REPORTER_ASSERT(reporter, !p.contains(4.5f, 5.5f)); + } +} + +static void test_operatorEqual(skiatest::Reporter* reporter) { + SkPath a; + SkPath b; + REPORTER_ASSERT(reporter, a == a); + REPORTER_ASSERT(reporter, a == b); + a.setFillType(SkPath::kInverseWinding_FillType); + REPORTER_ASSERT(reporter, a != b); + a.reset(); + REPORTER_ASSERT(reporter, a == b); + a.lineTo(1, 1); + REPORTER_ASSERT(reporter, a != b); + a.reset(); + REPORTER_ASSERT(reporter, a == b); + a.lineTo(1, 1); + b.lineTo(1, 2); + REPORTER_ASSERT(reporter, a != b); + a.reset(); + a.lineTo(1, 2); + REPORTER_ASSERT(reporter, a == b); +} + +class PathTest_Private { +public: + static void TestPathTo(skiatest::Reporter* reporter) { + SkPath p, q; + p.lineTo(4, 4); + p.reversePathTo(q); + check_path_is_line(reporter, &p, 4, 4); + q.moveTo(-4, -4); + p.reversePathTo(q); + check_path_is_line(reporter, &p, 4, 4); + q.lineTo(7, 8); + q.conicTo(8, 7, 6, 5, 0.5f); + q.quadTo(6, 7, 8, 6); + q.cubicTo(5, 6, 7, 8, 7, 5); + q.close(); + p.reversePathTo(q); + SkRect reverseExpected = {-4, -4, 8, 8}; + REPORTER_ASSERT(reporter, p.getBounds() == reverseExpected); + } +}; + static void TestPath(skiatest::Reporter* reporter) { SkTSize<SkScalar>::Make(3,4); @@ -2609,6 +3145,13 @@ static void TestPath(skiatest::Reporter* reporter) { REPORTER_ASSERT(reporter, p.getBounds().isEmpty()); + // this triggers a code path in SkPath::operator= which is otherwise unexercised + SkPath& self = p; + p = self; + + // this triggers a code path in SkPath::swap which is otherwise unexercised + p.swap(self); + bounds.set(0, 0, SK_Scalar1, SK_Scalar1); p.addRoundRect(bounds, SK_Scalar1, SK_Scalar1); @@ -2668,6 +3211,7 @@ static void TestPath(skiatest::Reporter* reporter) { p.addRect(bounds); REPORTER_ASSERT(reporter, !p.isRect(NULL)); + test_operatorEqual(reporter); test_isLine(reporter); test_isRect(reporter); test_isNestedRects(reporter); @@ -2691,6 +3235,7 @@ static void TestPath(skiatest::Reporter* reporter) { test_isfinite_after_transform(reporter); test_arb_round_rect_is_convex(reporter); test_arb_zero_rad_round_rect_is_rect(reporter); + test_addrect(reporter); test_addrect_isfinite(reporter); test_tricky_cubic(); test_clipped_cubic(); @@ -2702,6 +3247,13 @@ static void TestPath(skiatest::Reporter* reporter) { test_path_close_issue1474(reporter); test_path_to_region(reporter); test_rrect(reporter); + test_arc(reporter); + test_arcTo(reporter); + test_addPath(reporter); + test_conicTo_special_case(reporter); + test_get_point(reporter); + test_contains(reporter); + PathTest_Private::TestPathTo(reporter); } #include "TestClassDef.h" diff --git a/tools/coverage.sh b/tools/coverage.sh index 3e34806843..8fe75c5cd5 100755 --- a/tools/coverage.sh +++ b/tools/coverage.sh @@ -27,5 +27,5 @@ lcov $QUIET --gcov-tool=$GCOV -c -b out/Coverage -d out/Coverage -o /tmp/coverag lcov $QUIET -a /tmp/baseline -a /tmp/coverage -o /tmp/merged -genhtml $QUIET /tmp/merged -o out/Coverage/report +genhtml $QUIET /tmp/merged --legend -o out/Coverage/report xdg-open out/Coverage/report/index.html |