fixup winding contours
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>
This commit is contained in:
parent
773151fec9
commit
7d06e2642b
@ -497,6 +497,7 @@ skia_core_sources = [
|
||||
"$_src/pathops/SkOpEdgeBuilder.cpp",
|
||||
"$_src/pathops/SkOpSegment.cpp",
|
||||
"$_src/pathops/SkOpSpan.cpp",
|
||||
"$_src/pathops/SkPathOpsAsWinding.cpp",
|
||||
"$_src/pathops/SkPathOpsCommon.cpp",
|
||||
"$_src/pathops/SkPathOpsConic.cpp",
|
||||
"$_src/pathops/SkPathOpsCubic.cpp",
|
||||
|
@ -293,6 +293,7 @@ tests_sources = [
|
||||
pathops_tests_sources = [
|
||||
"$_tests/PathOpsAngleIdeas.cpp",
|
||||
"$_tests/PathOpsAngleTest.cpp",
|
||||
"$_tests/PathOpsAsWindingTest.cpp",
|
||||
"$_tests/PathOpsBattles.cpp",
|
||||
"$_tests/PathOpsBoundsTest.cpp",
|
||||
"$_tests/PathOpsBuilderConicTest.cpp",
|
||||
|
@ -67,6 +67,20 @@ bool SK_API Simplify(const SkPath& path, SkPath* result);
|
||||
*/
|
||||
bool SK_API TightBounds(const SkPath& path, SkRect* result);
|
||||
|
||||
/** Set the result with fill type winding to area equivalent to path.
|
||||
Returns true if successful. Does not detect if path contains contours which
|
||||
contain self-crossings or cross other contours; in these cases, may return
|
||||
true even though result does not fill same area as path.
|
||||
|
||||
Returns true if operation was able to produce a result;
|
||||
otherwise, result is unmodified. The result may be the input.
|
||||
|
||||
@param path The path typically with fill type set to even odd.
|
||||
@param result The equivalent path with fill type set to winding.
|
||||
@return True if winding path was set.
|
||||
*/
|
||||
bool SK_API AsWinding(const SkPath& path, SkPath* result);
|
||||
|
||||
/** Perform a series of path operations, optimized for unioning many paths together.
|
||||
*/
|
||||
class SK_API SkOpBuilder {
|
||||
|
421
src/pathops/SkPathOpsAsWinding.cpp
Normal file
421
src/pathops/SkPathOpsAsWinding.cpp
Normal file
@ -0,0 +1,421 @@
|
||||
/*
|
||||
* 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 "SkOpEdgeBuilder.h"
|
||||
#include "SkPathOpsCommon.h"
|
||||
#include "SkRect.h"
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
using std::vector;
|
||||
|
||||
struct Contour {
|
||||
enum class Direction { // SkPath::Direction doesn't have 'none' state
|
||||
kCCW = -1,
|
||||
kNone,
|
||||
kCW,
|
||||
};
|
||||
|
||||
Contour(const SkRect& bounds, int lastStart, int verbStart)
|
||||
: fBounds(bounds)
|
||||
, fVerbStart(lastStart)
|
||||
, fVerbEnd(verbStart) {
|
||||
}
|
||||
|
||||
vector<Contour*> fChildren;
|
||||
const SkRect fBounds;
|
||||
SkPoint fMinXY{SK_ScalarMax, SK_ScalarMax};
|
||||
const int fVerbStart;
|
||||
const int fVerbEnd;
|
||||
Direction fDirection{Direction::kNone};
|
||||
bool fContained{false};
|
||||
bool fReverse{false};
|
||||
};
|
||||
|
||||
static const int kPtCount[] = { 1, 1, 2, 2, 3, 0 };
|
||||
static const int kPtIndex[] = { 0, 1, 1, 1, 1, 0 };
|
||||
|
||||
static Contour::Direction to_direction(SkScalar dy) {
|
||||
return dy > 0 ? Contour::Direction::kCCW : dy < 0 ? Contour::Direction::kCW :
|
||||
Contour::Direction::kNone;
|
||||
}
|
||||
|
||||
static int contains_edge(SkPoint pts[4], SkPath::Verb verb, SkScalar weight, const SkPoint& edge) {
|
||||
SkRect bounds;
|
||||
bounds.set(pts, kPtCount[verb] + 1);
|
||||
if (bounds.fTop > edge.fY) {
|
||||
return 0;
|
||||
}
|
||||
if (bounds.fBottom <= edge.fY) { // check to see if y is at line end to avoid double counting
|
||||
return 0;
|
||||
}
|
||||
if (bounds.fLeft >= edge.fX) {
|
||||
return 0;
|
||||
}
|
||||
int winding = 0;
|
||||
double tVals[3];
|
||||
Contour::Direction directions[3];
|
||||
// must intersect horz ray with curve in case it intersects more than once
|
||||
int count = (*CurveIntercept[verb * 2])(pts, weight, edge.fY, tVals);
|
||||
SkASSERT(between(0, count, 3));
|
||||
// remove results to the right of edge
|
||||
for (int index = 0; index < count; ) {
|
||||
SkScalar intersectX = (*CurvePointAtT[verb])(pts, weight, tVals[index]).fX;
|
||||
if (intersectX < edge.fX) {
|
||||
++index;
|
||||
continue;
|
||||
}
|
||||
if (intersectX > edge.fX) {
|
||||
tVals[index] = tVals[--count];
|
||||
continue;
|
||||
}
|
||||
// if intersect x equals edge x, we need to determine if pts is to the left or right of edge
|
||||
if (pts[0].fX < edge.fX && pts[kPtCount[verb]].fX < edge.fX) {
|
||||
++index;
|
||||
continue;
|
||||
}
|
||||
// TODO : other cases need discriminating. need op angle code to figure it out
|
||||
// example: edge ends 45 degree diagonal going up. If pts is to the left of edge, keep.
|
||||
// if pts is to the right of edge, discard. With code as is, can't distiguish the two cases.
|
||||
if (intersectX < edge.fX) {
|
||||
tVals[index] = tVals[--count];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// use first derivative to determine if intersection is contributing +1 or -1 to winding
|
||||
for (int index = 0; index < count; ++index) {
|
||||
directions[index] = to_direction((*CurveSlopeAtT[verb])(pts, weight, tVals[index]).fY);
|
||||
}
|
||||
for (int index = 0; index < count; ++index) {
|
||||
// skip intersections that end at edge and go up
|
||||
if (zero_or_one(tVals[index]) && Contour::Direction::kCCW != directions[index]) {
|
||||
continue;
|
||||
}
|
||||
winding += (int) directions[index];
|
||||
}
|
||||
return winding; // note winding indicates containership, not contour direction
|
||||
}
|
||||
|
||||
static SkScalar conic_weight(const SkPath::Iter& iter, SkPath::Verb verb) {
|
||||
return SkPath::kConic_Verb == verb ? iter.conicWeight() : 1;
|
||||
}
|
||||
|
||||
static SkPoint left_edge(SkPoint pts[4], SkPath::Verb verb, SkScalar weight,
|
||||
Contour::Direction* direction) {
|
||||
SkASSERT(SkPath::kLine_Verb <= verb && verb <= SkPath::kCubic_Verb);
|
||||
SkPoint result;
|
||||
double dy;
|
||||
double t SK_INIT_TO_AVOID_WARNING;
|
||||
int roots = 0;
|
||||
if (SkPath::kLine_Verb == verb) {
|
||||
result = pts[0].fX < pts[1].fX ? pts[0] : pts[1];
|
||||
dy = pts[1].fY - pts[0].fY;
|
||||
} else if (SkPath::kQuad_Verb == verb) {
|
||||
SkDQuad quad;
|
||||
quad.set(pts);
|
||||
if (!quad.monotonicInX()) {
|
||||
roots = SkDQuad::FindExtrema(&quad[0].fX, &t);
|
||||
}
|
||||
if (roots) {
|
||||
result = quad.ptAtT(t).asSkPoint();
|
||||
} else {
|
||||
result = pts[0].fX < pts[2].fX ? pts[0] : pts[2];
|
||||
t = pts[0].fX < pts[2].fX ? 0 : 1;
|
||||
}
|
||||
dy = quad.dxdyAtT(t).fY;
|
||||
} else if (SkPath::kConic_Verb == verb) {
|
||||
SkDConic conic;
|
||||
conic.set(pts, weight);
|
||||
if (!conic.monotonicInX()) {
|
||||
roots = SkDConic::FindExtrema(&conic[0].fX, weight, &t);
|
||||
}
|
||||
if (roots) {
|
||||
result = conic.ptAtT(t).asSkPoint();
|
||||
} else {
|
||||
result = pts[0].fX < pts[2].fX ? pts[0] : pts[2];
|
||||
t = pts[0].fX < pts[2].fX ? 0 : 1;
|
||||
}
|
||||
dy = conic.dxdyAtT(t).fY;
|
||||
} else {
|
||||
SkASSERT(SkPath::kCubic_Verb == verb);
|
||||
SkDCubic cubic;
|
||||
cubic.set(pts);
|
||||
if (!cubic.monotonicInX()) {
|
||||
double tValues[2];
|
||||
roots = SkDCubic::FindExtrema(&cubic[0].fX, tValues);
|
||||
SkASSERT(roots <= 2);
|
||||
for (int index = 0; index < roots; ++index) {
|
||||
SkPoint temp = cubic.ptAtT(tValues[index]).asSkPoint();
|
||||
if (0 == index || result.fX > temp.fX) {
|
||||
result = temp;
|
||||
t = tValues[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (roots) {
|
||||
result = cubic.ptAtT(t).asSkPoint();
|
||||
} else {
|
||||
result = pts[0].fX < pts[3].fX ? pts[0] : pts[3];
|
||||
t = pts[0].fX < pts[3].fX ? 0 : 1;
|
||||
}
|
||||
dy = cubic.dxdyAtT(t).fY;
|
||||
}
|
||||
*direction = to_direction(dy);
|
||||
return result;
|
||||
}
|
||||
|
||||
class OpAsWinding {
|
||||
public:
|
||||
enum class Edge {
|
||||
kInitial,
|
||||
kCompare,
|
||||
};
|
||||
|
||||
OpAsWinding(const SkPath& path)
|
||||
: fPath(path) {
|
||||
}
|
||||
|
||||
void contourBounds(vector<Contour>* containers) {
|
||||
SkRect bounds;
|
||||
bounds.setEmpty();
|
||||
SkPath::RawIter iter(fPath);
|
||||
SkPoint pts[4];
|
||||
SkPath::Verb verb;
|
||||
int lastStart = 0;
|
||||
int verbStart = 0;
|
||||
do {
|
||||
verb = iter.next(pts);
|
||||
if (SkPath::kMove_Verb == verb) {
|
||||
if (!bounds.isEmpty()) {
|
||||
containers->emplace_back(bounds, lastStart, verbStart);
|
||||
lastStart = verbStart;
|
||||
}
|
||||
bounds.setBounds(&pts[kPtIndex[verb]], kPtCount[verb]);
|
||||
}
|
||||
if (SkPath::kLine_Verb <= verb && verb <= SkPath::kCubic_Verb) {
|
||||
SkRect verbBounds;
|
||||
verbBounds.setBounds(&pts[kPtIndex[verb]], kPtCount[verb]);
|
||||
bounds.joinPossiblyEmptyRect(verbBounds);
|
||||
}
|
||||
++verbStart;
|
||||
} while (SkPath::kDone_Verb != verb);
|
||||
if (!bounds.isEmpty()) {
|
||||
containers->emplace_back(bounds, lastStart, verbStart);
|
||||
}
|
||||
}
|
||||
|
||||
int nextEdge(Contour& contour, Edge edge) {
|
||||
SkPath::Iter iter(fPath, true);
|
||||
SkPoint pts[4];
|
||||
SkPath::Verb verb;
|
||||
int verbCount = -1;
|
||||
int winding = 0;
|
||||
do {
|
||||
verb = iter.next(pts);
|
||||
if (++verbCount < contour.fVerbStart) {
|
||||
continue;
|
||||
}
|
||||
if (verbCount >= contour.fVerbEnd) {
|
||||
continue;
|
||||
}
|
||||
if (SkPath::kLine_Verb > verb || verb > SkPath::kCubic_Verb) {
|
||||
continue;
|
||||
}
|
||||
bool horizontal = true;
|
||||
for (int index = 1; index <= kPtCount[verb]; ++index) {
|
||||
if (pts[0].fY != pts[index].fY) {
|
||||
horizontal = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (horizontal) {
|
||||
continue;
|
||||
}
|
||||
if (edge == Edge::kCompare) {
|
||||
winding += contains_edge(pts, verb, conic_weight(iter, verb), contour.fMinXY);
|
||||
continue;
|
||||
}
|
||||
SkASSERT(edge == Edge::kInitial);
|
||||
Contour::Direction direction;
|
||||
SkPoint minXY = left_edge(pts, verb, conic_weight(iter, verb), &direction);
|
||||
if (minXY.fX > contour.fMinXY.fX) {
|
||||
continue;
|
||||
}
|
||||
if (minXY.fX == contour.fMinXY.fX) {
|
||||
if (minXY.fY != contour.fMinXY.fY) {
|
||||
continue;
|
||||
}
|
||||
if (direction == contour.fDirection) {
|
||||
continue;
|
||||
}
|
||||
// incomplete: must sort edges to find the one most to left
|
||||
SkDebugf("incomplete\n");
|
||||
// TODO: add edges as opangle and sort
|
||||
}
|
||||
contour.fMinXY = minXY;
|
||||
contour.fDirection = direction;
|
||||
} while (SkPath::kDone_Verb != verb);
|
||||
return winding;
|
||||
}
|
||||
|
||||
void containerContains(Contour& contour, Contour& test) {
|
||||
// find outside point on lesser contour
|
||||
// arbitrarily, choose non-horizontal edge where point <= bounds left
|
||||
// note that if leftmost point is control point, may need tight bounds
|
||||
// to find edge with minimum-x
|
||||
if (SK_ScalarMax == test.fMinXY.fX) {
|
||||
this->nextEdge(test, Edge::kInitial);
|
||||
}
|
||||
// find all edges on greater equal or to the left of one on lesser
|
||||
contour.fMinXY = test.fMinXY;
|
||||
int winding = this->nextEdge(contour, Edge::kCompare);
|
||||
// if edge is up, mark contour cw, otherwise, ccw
|
||||
// sum of greater edges direction should be cw, 0, ccw
|
||||
SkASSERT(-1 <= winding && winding <= 1);
|
||||
test.fContained = winding != 0;
|
||||
}
|
||||
|
||||
void inParent(Contour& contour, Contour& parent) {
|
||||
// move contour into sibling list contained by parent
|
||||
for (auto test : parent.fChildren) {
|
||||
if (test->fBounds.contains(contour.fBounds)) {
|
||||
inParent(contour, *test);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// move parent's children into contour's children if contained by contour
|
||||
for (auto iter = parent.fChildren.begin(); iter != parent.fChildren.end(); ) {
|
||||
if (contour.fBounds.contains((*iter)->fBounds)) {
|
||||
contour.fChildren.push_back(*iter);
|
||||
iter = parent.fChildren.erase(iter);
|
||||
continue;
|
||||
}
|
||||
++iter;
|
||||
}
|
||||
parent.fChildren.push_back(&contour);
|
||||
}
|
||||
|
||||
void checkContainerChildren(Contour* parent, Contour* child) {
|
||||
for (auto grandChild : child->fChildren) {
|
||||
checkContainerChildren(child, grandChild);
|
||||
}
|
||||
if (parent) {
|
||||
containerContains(*parent, *child);
|
||||
}
|
||||
}
|
||||
|
||||
bool markReverse(Contour* parent, Contour* child) {
|
||||
bool reversed = false;
|
||||
for (auto grandChild : child->fChildren) {
|
||||
reversed |= markReverse(grandChild->fContained ? child : parent, grandChild);
|
||||
}
|
||||
if (parent && parent->fDirection == child->fDirection) {
|
||||
child->fReverse = true;
|
||||
child->fDirection = (Contour::Direction) -(int) child->fDirection;
|
||||
return true;
|
||||
}
|
||||
return reversed;
|
||||
}
|
||||
|
||||
void reverseMarkedContours(vector<Contour>& contours, SkPath* result) {
|
||||
SkPath::RawIter iter(fPath);
|
||||
int verbCount = 0;
|
||||
for (auto contour : contours) {
|
||||
SkPath reverse;
|
||||
SkPath* temp = contour.fReverse ? &reverse : result;
|
||||
do {
|
||||
SkPoint pts[4];
|
||||
switch (iter.next(pts)) {
|
||||
case SkPath::kMove_Verb:
|
||||
temp->moveTo(pts[0]);
|
||||
break;
|
||||
case SkPath::kLine_Verb:
|
||||
temp->lineTo(pts[1]);
|
||||
break;
|
||||
case SkPath::kQuad_Verb:
|
||||
temp->quadTo(pts[1], pts[2]);
|
||||
break;
|
||||
case SkPath::kConic_Verb:
|
||||
temp->conicTo(pts[1], pts[2], iter.conicWeight());
|
||||
break;
|
||||
case SkPath::kCubic_Verb:
|
||||
temp->cubicTo(pts[1], pts[2], pts[3]);
|
||||
break;
|
||||
case SkPath::kClose_Verb:
|
||||
temp->close();
|
||||
break;
|
||||
case SkPath::kDone_Verb:
|
||||
break;
|
||||
default:
|
||||
SkASSERT(0);
|
||||
}
|
||||
} while (++verbCount < contour.fVerbEnd);
|
||||
if (contour.fReverse) {
|
||||
result->reverseAddPath(reverse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const SkPath& fPath;
|
||||
};
|
||||
|
||||
static bool set_result_path(SkPath* result, const SkPath& path, SkPath::FillType fillType) {
|
||||
*result = path;
|
||||
result->setFillType(fillType);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SK_API AsWinding(const SkPath& path, SkPath* result) {
|
||||
if (!path.isFinite()) {
|
||||
return false;
|
||||
}
|
||||
SkPath::FillType fillType = path.getFillType();
|
||||
if (fillType == SkPath::kWinding_FillType
|
||||
|| fillType == SkPath::kInverseWinding_FillType ) {
|
||||
return set_result_path(result, path, fillType);
|
||||
}
|
||||
fillType = path.isInverseFillType() ? SkPath::kInverseWinding_FillType :
|
||||
SkPath::kWinding_FillType;
|
||||
if (path.isEmpty() || path.isConvex()) {
|
||||
return set_result_path(result, path, fillType);
|
||||
}
|
||||
// count contours
|
||||
vector<Contour> contours; // one per contour
|
||||
OpAsWinding winder(path);
|
||||
winder.contourBounds(&contours);
|
||||
if (contours.size() <= 1) {
|
||||
return set_result_path(result, path, fillType);
|
||||
}
|
||||
// create contour bounding box tree
|
||||
Contour sorted(SkRect(), 0, 0);
|
||||
for (auto& contour : contours) {
|
||||
winder.inParent(contour, sorted);
|
||||
}
|
||||
// if sorted has no grandchildren, no child has to fix its children's winding
|
||||
if (std::all_of(sorted.fChildren.begin(), sorted.fChildren.end(),
|
||||
[](const Contour* contour) -> bool { return !contour->fChildren.size(); } )) {
|
||||
return set_result_path(result, path, fillType);
|
||||
}
|
||||
// starting with outermost and moving inward, see if one path contains another
|
||||
for (auto contour : sorted.fChildren) {
|
||||
winder.nextEdge(*contour, OpAsWinding::Edge::kInitial);
|
||||
winder.checkContainerChildren(nullptr, contour);
|
||||
}
|
||||
// starting with outermost and moving inward, mark paths to reverse
|
||||
bool reversed = false;
|
||||
for (auto contour : sorted.fChildren) {
|
||||
reversed |= winder.markReverse(nullptr, contour);
|
||||
}
|
||||
if (!reversed) {
|
||||
return set_result_path(result, path, fillType);
|
||||
}
|
||||
SkPath temp;
|
||||
temp.setFillType(fillType);
|
||||
winder.reverseMarkedContours(contours, &temp);
|
||||
result->swap(temp);
|
||||
return true;
|
||||
}
|
186
tests/PathOpsAsWindingTest.cpp
Normal file
186
tests/PathOpsAsWindingTest.cpp
Normal file
@ -0,0 +1,186 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user