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