Flutter Engine
The Flutter Engine
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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)
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
56 return dy > 0 ? Contour::Direction::kCCW : dy < 0 ? Contour::Direction::kCW :
58}
59
60static int contains_edge(SkPoint pts[4], SkPath::Verb verb, SkScalar weight, const SkPoint& edge) {
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) {
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 {
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
178 : fPath(path) {
179 }
180
181 void contourBounds(vector<Contour>* containers) {
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 }
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) {
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);
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
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) {
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
Definition: FontMgrTest.cpp:50
#define SkASSERT(cond)
Definition: SkAssert.h:116
#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 between(double a, double b, double c)
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
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 & 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:1901
SkScalar conicWeight() const
Definition: SkPath.h:1535
Definition: SkPath.h:59
@ kMove_Verb
Definition: SkPath.h:1466
@ kConic_Verb
Definition: SkPath.h:1469
@ kDone_Verb
Definition: SkPath.h:1472
@ kCubic_Verb
Definition: SkPath.h:1470
@ kQuad_Verb
Definition: SkPath.h:1468
@ kLine_Verb
Definition: SkPath.h:1467
float SkScalar
Definition: extension.cpp:12
GAsyncResult * result
Optional< SkRect > bounds
Definition: SkRecords.h:189
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition: switches.h:57
AI float conic(float tolerance, const SkPoint pts[], float w, const VectorXform &vectorXform=VectorXform())
Definition: WangsFormula.h:287
AI float cubic(float precision, const SkPoint pts[], const VectorXform &vectorXform=VectorXform())
Definition: WangsFormula.h:195
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))
Definition: SkPathOpsQuad.h:65
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
Definition: SkPoint_impl.h:164
float fY
y-axis value
Definition: SkPoint_impl.h:165
void setBounds(const SkPoint pts[], int count)
Definition: SkRect.h:881