Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
SkPathOpsAsWinding.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2018 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
11#include "include/core/SkRect.h"
16#include "src/core/SkPathPriv.h"
23
24#include <algorithm>
25#include <vector>
26
27using std::vector;
28
29struct Contour {
30 enum class Direction { // SkPathDirection doesn't have 'none' state
31 kCCW = -1,
32 kNone,
33 kCW,
34 };
35
36 Contour(const SkRect& bounds, int lastStart, int verbStart)
37 : fBounds(bounds)
38 , fVerbStart(lastStart)
39 , fVerbEnd(verbStart) {
40 }
41
42 vector<Contour*> fChildren;
45 const int fVerbStart;
46 const int fVerbEnd;
48 bool fContained{false};
49 bool fReverse{false};
50};
51
52static const int kPtCount[] = { 1, 1, 2, 2, 3, 0 };
53static const int kPtIndex[] = { 0, 1, 1, 1, 1, 0 };
54
59
60static int contains_edge(SkPoint pts[4], SkPath::Verb verb, SkScalar weight, const SkPoint& edge) {
61 SkRect bounds;
62 bounds.setBounds(pts, kPtCount[verb] + 1);
63 if (bounds.fTop > edge.fY) {
64 return 0;
65 }
66 if (bounds.fBottom <= edge.fY) { // check to see if y is at line end to avoid double counting
67 return 0;
68 }
69 if (bounds.fLeft >= edge.fX) {
70 return 0;
71 }
72 int winding = 0;
73 double tVals[3];
74 Contour::Direction directions[3];
75 // must intersect horz ray with curve in case it intersects more than once
76 int count = (*CurveIntercept[verb * 2])(pts, weight, edge.fY, tVals);
77 SkASSERT(between(0, count, 3));
78 // remove results to the right of edge
79 for (int index = 0; index < count; ) {
80 SkScalar intersectX = (*CurvePointAtT[verb])(pts, weight, tVals[index]).fX;
81 if (intersectX < edge.fX) {
82 ++index;
83 continue;
84 }
85 if (intersectX > edge.fX) {
86 tVals[index] = tVals[--count];
87 continue;
88 }
89 // if intersect x equals edge x, we need to determine if pts is to the left or right of edge
90 if (pts[0].fX < edge.fX && pts[kPtCount[verb]].fX < edge.fX) {
91 ++index;
92 continue;
93 }
94 // TODO : other cases need discriminating. need op angle code to figure it out
95 // example: edge ends 45 degree diagonal going up. If pts is to the left of edge, keep.
96 // if pts is to the right of edge, discard. With code as is, can't distiguish the two cases.
97 tVals[index] = tVals[--count];
98 }
99 // use first derivative to determine if intersection is contributing +1 or -1 to winding
100 for (int index = 0; index < count; ++index) {
101 directions[index] = to_direction((*CurveSlopeAtT[verb])(pts, weight, tVals[index]).fY);
102 }
103 for (int index = 0; index < count; ++index) {
104 // skip intersections that end at edge and go up
105 if (zero_or_one(tVals[index]) && Contour::Direction::kCCW != directions[index]) {
106 continue;
107 }
108 winding += (int) directions[index];
109 }
110 return winding; // note winding indicates containership, not contour direction
111}
112
114 return SkPath::kConic_Verb == verb ? iter.conicWeight() : 1;
115}
116
117static SkPoint left_edge(SkPoint pts[4], SkPath::Verb verb, SkScalar weight) {
121 int roots = 0;
122 if (SkPath::kLine_Verb == verb) {
123 result = pts[0].fX < pts[1].fX ? pts[0] : pts[1];
124 } else if (SkPath::kQuad_Verb == verb) {
125 SkDQuad quad;
126 quad.set(pts);
127 if (!quad.monotonicInX()) {
128 roots = SkDQuad::FindExtrema(&quad[0].fX, &t);
129 }
130 if (roots) {
131 result = quad.ptAtT(t).asSkPoint();
132 } else {
133 result = pts[0].fX < pts[2].fX ? pts[0] : pts[2];
134 }
135 } else if (SkPath::kConic_Verb == verb) {
136 SkDConic conic;
137 conic.set(pts, weight);
138 if (!conic.monotonicInX()) {
139 roots = SkDConic::FindExtrema(&conic[0].fX, weight, &t);
140 }
141 if (roots) {
142 result = conic.ptAtT(t).asSkPoint();
143 } else {
144 result = pts[0].fX < pts[2].fX ? pts[0] : pts[2];
145 }
146 } else {
148 SkDCubic cubic;
149 cubic.set(pts);
150 if (!cubic.monotonicInX()) {
151 double tValues[2];
152 roots = SkDCubic::FindExtrema(&cubic[0].fX, tValues);
153 SkASSERT(roots <= 2);
154 for (int index = 0; index < roots; ++index) {
155 SkPoint temp = cubic.ptAtT(tValues[index]).asSkPoint();
156 if (0 == index || result.fX > temp.fX) {
157 result = temp;
158 }
159 }
160 }
161 if (roots) {
162 result = cubic.ptAtT(t).asSkPoint();
163 } else {
164 result = pts[0].fX < pts[3].fX ? pts[0] : pts[3];
165 }
166 }
167 return result;
168}
169
171public:
172 enum class Edge {
173 kInitial,
174 kCompare,
175 };
176
177 OpAsWinding(const SkPath& path)
178 : fPath(path) {
179 }
180
181 void contourBounds(vector<Contour>* containers) {
182 SkRect bounds;
183 bounds.setEmpty();
184 int lastStart = 0;
185 int verbStart = 0;
186 for (auto [verb, pts, w] : SkPathPriv::Iterate(fPath)) {
187 if (SkPathVerb::kMove == verb) {
188 if (!bounds.isEmpty()) {
189 containers->emplace_back(bounds, lastStart, verbStart);
190 lastStart = verbStart;
191 }
192 bounds.setBounds(&pts[kPtIndex[SkPath::kMove_Verb]], kPtCount[SkPath::kMove_Verb]);
193 }
194 if (SkPathVerb::kLine <= verb && verb <= SkPathVerb::kCubic) {
195 SkRect verbBounds;
196 verbBounds.setBounds(&pts[kPtIndex[(int)verb]], kPtCount[(int)verb]);
197 bounds.joinPossiblyEmptyRect(verbBounds);
198 }
199 ++verbStart;
200 }
201 if (!bounds.isEmpty()) {
202 containers->emplace_back(bounds, lastStart, ++verbStart);
203 }
204 }
205
207 SkPath::Iter iter(fPath, true);
208 int verbCount = -1;
209 SkPath::Verb verb;
210 SkPoint pts[4];
211
212 SkScalar total_signed_area = 0;
213 do {
214 verb = iter.next(pts);
215 if (++verbCount < contour.fVerbStart) {
216 continue;
217 }
218 if (verbCount >= contour.fVerbEnd) {
219 continue;
220 }
221 if (SkPath::kLine_Verb > verb || verb > SkPath::kCubic_Verb) {
222 continue;
223 }
224
225 switch (verb)
226 {
228 total_signed_area += (pts[0].fY - pts[1].fY) * (pts[0].fX + pts[1].fX);
229 break;
232 total_signed_area += (pts[0].fY - pts[2].fY) * (pts[0].fX + pts[2].fX);
233 break;
235 total_signed_area += (pts[0].fY - pts[3].fY) * (pts[0].fX + pts[3].fX);
236 break;
237 default:
238 break;
239 }
240 } while (SkPath::kDone_Verb != verb);
241
242 return total_signed_area < 0 ? Contour::Direction::kCCW: Contour::Direction::kCW;
243 }
244
246 SkPath::Iter iter(fPath, true);
247 SkPoint pts[4];
248 SkPath::Verb verb;
249 int verbCount = -1;
250 int winding = 0;
251 do {
252 verb = iter.next(pts);
253 if (++verbCount < contour.fVerbStart) {
254 continue;
255 }
256 if (verbCount >= contour.fVerbEnd) {
257 continue;
258 }
259 if (SkPath::kLine_Verb > verb || verb > SkPath::kCubic_Verb) {
260 continue;
261 }
262 bool horizontal = true;
263 for (int index = 1; index <= kPtCount[verb]; ++index) {
264 if (pts[0].fY != pts[index].fY) {
265 horizontal = false;
266 break;
267 }
268 }
269 if (horizontal) {
270 continue;
271 }
272 if (edge == Edge::kCompare) {
273 winding += contains_edge(pts, verb, conic_weight(iter, verb), contour.fMinXY);
274 continue;
275 }
276 SkASSERT(edge == Edge::kInitial);
277 SkPoint minXY = left_edge(pts, verb, conic_weight(iter, verb));
278 if (minXY.fX > contour.fMinXY.fX) {
279 continue;
280 }
281 if (minXY.fX == contour.fMinXY.fX) {
282 if (minXY.fY != contour.fMinXY.fY) {
283 continue;
284 }
285 }
286 contour.fMinXY = minXY;
287 } while (SkPath::kDone_Verb != verb);
288 return winding;
289 }
290
292 // find outside point on lesser contour
293 // arbitrarily, choose non-horizontal edge where point <= bounds left
294 // note that if leftmost point is control point, may need tight bounds
295 // to find edge with minimum-x
296 if (SK_ScalarMax == test.fMinXY.fX) {
297 this->nextEdge(test, Edge::kInitial);
298 }
299 // find all edges on greater equal or to the left of one on lesser
300 contour.fMinXY = test.fMinXY;
301 int winding = this->nextEdge(contour, Edge::kCompare);
302 // if edge is up, mark contour cw, otherwise, ccw
303 // sum of greater edges direction should be cw, 0, ccw
304 test.fContained = winding != 0;
305 return -1 <= winding && winding <= 1;
306 }
307
308 void inParent(Contour& contour, Contour& parent) {
309 // move contour into sibling list contained by parent
310 for (auto test : parent.fChildren) {
311 if (test->fBounds.contains(contour.fBounds)) {
313 return;
314 }
315 }
316 // move parent's children into contour's children if contained by contour
317 for (auto iter = parent.fChildren.begin(); iter != parent.fChildren.end(); ) {
318 if (contour.fBounds.contains((*iter)->fBounds)) {
319 contour.fChildren.push_back(*iter);
320 iter = parent.fChildren.erase(iter);
321 continue;
322 }
323 ++iter;
324 }
325 parent.fChildren.push_back(&contour);
326 }
327
328 bool checkContainerChildren(Contour* parent, Contour* child) {
329 for (auto grandChild : child->fChildren) {
330 if (!checkContainerChildren(child, grandChild)) {
331 return false;
332 }
333 }
334 if (parent) {
335 if (!containerContains(*parent, *child)) {
336 return false;
337 }
338 }
339 return true;
340 }
341
342 bool markReverse(Contour* parent, Contour* child) {
343 bool reversed = false;
344 for (auto grandChild : child->fChildren) {
345 reversed |= markReverse(grandChild->fContained ? child : parent, grandChild);
346 }
347
348 child->fDirection = getDirection(*child);
349 if (parent && parent->fDirection == child->fDirection) {
350 child->fReverse = true;
351 child->fDirection = (Contour::Direction) -(int) child->fDirection;
352 return true;
353 }
354 return reversed;
355 }
356
357 SkPath reverseMarkedContours(vector<Contour>& contours, SkPathFillType fillType) {
358 SkPathPriv::Iterate iterate(fPath);
359 auto iter = iterate.begin();
360 int verbCount = 0;
361
363 result.setFillType(fillType);
364 for (const Contour& contour : contours) {
365 SkPathBuilder reverse;
366 SkPathBuilder* temp = contour.fReverse ? &reverse : &result;
367 for (; iter != iterate.end() && verbCount < contour.fVerbEnd; ++iter, ++verbCount) {
368 auto [verb, pts, w] = *iter;
369 switch (verb) {
371 temp->moveTo(pts[0]);
372 break;
374 temp->lineTo(pts[1]);
375 break;
377 temp->quadTo(pts[1], pts[2]);
378 break;
380 temp->conicTo(pts[1], pts[2], *w);
381 break;
383 temp->cubicTo(pts[1], pts[2], pts[3]);
384 break;
386 temp->close();
387 break;
388 }
389 }
390 if (contour.fReverse) {
391 SkASSERT(temp == &reverse);
392 SkPathPriv::ReverseAddPath(&result, reverse.detach());
393 }
394 }
395 return result.detach();
396 }
397
398private:
399 const SkPath& fPath;
400};
401
402static bool set_result_path(SkPath* result, const SkPath& path, SkPathFillType fillType) {
403 *result = path;
404 result->setFillType(fillType);
405 return true;
406}
407
408bool AsWinding(const SkPath& path, SkPath* result) {
409 if (!path.isFinite()) {
410 return false;
411 }
412 SkPathFillType fillType = path.getFillType();
413 if (fillType == SkPathFillType::kWinding
414 || fillType == SkPathFillType::kInverseWinding ) {
415 return set_result_path(result, path, fillType);
416 }
417 fillType = path.isInverseFillType() ? SkPathFillType::kInverseWinding :
419 if (path.isEmpty() || path.isConvex()) {
420 return set_result_path(result, path, fillType);
421 }
422 // count contours
423 vector<Contour> contours; // one per contour
424 OpAsWinding winder(path);
425 winder.contourBounds(&contours);
426 if (contours.size() <= 1) {
427 return set_result_path(result, path, fillType);
428 }
429 // create contour bounding box tree
430 Contour sorted(SkRect(), 0, 0);
431 for (auto& contour : contours) {
432 winder.inParent(contour, sorted);
433 }
434 // if sorted has no grandchildren, no child has to fix its children's winding
435 if (std::all_of(sorted.fChildren.begin(), sorted.fChildren.end(),
436 [](const Contour* contour) -> bool { return contour->fChildren.empty(); } )) {
437 return set_result_path(result, path, fillType);
438 }
439 // starting with outermost and moving inward, see if one path contains another
440 for (auto contour : sorted.fChildren) {
441 winder.nextEdge(*contour, OpAsWinding::Edge::kInitial);
442 contour->fDirection = winder.getDirection(*contour);
443 if (!winder.checkContainerChildren(nullptr, contour)) {
444 return false;
445 }
446 }
447 // starting with outermost and moving inward, mark paths to reverse
448 bool reversed = false;
449 for (auto contour : sorted.fChildren) {
450 reversed |= winder.markReverse(nullptr, contour);
451 }
452 if (!reversed) {
453 return set_result_path(result, path, fillType);
454 }
455 *result = winder.reverseMarkedContours(contours, fillType);
456 return true;
457}
int count
#define SkASSERT(cond)
Definition SkAssert.h:116
static bool between(SkScalar a, SkScalar b, SkScalar c)
#define SK_INIT_TO_AVOID_WARNING
Definition SkMacros.h:58
static bool set_result_path(SkPath *result, const SkPath &path, SkPathFillType fillType)
static Contour::Direction to_direction(SkScalar dy)
static SkPoint left_edge(SkPoint pts[4], SkPath::Verb verb, SkScalar weight)
static const int kPtIndex[]
static SkScalar conic_weight(const SkPath::Iter &iter, SkPath::Verb verb)
static int contains_edge(SkPoint pts[4], SkPath::Verb verb, SkScalar weight, const SkPoint &edge)
static const int kPtCount[]
bool AsWinding(const SkPath &path, SkPath *result)
static int(*const CurveIntercept[])(const SkPoint[], SkScalar, SkScalar, double *)
static SkPoint(*const CurvePointAtT[])(const SkPoint[], SkScalar, double)
static SkVector(*const CurveSlopeAtT[])(const SkPoint[], SkScalar, double)
bool zero_or_one(double x)
SkPathFillType
Definition SkPathTypes.h:11
@ kClose
SkPath::RawIter returns 0 points.
@ kCubic
SkPath::RawIter returns 4 points.
@ kConic
SkPath::RawIter returns 3 points + 1 weight.
@ kQuad
SkPath::RawIter returns 3 points.
@ kMove
SkPath::RawIter returns 1 point.
@ kLine
SkPath::RawIter returns 2 points.
#define SK_ScalarMax
Definition SkScalar.h:24
Type::kYUV Type::kRGBA() int(0.7 *637)
bool containerContains(Contour &contour, Contour &test)
int nextEdge(Contour &contour, Edge edge)
SkPath reverseMarkedContours(vector< Contour > &contours, SkPathFillType fillType)
void inParent(Contour &contour, Contour &parent)
Contour::Direction getDirection(Contour &contour)
bool markReverse(Contour *parent, Contour *child)
void contourBounds(vector< Contour > *containers)
bool checkContainerChildren(Contour *parent, Contour *child)
OpAsWinding(const SkPath &path)
SkPathBuilder & conicTo(SkPoint pt1, SkPoint pt2, SkScalar w)
SkPathBuilder & close()
SkPathBuilder & lineTo(SkPoint pt)
SkPathBuilder & setFillType(SkPathFillType ft)
SkPathBuilder & cubicTo(SkPoint pt1, SkPoint pt2, SkPoint pt3)
SkPathBuilder & moveTo(SkPoint pt)
SkPathBuilder & quadTo(SkPoint pt1, SkPoint pt2)
static void ReverseAddPath(SkPathBuilder *builder, const SkPath &reverseMe)
Definition SkPathPriv.h:421
Verb next(SkPoint pts[4])
Definition SkPath.cpp:1837
SkScalar conicWeight() const
Definition SkPath.h:1527
@ kMove_Verb
Definition SkPath.h:1458
@ kConic_Verb
Definition SkPath.h:1461
@ kDone_Verb
Definition SkPath.h:1464
@ kCubic_Verb
Definition SkPath.h:1462
@ kQuad_Verb
Definition SkPath.h:1460
@ kLine_Verb
Definition SkPath.h:1459
float SkScalar
Definition extension.cpp:12
GAsyncResult * result
SkScalar w
Direction fDirection
const int fVerbEnd
Contour(const SkRect &bounds, int lastStart, int verbStart)
vector< Contour * > fChildren
const SkRect fBounds
const int fVerbStart
static int FindExtrema(const double src[], SkScalar weight, double tValue[1])
static int FindExtrema(const double src[], double tValue[2])
SkPoint asSkPoint() const
const SkDQuad & set(const SkPoint pts[kPointCount] SkDEBUGPARAMS(SkOpGlobalState *state=nullptr))
bool monotonicInX() const
static int FindExtrema(const double src[], double tValue[1])
SkDPoint ptAtT(double t) const
SkPath::RangeIter end()
Definition SkPathPriv.h:187
SkPath::RangeIter begin()
Definition SkPathPriv.h:186
float fX
x-axis value
float fY
y-axis value
void setBounds(const SkPoint pts[], int count)
Definition SkRect.h:881