7d06e2642b
Add AsWinding to convert SkPath with even odd fill to winding fill. This basic implementation works for simple non-intersecting paths. It may fail if contours in paths touch, specifically when the leftmost point in a contour is shared with another contour. The incomplete parts are marked with TODO in the code. If this interface and implementation look promising, I will continue to tackle the more difficult cases. R=reed@google.com,bungeman@google.com Bug: skia:7682 Change-Id: I479fba60072eb1391b451fcb819504245da2e2a9 Reviewed-on: https://skia-review.googlesource.com/147044 Commit-Queue: Cary Clark <caryclark@google.com> Reviewed-by: Mike Reed <reed@google.com>
187 lines
8.3 KiB
C++
187 lines
8.3 KiB
C++
/*
|
|
* 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 "PathOpsExtendedTest.h"
|
|
#include "PathOpsThreadedCommon.h"
|
|
#include "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<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 : {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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|