| /* |
| * 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, SkPathDirection dir) { |
| SkPath path; |
| bool reverse = SkPathDirection::kCCW == 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(SkPathFillType::kEvenOdd); |
| REPORTER_ASSERT(reporter, AsWinding(test, &result)); |
| REPORTER_ASSERT(reporter, result.isEmpty()); |
| REPORTER_ASSERT(reporter, result.getFillType() == SkPathFillType::kWinding); |
| // if test is convex |
| test.addCircle(5, 5, 10); |
| REPORTER_ASSERT(reporter, AsWinding(test, &result)); |
| REPORTER_ASSERT(reporter, result.isConvex()); |
| test.setFillType(SkPathFillType::kWinding); |
| REPORTER_ASSERT(reporter, test == result); |
| // if test has infinity |
| test.reset(); |
| test.addRect({1, 2, 3, SK_ScalarInfinity}); |
| test.setFillType(SkPathFillType::kEvenOdd); |
| 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(SkPathFillType::kEvenOdd); |
| REPORTER_ASSERT(reporter, AsWinding(test, &result)); |
| REPORTER_ASSERT(reporter, !result.isConvex()); |
| test.setFillType(SkPathFillType::kWinding); |
| REPORTER_ASSERT(reporter, test == result); |
| // test two contours that do not overlap or share bounds |
| test.addRect({5, 2, 6, 3}); |
| test.setFillType(SkPathFillType::kEvenOdd); |
| REPORTER_ASSERT(reporter, AsWinding(test, &result)); |
| REPORTER_ASSERT(reporter, !result.isConvex()); |
| test.setFillType(SkPathFillType::kWinding); |
| 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(SkPathFillType::kEvenOdd); |
| REPORTER_ASSERT(reporter, AsWinding(test, &result)); |
| REPORTER_ASSERT(reporter, !result.isConvex()); |
| test.setFillType(SkPathFillType::kWinding); |
| 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(SkPathFillType::kEvenOdd); |
| REPORTER_ASSERT(reporter, AsWinding(test, &result)); |
| REPORTER_ASSERT(reporter, !result.isConvex()); |
| test.setFillType(SkPathFillType::kWinding); |
| REPORTER_ASSERT(reporter, test == result); |
| // test that result may be input |
| SkPath copy = test; |
| test.setFillType(SkPathFillType::kEvenOdd); |
| 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<SkPoint> revBccw = {{1, 2}, {2, 2}, {2, 1}, {1, 1}}; |
| const std::initializer_list<SkPoint> revBcw = {{2, 1}, {2, 2}, {1, 2}, {1, 1}}; |
| for (bool aFirst : {false, true}) { |
| for (auto dirA : {SkPathDirection::kCW, SkPathDirection::kCCW}) { |
| for (auto dirB : {SkPathDirection::kCW, SkPathDirection::kCCW}) { |
| test.reset(); |
| test.setFillType(SkPathFillType::kEvenOdd); |
| 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() == SkPathFillType::kWinding); |
| test.reset(); |
| if (aFirst) { |
| test.addRect(rectA, dirA); |
| } |
| if (dirA != dirB) { |
| test.addRect(rectB, dirB); |
| } else { |
| test.addPoly(SkPathDirection::kCW == 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() == SkPathFillType::kWinding); |
| 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 : {SkPathDirection::kCW, SkPathDirection::kCCW}) { |
| for (auto dirB : {SkPathDirection::kCW, SkPathDirection::kCCW}) { |
| 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(SkPathFillType::kEvenOdd); |
| REPORTER_ASSERT(reporter, AsWinding(test, &result)); |
| REPORTER_ASSERT(reporter, result.getFillType() == SkPathFillType::kWinding); |
| 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); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |