/* * Copyright 2018 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "tests/PathOpsExtendedTest.h" #include "tests/PathOpsThreadedCommon.h" #include "tests/Test.h" static SkPath build_squircle(SkPath::Verb verb, const SkRect& rect, SkPath::Direction dir) { SkPath path; bool reverse = SkPath::kCCW_Direction == dir; switch (verb) { case SkPath::kLine_Verb: path.addRect(rect, dir); reverse = false; break; case SkPath::kQuad_Verb: path.moveTo(rect.centerX(), rect.fTop); path.quadTo(rect.fRight, rect.fTop, rect.fRight, rect.centerY()); path.quadTo(rect.fRight, rect.fBottom, rect.centerX(), rect.fBottom); path.quadTo(rect.fLeft, rect.fBottom, rect.fLeft, rect.centerY()); path.quadTo(rect.fLeft, rect.fTop, rect.centerX(), rect.fTop); break; case SkPath::kConic_Verb: path.addCircle(rect.centerX(), rect.centerY(), rect.width() / 2, dir); reverse = false; break; case SkPath::kCubic_Verb: { SkScalar aX14 = rect.fLeft + rect.width() * 1 / 4; SkScalar aX34 = rect.fLeft + rect.width() * 3 / 4; SkScalar aY14 = rect.fTop + rect.height() * 1 / 4; SkScalar aY34 = rect.fTop + rect.height() * 3 / 4; path.moveTo(rect.centerX(), rect.fTop); path.cubicTo(aX34, rect.fTop, rect.fRight, aY14, rect.fRight, rect.centerY()); path.cubicTo(rect.fRight, aY34, aX34, rect.fBottom, rect.centerX(), rect.fBottom); path.cubicTo(aX14, rect.fBottom, rect.fLeft, aY34, rect.fLeft, rect.centerY()); path.cubicTo(rect.fLeft, aY14, aX14, rect.fTop, rect.centerX(), rect.fTop); } break; default: SkASSERT(0); } if (reverse) { SkPath temp; temp.reverseAddPath(path); path.swap(temp); } return path; } DEF_TEST(PathOpsAsWinding, reporter) { SkPath test, result; test.addRect({1, 2, 3, 4}); // if test is winding REPORTER_ASSERT(reporter, AsWinding(test, &result)); REPORTER_ASSERT(reporter, test == result); // if test is empty test.reset(); test.setFillType(SkPath::kEvenOdd_FillType); REPORTER_ASSERT(reporter, AsWinding(test, &result)); REPORTER_ASSERT(reporter, result.isEmpty()); REPORTER_ASSERT(reporter, result.getFillType() == SkPath::kWinding_FillType); // if test is convex test.addCircle(5, 5, 10); REPORTER_ASSERT(reporter, AsWinding(test, &result)); REPORTER_ASSERT(reporter, result.isConvex()); test.setFillType(SkPath::kWinding_FillType); REPORTER_ASSERT(reporter, test == result); // if test has infinity test.reset(); test.addRect({1, 2, 3, SK_ScalarInfinity}); test.setFillType(SkPath::kEvenOdd_FillType); REPORTER_ASSERT(reporter, !AsWinding(test, &result)); // if test has only one contour test.reset(); SkPoint ell[] = {{0, 0}, {4, 0}, {4, 1}, {1, 1}, {1, 4}, {0, 4}}; test.addPoly(ell, SK_ARRAY_COUNT(ell), true); test.setFillType(SkPath::kEvenOdd_FillType); REPORTER_ASSERT(reporter, AsWinding(test, &result)); REPORTER_ASSERT(reporter, !result.isConvex()); test.setFillType(SkPath::kWinding_FillType); REPORTER_ASSERT(reporter, test == result); // test two contours that do not overlap or share bounds test.addRect({5, 2, 6, 3}); test.setFillType(SkPath::kEvenOdd_FillType); REPORTER_ASSERT(reporter, AsWinding(test, &result)); REPORTER_ASSERT(reporter, !result.isConvex()); test.setFillType(SkPath::kWinding_FillType); REPORTER_ASSERT(reporter, test == result); // test two contours that do not overlap but share bounds test.reset(); test.addPoly(ell, SK_ARRAY_COUNT(ell), true); test.addRect({2, 2, 3, 3}); test.setFillType(SkPath::kEvenOdd_FillType); REPORTER_ASSERT(reporter, AsWinding(test, &result)); REPORTER_ASSERT(reporter, !result.isConvex()); test.setFillType(SkPath::kWinding_FillType); REPORTER_ASSERT(reporter, test == result); // test two contours that partially overlap test.reset(); test.addRect({0, 0, 3, 3}); test.addRect({1, 1, 4, 4}); test.setFillType(SkPath::kEvenOdd_FillType); REPORTER_ASSERT(reporter, AsWinding(test, &result)); REPORTER_ASSERT(reporter, !result.isConvex()); test.setFillType(SkPath::kWinding_FillType); REPORTER_ASSERT(reporter, test == result); // test that result may be input SkPath copy = test; test.setFillType(SkPath::kEvenOdd_FillType); REPORTER_ASSERT(reporter, AsWinding(test, &test)); REPORTER_ASSERT(reporter, !test.isConvex()); REPORTER_ASSERT(reporter, test == copy); // test a in b, b in a, cw/ccw constexpr SkRect rectA = {0, 0, 3, 3}; constexpr SkRect rectB = {1, 1, 2, 2}; const std::initializer_list revBccw = {{1, 2}, {2, 2}, {2, 1}, {1, 1}}; const std::initializer_list revBcw = {{2, 1}, {2, 2}, {1, 2}, {1, 1}}; for (bool aFirst : {false, true}) { for (auto dirA : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { for (auto dirB : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { test.reset(); test.setFillType(SkPath::kEvenOdd_FillType); if (aFirst) { test.addRect(rectA, dirA); test.addRect(rectB, dirB); } else { test.addRect(rectB, dirB); test.addRect(rectA, dirA); } SkPath original = test; REPORTER_ASSERT(reporter, AsWinding(test, &result)); REPORTER_ASSERT(reporter, result.getFillType() == SkPath::kWinding_FillType); test.reset(); if (aFirst) { test.addRect(rectA, dirA); } if (dirA != dirB) { test.addRect(rectB, dirB); } else { test.addPoly(SkPath::kCW_Direction == dirA ? revBccw : revBcw, true); } if (!aFirst) { test.addRect(rectA, dirA); } REPORTER_ASSERT(reporter, test == result); // test that result may be input REPORTER_ASSERT(reporter, AsWinding(original, &original)); REPORTER_ASSERT(reporter, original.getFillType() == SkPath::kWinding_FillType); REPORTER_ASSERT(reporter, original == result); } } } // Test curve types with donuts. Create a donut with outer and hole in all directions. // After converting to winding, all donuts should have a hole in the middle. for (bool aFirst : {false, true}) { for (auto dirA : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { for (auto dirB : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { for (auto curveA : { SkPath::kLine_Verb, SkPath::kQuad_Verb, SkPath::kConic_Verb, SkPath::kCubic_Verb } ) { SkPath pathA = build_squircle(curveA, rectA, dirA); for (auto curveB : { SkPath::kLine_Verb, SkPath::kQuad_Verb, SkPath::kConic_Verb, SkPath::kCubic_Verb } ) { test = aFirst ? pathA : SkPath(); test.addPath(build_squircle(curveB, rectB, dirB)); if (!aFirst) { test.addPath(pathA); } test.setFillType(SkPath::kEvenOdd_FillType); REPORTER_ASSERT(reporter, AsWinding(test, &result)); REPORTER_ASSERT(reporter, result.getFillType() == SkPath::kWinding_FillType); for (SkScalar x = rectA.fLeft - 1; x <= rectA.fRight + 1; ++x) { for (SkScalar y = rectA.fTop - 1; y <= rectA.fBottom + 1; ++y) { bool evenOddContains = test.contains(x, y); bool windingContains = result.contains(x, y); REPORTER_ASSERT(reporter, evenOddContains == windingContains); } } } } } } } }