• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  */
7 #include "include/core/SkRect.h"
8 #include "src/core/SkPathPriv.h"
9 #include "src/pathops/SkOpEdgeBuilder.h"
10 #include "src/pathops/SkPathOpsCommon.h"
11 #include <algorithm>
12 #include <vector>
13 
14 using std::vector;
15 
16 struct Contour {
17     enum class Direction {  // SkPathDirection doesn't have 'none' state
18         kCCW = -1,
19         kNone,
20         kCW,
21     };
22 
ContourContour23     Contour(const SkRect& bounds, int lastStart, int verbStart)
24         : fBounds(bounds)
25         , fVerbStart(lastStart)
26         , fVerbEnd(verbStart) {
27     }
28 
29     vector<Contour*> fChildren;
30     const SkRect fBounds;
31     SkPoint fMinXY{SK_ScalarMax, SK_ScalarMax};
32     const int fVerbStart;
33     const int fVerbEnd;
34     Direction fDirection{Direction::kNone};
35     bool fContained{false};
36     bool fReverse{false};
37 };
38 
39 static const int kPtCount[] = { 1, 1, 2, 2, 3, 0 };
40 static const int kPtIndex[] = { 0, 1, 1, 1, 1, 0 };
41 
to_direction(SkScalar dy)42 static Contour::Direction to_direction(SkScalar dy) {
43     return dy > 0 ? Contour::Direction::kCCW : dy < 0 ? Contour::Direction::kCW :
44             Contour::Direction::kNone;
45 }
46 
contains_edge(SkPoint pts[4],SkPath::Verb verb,SkScalar weight,const SkPoint & edge)47 static int contains_edge(SkPoint pts[4], SkPath::Verb verb, SkScalar weight, const SkPoint& edge) {
48     SkRect bounds;
49     bounds.setBounds(pts, kPtCount[verb] + 1);
50     if (bounds.fTop > edge.fY) {
51         return 0;
52     }
53     if (bounds.fBottom <= edge.fY) {  // check to see if y is at line end to avoid double counting
54         return 0;
55     }
56     if (bounds.fLeft >= edge.fX) {
57         return 0;
58     }
59     int winding = 0;
60     double tVals[3];
61     Contour::Direction directions[3];
62     // must intersect horz ray with curve in case it intersects more than once
63     int count = (*CurveIntercept[verb * 2])(pts, weight, edge.fY, tVals);
64     SkASSERT(between(0, count, 3));
65     // remove results to the right of edge
66     for (int index = 0; index < count; ) {
67         SkScalar intersectX = (*CurvePointAtT[verb])(pts, weight, tVals[index]).fX;
68         if (intersectX < edge.fX) {
69             ++index;
70             continue;
71         }
72         if (intersectX > edge.fX) {
73             tVals[index] = tVals[--count];
74             continue;
75         }
76         // if intersect x equals edge x, we need to determine if pts is to the left or right of edge
77         if (pts[0].fX < edge.fX && pts[kPtCount[verb]].fX < edge.fX) {
78             ++index;
79             continue;
80         }
81         // TODO : other cases need discriminating. need op angle code to figure it out
82         // example: edge ends 45 degree diagonal going up. If pts is to the left of edge, keep.
83         // if pts is to the right of edge, discard. With code as is, can't distiguish the two cases.
84         tVals[index] = tVals[--count];
85     }
86     // use first derivative to determine if intersection is contributing +1 or -1 to winding
87     for (int index = 0; index < count; ++index) {
88         directions[index] = to_direction((*CurveSlopeAtT[verb])(pts, weight, tVals[index]).fY);
89     }
90     for (int index = 0; index < count; ++index) {
91         // skip intersections that end at edge and go up
92         if (zero_or_one(tVals[index]) && Contour::Direction::kCCW != directions[index]) {
93             continue;
94         }
95         winding += (int) directions[index];
96     }
97     return winding;  // note winding indicates containership, not contour direction
98 }
99 
conic_weight(const SkPath::Iter & iter,SkPath::Verb verb)100 static SkScalar conic_weight(const SkPath::Iter& iter, SkPath::Verb verb) {
101     return SkPath::kConic_Verb == verb ? iter.conicWeight() : 1;
102 }
103 
left_edge(SkPoint pts[4],SkPath::Verb verb,SkScalar weight,Contour::Direction * direction)104 static SkPoint left_edge(SkPoint pts[4], SkPath::Verb verb, SkScalar weight,
105         Contour::Direction* direction) {
106     SkASSERT(SkPath::kLine_Verb <= verb && verb <= SkPath::kCubic_Verb);
107     SkPoint result;
108     double dy;
109     double t SK_INIT_TO_AVOID_WARNING;
110     int roots = 0;
111     if (SkPath::kLine_Verb == verb) {
112         result = pts[0].fX < pts[1].fX ? pts[0] : pts[1];
113         dy = pts[1].fY - pts[0].fY;
114     } else if (SkPath::kQuad_Verb == verb) {
115         SkDQuad quad;
116         quad.set(pts);
117         if (!quad.monotonicInX()) {
118             roots = SkDQuad::FindExtrema(&quad[0].fX, &t);
119         }
120         if (roots) {
121             result = quad.ptAtT(t).asSkPoint();
122         } else {
123             result = pts[0].fX < pts[2].fX ? pts[0] : pts[2];
124             t = pts[0].fX < pts[2].fX ? 0 : 1;
125         }
126         dy = quad.dxdyAtT(t).fY;
127     } else if (SkPath::kConic_Verb == verb) {
128         SkDConic conic;
129         conic.set(pts, weight);
130         if (!conic.monotonicInX()) {
131             roots = SkDConic::FindExtrema(&conic[0].fX, weight, &t);
132         }
133         if (roots) {
134             result = conic.ptAtT(t).asSkPoint();
135         } else {
136             result = pts[0].fX < pts[2].fX ? pts[0] : pts[2];
137             t = pts[0].fX < pts[2].fX ? 0 : 1;
138         }
139         dy = conic.dxdyAtT(t).fY;
140     } else {
141         SkASSERT(SkPath::kCubic_Verb == verb);
142         SkDCubic cubic;
143         cubic.set(pts);
144         if (!cubic.monotonicInX()) {
145             double tValues[2];
146             roots = SkDCubic::FindExtrema(&cubic[0].fX, tValues);
147             SkASSERT(roots <= 2);
148             for (int index = 0; index < roots; ++index) {
149                 SkPoint temp = cubic.ptAtT(tValues[index]).asSkPoint();
150                 if (0 == index || result.fX > temp.fX) {
151                     result = temp;
152                     t = tValues[index];
153                 }
154             }
155         }
156         if (roots) {
157             result = cubic.ptAtT(t).asSkPoint();
158         } else {
159             result = pts[0].fX < pts[3].fX ? pts[0] : pts[3];
160             t = pts[0].fX < pts[3].fX ? 0 : 1;
161         }
162         dy = cubic.dxdyAtT(t).fY;
163     }
164     *direction = to_direction(dy);
165     return result;
166 }
167 
168 class OpAsWinding {
169 public:
170     enum class Edge {
171         kInitial,
172         kCompare,
173     };
174 
OpAsWinding(const SkPath & path)175     OpAsWinding(const SkPath& path)
176         : fPath(path) {
177     }
178 
contourBounds(vector<Contour> * containers)179     void contourBounds(vector<Contour>* containers) {
180         SkRect bounds;
181         bounds.setEmpty();
182         int lastStart = 0;
183         int verbStart = 0;
184         for (auto [verb, pts, w] : SkPathPriv::Iterate(fPath)) {
185             if (SkPathVerb::kMove == verb) {
186                 if (!bounds.isEmpty()) {
187                     containers->emplace_back(bounds, lastStart, verbStart);
188                     lastStart = verbStart;
189                }
190                bounds.setBounds(&pts[kPtIndex[SkPath::kMove_Verb]], kPtCount[SkPath::kMove_Verb]);
191             }
192             if (SkPathVerb::kLine <= verb && verb <= SkPathVerb::kCubic) {
193                 SkRect verbBounds;
194                 verbBounds.setBounds(&pts[kPtIndex[(int)verb]], kPtCount[(int)verb]);
195                 bounds.joinPossiblyEmptyRect(verbBounds);
196             }
197             ++verbStart;
198         }
199         if (!bounds.isEmpty()) {
200             containers->emplace_back(bounds, lastStart, ++verbStart);
201         }
202     }
203 
nextEdge(Contour & contour,Edge edge)204     int nextEdge(Contour& contour, Edge edge) {
205         SkPath::Iter iter(fPath, true);
206         SkPoint pts[4];
207         SkPath::Verb verb;
208         int verbCount = -1;
209         int winding = 0;
210         do {
211             verb = iter.next(pts);
212             if (++verbCount < contour.fVerbStart) {
213                 continue;
214             }
215             if (verbCount >= contour.fVerbEnd) {
216                 continue;
217             }
218             if (SkPath::kLine_Verb > verb || verb > SkPath::kCubic_Verb) {
219                 continue;
220             }
221             bool horizontal = true;
222             for (int index = 1; index <= kPtCount[verb]; ++index) {
223                 if (pts[0].fY != pts[index].fY) {
224                     horizontal = false;
225                     break;
226                 }
227             }
228             if (horizontal) {
229                 continue;
230             }
231             if (edge == Edge::kCompare) {
232                 winding += contains_edge(pts, verb, conic_weight(iter, verb), contour.fMinXY);
233                 continue;
234             }
235             SkASSERT(edge == Edge::kInitial);
236             Contour::Direction direction;
237             SkPoint minXY = left_edge(pts, verb, conic_weight(iter, verb), &direction);
238             if (minXY.fX > contour.fMinXY.fX) {
239                 continue;
240             }
241             if (minXY.fX == contour.fMinXY.fX) {
242                 if (minXY.fY != contour.fMinXY.fY) {
243                     continue;
244                 }
245                 if (direction == contour.fDirection) {
246                     continue;
247                 }
248                 // incomplete: must sort edges to find the one most to left
249                 // File a bug if this code path is triggered and AsWinding was
250                 // expected to succeed.
251                 SkDEBUGF("incomplete\n");
252                 // TODO: add edges as opangle and sort
253             }
254             contour.fMinXY = minXY;
255             contour.fDirection = direction;
256         } while (SkPath::kDone_Verb != verb);
257         return winding;
258     }
259 
containerContains(Contour & contour,Contour & test)260     bool containerContains(Contour& contour, Contour& test) {
261         // find outside point on lesser contour
262         // arbitrarily, choose non-horizontal edge where point <= bounds left
263         // note that if leftmost point is control point, may need tight bounds
264             // to find edge with minimum-x
265         if (SK_ScalarMax == test.fMinXY.fX) {
266             this->nextEdge(test, Edge::kInitial);
267         }
268         // find all edges on greater equal or to the left of one on lesser
269         contour.fMinXY = test.fMinXY;
270         int winding = this->nextEdge(contour, Edge::kCompare);
271         // if edge is up, mark contour cw, otherwise, ccw
272         // sum of greater edges direction should be cw, 0, ccw
273         test.fContained = winding != 0;
274         return -1 <= winding && winding <= 1;
275     }
276 
inParent(Contour & contour,Contour & parent)277     void inParent(Contour& contour, Contour& parent) {
278         // move contour into sibling list contained by parent
279         for (auto test : parent.fChildren) {
280             if (test->fBounds.contains(contour.fBounds)) {
281                 inParent(contour, *test);
282                 return;
283             }
284         }
285         // move parent's children into contour's children if contained by contour
286         for (auto iter = parent.fChildren.begin(); iter != parent.fChildren.end(); ) {
287             if (contour.fBounds.contains((*iter)->fBounds)) {
288                 contour.fChildren.push_back(*iter);
289                 iter = parent.fChildren.erase(iter);
290                 continue;
291             }
292             ++iter;
293         }
294         parent.fChildren.push_back(&contour);
295     }
296 
checkContainerChildren(Contour * parent,Contour * child)297     bool checkContainerChildren(Contour* parent, Contour* child) {
298         for (auto grandChild : child->fChildren) {
299             if (!checkContainerChildren(child, grandChild)) {
300                 return false;
301             }
302         }
303         if (parent) {
304             if (!containerContains(*parent, *child)) {
305                 return false;
306             }
307         }
308         return true;
309     }
310 
markReverse(Contour * parent,Contour * child)311     bool markReverse(Contour* parent, Contour* child) {
312         bool reversed = false;
313         for (auto grandChild : child->fChildren) {
314             reversed |= markReverse(grandChild->fContained ? child : parent, grandChild);
315         }
316         if (parent && parent->fDirection == child->fDirection) {
317             child->fReverse = true;
318             child->fDirection = (Contour::Direction) -(int) child->fDirection;
319             return true;
320         }
321         return reversed;
322     }
323 
reverseMarkedContours(vector<Contour> & contours,SkPathFillType fillType)324     SkPath reverseMarkedContours(vector<Contour>& contours, SkPathFillType fillType) {
325         SkPathPriv::Iterate iterate(fPath);
326         auto iter = iterate.begin();
327         int verbCount = 0;
328 
329         SkPathBuilder result;
330         result.setFillType(fillType);
331         for (const Contour& contour : contours) {
332             SkPathBuilder reverse;
333             SkPathBuilder* temp = contour.fReverse ? &reverse : &result;
334             for (; iter != iterate.end() && verbCount < contour.fVerbEnd; ++iter, ++verbCount) {
335                 auto [verb, pts, w] = *iter;
336                 switch (verb) {
337                     case SkPathVerb::kMove:
338                         temp->moveTo(pts[0]);
339                         break;
340                     case SkPathVerb::kLine:
341                         temp->lineTo(pts[1]);
342                         break;
343                     case SkPathVerb::kQuad:
344                         temp->quadTo(pts[1], pts[2]);
345                         break;
346                     case SkPathVerb::kConic:
347                         temp->conicTo(pts[1], pts[2], *w);
348                         break;
349                     case SkPathVerb::kCubic:
350                         temp->cubicTo(pts[1], pts[2], pts[3]);
351                         break;
352                     case SkPathVerb::kClose:
353                         temp->close();
354                         break;
355                 }
356             }
357             if (contour.fReverse) {
358                 SkASSERT(temp == &reverse);
359                 SkPathPriv::ReverseAddPath(&result, reverse.detach());
360             }
361         }
362         return result.detach();
363     }
364 
365 private:
366     const SkPath& fPath;
367 };
368 
set_result_path(SkPath * result,const SkPath & path,SkPathFillType fillType)369 static bool set_result_path(SkPath* result, const SkPath& path, SkPathFillType fillType) {
370     *result = path;
371     result->setFillType(fillType);
372     return true;
373 }
374 
AsWinding(const SkPath & path,SkPath * result)375 bool AsWinding(const SkPath& path, SkPath* result) {
376     if (!path.isFinite()) {
377         return false;
378     }
379     SkPathFillType fillType = path.getFillType();
380     if (fillType == SkPathFillType::kWinding
381             || fillType == SkPathFillType::kInverseWinding ) {
382         return set_result_path(result, path, fillType);
383     }
384     fillType = path.isInverseFillType() ? SkPathFillType::kInverseWinding :
385             SkPathFillType::kWinding;
386     if (path.isEmpty() || path.isConvex()) {
387         return set_result_path(result, path, fillType);
388     }
389     // count contours
390     vector<Contour> contours;   // one per contour
391     OpAsWinding winder(path);
392     winder.contourBounds(&contours);
393     if (contours.size() <= 1) {
394         return set_result_path(result, path, fillType);
395     }
396     // create contour bounding box tree
397     Contour sorted(SkRect(), 0, 0);
398     for (auto& contour : contours) {
399         winder.inParent(contour, sorted);
400     }
401     // if sorted has no grandchildren, no child has to fix its children's winding
402     if (std::all_of(sorted.fChildren.begin(), sorted.fChildren.end(),
403             [](const Contour* contour) -> bool { return !contour->fChildren.size(); } )) {
404         return set_result_path(result, path, fillType);
405     }
406     // starting with outermost and moving inward, see if one path contains another
407     for (auto contour : sorted.fChildren) {
408         winder.nextEdge(*contour, OpAsWinding::Edge::kInitial);
409         if (!winder.checkContainerChildren(nullptr, contour)) {
410             return false;
411         }
412     }
413     // starting with outermost and moving inward, mark paths to reverse
414     bool reversed = false;
415     for (auto contour : sorted.fChildren) {
416         reversed |= winder.markReverse(nullptr, contour);
417     }
418     if (!reversed) {
419         return set_result_path(result, path, fillType);
420     }
421     *result = winder.reverseMarkedContours(contours, fillType);
422     return true;
423 }
424