Add SkPathPriv::IsSimpleClosedRect
This function looks for "simple" rect paths. Simple here means begins and ends at a corner and is closed (either manually or with a close verb). Unlike SkPath::isRect this returns the starting point index (using the same start indexing scheme as SkPath::addRect).
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2017313002
Review-Url: https://codereview.chromium.org/2017313002
diff --git a/src/core/SkPath.cpp b/src/core/SkPath.cpp
index 5cd6316..84181a3 100644
--- a/src/core/SkPath.cpp
+++ b/src/core/SkPath.cpp
@@ -3249,3 +3249,124 @@
const SkConic conic(p0, p1, p2, w);
return conic.chopIntoQuadsPOW2(pts, pow2);
}
+
+bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
+ unsigned* start) {
+ if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
+ return false;
+ }
+ SkPath::RawIter iter(path);
+ SkPoint verbPts[4];
+ SkPath::Verb v;
+ SkPoint rectPts[5];
+ int rectPtCnt = 0;
+ while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
+ switch (v) {
+ case SkPath::kMove_Verb:
+ if (0 != rectPtCnt) {
+ return false;
+ }
+ rectPts[0] = verbPts[0];
+ ++rectPtCnt;
+ break;
+ case SkPath::kLine_Verb:
+ if (5 == rectPtCnt) {
+ return false;
+ }
+ rectPts[rectPtCnt] = verbPts[1];
+ ++rectPtCnt;
+ break;
+ case SkPath::kClose_Verb:
+ if (4 == rectPtCnt) {
+ rectPts[4] = rectPts[0];
+ rectPtCnt = 5;
+ }
+ break;
+ default:
+ return false;
+ }
+ }
+ if (rectPtCnt < 5) {
+ return false;
+ }
+ if (rectPts[0] != rectPts[4]) {
+ return false;
+ }
+ int verticalCnt = 0;
+ int horizontalCnt = 0;
+ // dirs are 0 - right, 1 - down, 2 - left, 3 - up.
+ int firstDir;
+ int secondDir;
+ SkRect tempRect;
+ for (int i = 0; i < 4; ++i) {
+ int sameCnt = 0;
+ if (rectPts[i].fX == rectPts[i + 1].fX) {
+ verticalCnt += 1;
+ sameCnt = 1;
+ if (0 == i) {
+ if (rectPts[1].fY > rectPts[0].fY) {
+ firstDir = 1;
+ tempRect.fTop = rectPts[0].fY;
+ tempRect.fBottom = rectPts[1].fY;
+ } else {
+ firstDir = 3;
+ tempRect.fTop = rectPts[1].fY;
+ tempRect.fBottom = rectPts[0].fY;
+ }
+ } else if (1 == i) {
+ if (rectPts[2].fY > rectPts[1].fY) {
+ secondDir = 1;
+ tempRect.fTop = rectPts[1].fY;
+ tempRect.fBottom = rectPts[2].fY;
+ } else {
+ secondDir = 3;
+ tempRect.fTop = rectPts[2].fY;
+ tempRect.fBottom = rectPts[1].fY;
+ }
+ }
+ }
+ if (rectPts[i].fY == rectPts[i + 1].fY) {
+ horizontalCnt += 1;
+ sameCnt += 1;
+ if (0 == i) {
+ if (rectPts[1].fX > rectPts[0].fX) {
+ firstDir = 0;
+ tempRect.fLeft = rectPts[0].fX;
+ tempRect.fRight = rectPts[1].fX;
+ } else {
+ firstDir = 2;
+ tempRect.fLeft = rectPts[1].fX;
+ tempRect.fRight = rectPts[0].fX;
+ }
+ } else if (1 == i) {
+ if (rectPts[2].fX > rectPts[1].fX) {
+ secondDir = 0;
+ tempRect.fLeft = rectPts[1].fX;
+ tempRect.fRight = rectPts[2].fX;
+ } else {
+ secondDir = 2;
+ tempRect.fLeft = rectPts[2].fX;
+ tempRect.fRight = rectPts[1].fX;
+ }
+ }
+ }
+ if (sameCnt != 1) {
+ return false;
+ }
+ }
+ if (2 != horizontalCnt || 2 != verticalCnt) {
+ return false;
+ }
+ // low bit indicates a vertical dir
+ SkASSERT((firstDir ^ secondDir) & 0b1);
+ if (((firstDir + 1) & 0b11) == secondDir) {
+ *direction = SkPath::kCW_Direction;
+ *start = firstDir;
+ } else {
+ SkASSERT(((secondDir + 1) & 0b11) == firstDir);
+ *direction = SkPath::kCCW_Direction;
+ *start = secondDir;
+ }
+ *rect = tempRect;
+ return true;
+}
diff --git a/src/core/SkPathPriv.h b/src/core/SkPathPriv.h
index f0bab95..8689dee 100644
--- a/src/core/SkPathPriv.h
+++ b/src/core/SkPathPriv.h
@@ -83,6 +83,14 @@
static void AddGenIDChangeListener(const SkPath& path, SkPathRef::GenIDChangeListener* listener) {
path.fPathRef->addGenIDChangeListener(listener);
}
+
+ /**
+ * This returns true for a rect that begins and ends at the same corner and has either a move
+ * followed by four lines or a move followed by 3 lines and a close. None of the parameters are
+ * optional. This does not permit degenerate line or point rectangles.
+ */
+ static bool IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
+ unsigned* start);
};
#endif
diff --git a/tests/PathTest.cpp b/tests/PathTest.cpp
index f4b488b..b406f25 100644
--- a/tests/PathTest.cpp
+++ b/tests/PathTest.cpp
@@ -1975,6 +1975,103 @@
REPORTER_ASSERT(reporter, !path1.isRect(nullptr));
}
+static void check_simple_closed_rect(skiatest::Reporter* reporter, const SkPath& path,
+ const SkRect& rect, SkPath::Direction dir, unsigned start) {
+ SkRect r = SkRect::MakeEmpty();
+ SkPath::Direction d = SkPath::kCCW_Direction;
+ unsigned s = ~0U;
+
+ REPORTER_ASSERT(reporter, SkPathPriv::IsSimpleClosedRect(path, &r, &d, &s));
+ REPORTER_ASSERT(reporter, r == rect);
+ REPORTER_ASSERT(reporter, d == dir);
+ REPORTER_ASSERT(reporter, s == start);
+}
+
+static void test_is_simple_closed_rect(skiatest::Reporter* reporter) {
+ SkRect r = SkRect::MakeEmpty();
+ SkPath::Direction d = SkPath::kCCW_Direction;
+ unsigned s = ~0U;
+
+ const SkRect testRect = SkRect::MakeXYWH(10, 10, 50, 70);
+ const SkRect emptyRect = SkRect::MakeEmpty();
+ SkPath path;
+ for (int start = 0; start < 4; ++start) {
+ for (auto dir : {SkPath::kCCW_Direction, SkPath::kCW_Direction}) {
+ SkPath path;
+ path.addRect(testRect, dir, start);
+ check_simple_closed_rect(reporter, path, testRect, dir, start);
+ path.close();
+ check_simple_closed_rect(reporter, path, testRect, dir, start);
+ SkPath path2 = path;
+ path2.lineTo(10, 10);
+ REPORTER_ASSERT(reporter, !SkPathPriv::IsSimpleClosedRect(path2, &r, &d, &s));
+ path2 = path;
+ path2.moveTo(10, 10);
+ REPORTER_ASSERT(reporter, !SkPathPriv::IsSimpleClosedRect(path2, &r, &d, &s));
+ path2 = path;
+ path2.addRect(testRect, dir, start);
+ REPORTER_ASSERT(reporter, !SkPathPriv::IsSimpleClosedRect(path2, &r, &d, &s));
+ // Make the path by hand, manually closing it.
+ path2.reset();
+ SkPath::RawIter iter(path);
+ SkPath::Verb v;
+ SkPoint verbPts[4];
+ SkPoint firstPt = {0.f, 0.f};
+ while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
+ switch(v) {
+ case SkPath::kMove_Verb:
+ firstPt = verbPts[0];
+ path2.moveTo(verbPts[0]);
+ break;
+ case SkPath::kLine_Verb:
+ path2.lineTo(verbPts[1]);
+ break;
+ default:
+ break;
+ }
+ }
+ // We haven't closed it yet...
+ REPORTER_ASSERT(reporter, !SkPathPriv::IsSimpleClosedRect(path2, &r, &d, &s));
+ // ... now we do and test again.
+ path2.lineTo(firstPt);
+ check_simple_closed_rect(reporter, path2, testRect, dir, start);
+ // A redundant close shouldn't cause a failure.
+ path2.close();
+ check_simple_closed_rect(reporter, path2, testRect, dir, start);
+ // Degenerate point and line rects are not allowed
+ path2.reset();
+ path2.addRect(emptyRect, dir, start);
+ REPORTER_ASSERT(reporter, !SkPathPriv::IsSimpleClosedRect(path2, &r, &d, &s));
+ SkRect degenRect = testRect;
+ degenRect.fLeft = degenRect.fRight;
+ path2.reset();
+ path2.addRect(degenRect, dir, start);
+ REPORTER_ASSERT(reporter, !SkPathPriv::IsSimpleClosedRect(path2, &r, &d, &s));
+ degenRect = testRect;
+ degenRect.fTop = degenRect.fBottom;
+ path2.reset();
+ path2.addRect(degenRect, dir, start);
+ REPORTER_ASSERT(reporter, !SkPathPriv::IsSimpleClosedRect(path2, &r, &d, &s));
+ // An inverted rect makes a rect path, but changes the winding dir and start point.
+ SkPath::Direction swapDir = (dir == SkPath::kCW_Direction)
+ ? SkPath::kCCW_Direction
+ : SkPath::kCW_Direction;
+ static constexpr unsigned kXSwapStarts[] = { 1, 0, 3, 2 };
+ static constexpr unsigned kYSwapStarts[] = { 3, 2, 1, 0 };
+ SkRect swapRect = testRect;
+ SkTSwap(swapRect.fLeft, swapRect.fRight);
+ path2.reset();
+ path2.addRect(swapRect, dir, start);
+ check_simple_closed_rect(reporter, path2, testRect, swapDir, kXSwapStarts[start]);
+ swapRect = testRect;
+ SkTSwap(swapRect.fTop, swapRect.fBottom);
+ path2.reset();
+ path2.addRect(swapRect, dir, start);
+ check_simple_closed_rect(reporter, path2, testRect, swapDir, kYSwapStarts[start]);
+ }
+ }
+}
+
static void test_isNestedFillRects(skiatest::Reporter* reporter) {
// passing tests (all moveTo / lineTo...
SkPoint r1[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}}; // CW
@@ -4150,6 +4247,7 @@
test_operatorEqual(reporter);
test_isLine(reporter);
test_isRect(reporter);
+ test_is_simple_closed_rect(reporter);
test_isNestedFillRects(reporter);
test_zero_length_paths(reporter);
test_direction(reporter);