caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 1 | |
| 2 | /* |
| 3 | * Copyright 2012 Google Inc. |
| 4 | * |
| 5 | * Use of this source code is governed by a BSD-style license that can be |
| 6 | * found in the LICENSE file. |
| 7 | */ |
| 8 | |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 9 | #include "CurveIntersection.h" |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 10 | #include "LineIntersection.h" |
| 11 | #include "SkPath.h" |
| 12 | #include "SkRect.h" |
| 13 | #include "SkTArray.h" |
| 14 | #include "SkTDArray.h" |
| 15 | #include "TSearch.h" |
| 16 | |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 17 | static int LineIntersect(const SkPoint a[2], const SkPoint b[2], |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 18 | double aRange[2], double bRange[2]) { |
| 19 | _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}}; |
| 20 | _Line bLine = {{b[0].fX, b[0].fY}, {b[1].fX, b[1].fY}}; |
| 21 | return intersect(aLine, bLine, aRange, bRange); |
| 22 | } |
| 23 | |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 24 | static int LineIntersect(const SkPoint a[2], SkScalar y, double aRange[2]) { |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 25 | _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}}; |
| 26 | return horizontalIntersect(aLine, y, aRange); |
| 27 | } |
| 28 | |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 29 | static SkScalar LineYAtT(const SkPoint a[2], double t) { |
| 30 | _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}}; |
| 31 | double y; |
| 32 | xy_at_t(aLine, t, *(double*) 0, y); |
| 33 | return SkDoubleToScalar(y); |
| 34 | } |
| 35 | |
| 36 | static void LineSubDivide(const SkPoint a[2], double startT, double endT, |
| 37 | SkPoint sub[2]) { |
| 38 | _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}}; |
| 39 | _Line dst; |
| 40 | sub_divide(aLine, startT, endT, dst); |
| 41 | sub[0].fX = SkDoubleToScalar(dst[0].x); |
| 42 | sub[0].fY = SkDoubleToScalar(dst[0].y); |
| 43 | sub[1].fX = SkDoubleToScalar(dst[1].x); |
| 44 | sub[1].fY = SkDoubleToScalar(dst[1].y); |
| 45 | } |
| 46 | |
| 47 | |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 48 | // functions |
| 49 | void contourBounds(const SkPath& path, SkTDArray<SkRect>& boundsArray); |
| 50 | void simplify(const SkPath& path, bool asFill, SkPath& simple); |
| 51 | /* |
| 52 | list of edges |
| 53 | bounds for edge |
| 54 | sort |
| 55 | active T |
| 56 | |
| 57 | if a contour's bounds is outside of the active area, no need to create edges |
| 58 | */ |
| 59 | |
| 60 | /* given one or more paths, |
| 61 | find the bounds of each contour, select the active contours |
| 62 | for each active contour, compute a set of edges |
| 63 | each edge corresponds to one or more lines and curves |
| 64 | leave edges unbroken as long as possible |
| 65 | when breaking edges, compute the t at the break but leave the control points alone |
| 66 | |
| 67 | */ |
| 68 | |
| 69 | void contourBounds(const SkPath& path, SkTDArray<SkRect>& boundsArray) { |
| 70 | SkPath::Iter iter(path, false); |
| 71 | SkPoint pts[4]; |
| 72 | SkPath::Verb verb; |
| 73 | SkRect bounds; |
| 74 | bounds.setEmpty(); |
| 75 | int count = 0; |
| 76 | while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { |
| 77 | switch (verb) { |
| 78 | case SkPath::kMove_Verb: |
| 79 | if (!bounds.isEmpty()) { |
| 80 | *boundsArray.append() = bounds; |
| 81 | } |
| 82 | bounds.set(pts[0].fX, pts[0].fY, pts[0].fX, pts[0].fY); |
| 83 | count = 0; |
| 84 | break; |
| 85 | case SkPath::kLine_Verb: |
| 86 | count = 1; |
| 87 | break; |
| 88 | case SkPath::kQuad_Verb: |
| 89 | count = 2; |
| 90 | break; |
| 91 | case SkPath::kCubic_Verb: |
| 92 | count = 3; |
| 93 | break; |
| 94 | case SkPath::kClose_Verb: |
| 95 | count = 0; |
| 96 | break; |
| 97 | default: |
| 98 | SkDEBUGFAIL("bad verb"); |
| 99 | return; |
| 100 | } |
| 101 | for (int i = 1; i <= count; ++i) { |
| 102 | bounds.growToInclude(pts[i].fX, pts[i].fY); |
| 103 | } |
| 104 | } |
| 105 | } |
| 106 | |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 107 | struct OutEdge { |
| 108 | |
| 109 | SkTDArray<SkPoint> fPts; |
| 110 | SkTDArray<uint8_t> fVerbs; |
| 111 | }; |
| 112 | |
| 113 | class OutEdgeBuilder { |
| 114 | public: |
| 115 | void addLine(SkPoint pts[2]) { |
| 116 | ; |
| 117 | OutEdge* edge; |
| 118 | |
| 119 | edge = fEdges.append(); |
| 120 | |
| 121 | if (empty) { |
| 122 | *edge->fPts.append() = pts[0]; |
| 123 | } |
| 124 | *edge->fPts.append() = pts[1]; |
| 125 | } |
| 126 | |
| 127 | SkTArray<OutEdge> fEdges; |
| 128 | }; |
| 129 | |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 130 | // Bounds, unlike Rect, does not consider a vertical line to be empty. |
| 131 | struct Bounds : public SkRect { |
| 132 | static bool Intersects(const Bounds& a, const Bounds& b) { |
| 133 | return a.fLeft <= b.fRight && b.fLeft <= a.fRight && |
| 134 | a.fTop <= b.fBottom && b.fTop <= a.fBottom; |
| 135 | } |
| 136 | }; |
| 137 | |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 138 | struct Intercepts { |
| 139 | SkTDArray<double> fTs; |
| 140 | }; |
| 141 | |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 142 | struct InEdge { |
| 143 | bool operator<(const InEdge& rh) const { |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 144 | return fBounds.fTop == rh.fBounds.fTop |
| 145 | ? fBounds.fLeft < rh.fBounds.fLeft |
| 146 | : fBounds.fTop < rh.fBounds.fTop; |
| 147 | } |
| 148 | |
| 149 | void add(double* ts, size_t count, int verbIndex) { |
| 150 | Intercepts& intercepts = fIntercepts[verbIndex]; |
| 151 | // FIXME: in the pathological case where there is a ton of intercepts, binary search? |
| 152 | for (size_t index = 0; index < count; ++index) { |
| 153 | double t = ts[index]; |
| 154 | size_t tCount = intercepts.fTs.count(); |
| 155 | for (size_t idx2 = 0; idx2 < tCount; ++idx2) { |
| 156 | if (t <= intercepts.fTs[idx2]) { |
| 157 | if (t < intercepts.fTs[idx2]) { |
| 158 | *intercepts.fTs.insert(idx2) = t; |
| 159 | break; |
| 160 | } |
| 161 | } |
| 162 | } |
| 163 | if (tCount == 0 || t > intercepts.fTs[tCount - 1]) { |
| 164 | *intercepts.fTs.append() = t; |
| 165 | } |
| 166 | } |
| 167 | } |
| 168 | |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 169 | bool cached(const InEdge* edge) { |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 170 | // FIXME: in the pathological case where there is a ton of edges, binary search? |
| 171 | size_t count = fCached.count(); |
| 172 | for (size_t index = 0; index < count; ++index) { |
| 173 | if (edge == fCached[index]) { |
| 174 | return true; |
| 175 | } |
| 176 | if (edge < fCached[index]) { |
| 177 | *fCached.insert(index) = edge; |
| 178 | return false; |
| 179 | } |
| 180 | } |
| 181 | *fCached.append() = edge; |
| 182 | return false; |
| 183 | } |
| 184 | |
| 185 | void complete(signed char winding) { |
| 186 | SkPoint* ptPtr = fPts.begin(); |
| 187 | SkPoint* ptLast = fPts.end(); |
| 188 | if (ptPtr == ptLast) { |
| 189 | SkDebugf("empty edge\n"); |
| 190 | SkASSERT(0); |
| 191 | // FIXME: delete empty edge? |
| 192 | return; |
| 193 | } |
| 194 | fBounds.set(ptPtr->fX, ptPtr->fY, ptPtr->fX, ptPtr->fY); |
| 195 | ++ptPtr; |
| 196 | while (ptPtr != ptLast) { |
| 197 | fBounds.growToInclude(ptPtr->fX, ptPtr->fY); |
| 198 | ++ptPtr; |
| 199 | } |
| 200 | fIntercepts.push_back_n(1); |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 201 | if ((fWinding = winding) < 0) { // reverse verbs, pts, if bottom to top |
| 202 | size_t index; |
| 203 | size_t last = fPts.count() - 1; |
| 204 | for (index = 0; index < last; ++index, --last) { |
| 205 | SkTSwap<SkPoint>(fPts[index], fPts[last]); |
| 206 | } |
| 207 | last = fVerbs.count() - 1; |
| 208 | for (index = 0; index < last; ++index, --last) { |
| 209 | SkTSwap<uint8_t>(fVerbs[index], fVerbs[last]); |
| 210 | } |
| 211 | } |
| 212 | fContainsIntercepts = false; |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 213 | } |
| 214 | |
| 215 | // temporary data : move this to a separate struct? |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 216 | SkTDArray<const InEdge*> fCached; // list of edges already intercepted |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 217 | SkTArray<Intercepts> fIntercepts; // one per verb |
| 218 | |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 219 | // persistent data |
| 220 | SkTDArray<SkPoint> fPts; |
| 221 | SkTDArray<uint8_t> fVerbs; |
| 222 | Bounds fBounds; |
| 223 | signed char fWinding; |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 224 | bool fContainsIntercepts; |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 225 | }; |
| 226 | |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 227 | class InEdgeBuilder { |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 228 | public: |
| 229 | |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 230 | InEdgeBuilder(const SkPath& path, bool ignoreHorizontal, SkTArray<InEdge>& edges) |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 231 | : fPath(path) |
| 232 | , fCurrentEdge(NULL) |
| 233 | , fEdges(edges) |
| 234 | , fIgnoreHorizontal(ignoreHorizontal) |
| 235 | { |
| 236 | walk(); |
| 237 | } |
| 238 | |
| 239 | protected: |
| 240 | |
| 241 | void addEdge() { |
| 242 | fCurrentEdge->fPts.append(fPtCount - fPtOffset, &fPts[fPtOffset]); |
| 243 | fPtOffset = 1; |
| 244 | *fCurrentEdge->fVerbs.append() = fVerb; |
| 245 | } |
| 246 | |
| 247 | int direction(int count) { |
| 248 | fPtCount = count; |
| 249 | fIgnorableHorizontal = fIgnoreHorizontal && isHorizontal(); |
| 250 | if (fIgnorableHorizontal) { |
| 251 | return 0; |
| 252 | } |
| 253 | int last = count - 1; |
| 254 | return fPts[0].fY == fPts[last].fY |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 255 | ? fPts[0].fX == fPts[last].fX ? 0 : fPts[0].fX < fPts[last].fX |
| 256 | ? 1 : -1 : fPts[0].fY < fPts[last].fY ? 1 : -1; |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 257 | } |
| 258 | |
| 259 | bool isHorizontal() { |
| 260 | SkScalar y = fPts[0].fY; |
| 261 | for (int i = 1; i < fPtCount; ++i) { |
| 262 | if (fPts[i].fY != y) { |
| 263 | return false; |
| 264 | } |
| 265 | } |
| 266 | return true; |
| 267 | } |
| 268 | |
| 269 | void startEdge() { |
| 270 | fCurrentEdge = fEdges.push_back_n(1); |
| 271 | fWinding = 0; |
| 272 | fPtOffset = 0; |
| 273 | } |
| 274 | |
| 275 | void walk() { |
| 276 | SkPath::Iter iter(fPath, true); |
| 277 | int winding = 0; |
| 278 | while ((fVerb = iter.next(fPts)) != SkPath::kDone_Verb) { |
| 279 | switch (fVerb) { |
| 280 | case SkPath::kMove_Verb: |
| 281 | winding = 0; |
| 282 | startEdge(); |
| 283 | continue; |
| 284 | case SkPath::kLine_Verb: |
| 285 | winding = direction(2); |
| 286 | break; |
| 287 | case SkPath::kQuad_Verb: |
| 288 | winding = direction(3); |
| 289 | break; |
| 290 | case SkPath::kCubic_Verb: |
| 291 | winding = direction(4); |
| 292 | break; |
| 293 | case SkPath::kClose_Verb: |
| 294 | if (fCurrentEdge->fVerbs.count()) { |
| 295 | fCurrentEdge->complete(fWinding); |
| 296 | } |
| 297 | continue; |
| 298 | default: |
| 299 | SkDEBUGFAIL("bad verb"); |
| 300 | return; |
| 301 | } |
| 302 | if (fIgnorableHorizontal) { |
| 303 | continue; |
| 304 | } |
| 305 | if (fWinding + winding == 0) { |
| 306 | // FIXME: if prior verb or this verb is a horizontal line, reverse |
| 307 | // it instead of starting a new edge |
| 308 | fCurrentEdge->complete(fWinding); |
| 309 | startEdge(); |
| 310 | } |
| 311 | fWinding = winding; |
| 312 | addEdge(); |
| 313 | } |
| 314 | fCurrentEdge->complete(fWinding); |
| 315 | } |
| 316 | |
| 317 | private: |
| 318 | const SkPath& fPath; |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 319 | InEdge* fCurrentEdge; |
| 320 | SkTArray<InEdge>& fEdges; |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 321 | SkPoint fPts[4]; |
| 322 | SkPath::Verb fVerb; |
| 323 | int fPtCount; |
| 324 | int fPtOffset; |
| 325 | int8_t fWinding; |
| 326 | bool fIgnorableHorizontal; |
| 327 | bool fIgnoreHorizontal; |
| 328 | }; |
| 329 | |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 330 | struct WorkEdge { |
| 331 | SkScalar bottom() const { |
| 332 | return fPts[verb()].fY; |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 333 | } |
| 334 | |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 335 | void init(const InEdge* edge) { |
| 336 | fEdge = edge; |
| 337 | fPts = edge->fPts.begin(); |
| 338 | fVerb = edge->fVerbs.begin(); |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 339 | } |
| 340 | |
| 341 | bool next() { |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 342 | SkASSERT(fVerb < fEdge->fVerbs.end()); |
| 343 | fPts += *fVerb++; |
| 344 | return fVerb != fEdge->fVerbs.end(); |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 345 | } |
| 346 | |
| 347 | SkPath::Verb verb() const { |
| 348 | return (SkPath::Verb) *fVerb; |
| 349 | } |
| 350 | |
| 351 | int verbIndex() const { |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 352 | return fVerb - fEdge->fVerbs.begin(); |
| 353 | } |
| 354 | |
| 355 | int winding() const { |
| 356 | return fEdge->fWinding; |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 357 | } |
| 358 | |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 359 | const InEdge* fEdge; |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 360 | const SkPoint* fPts; |
| 361 | const uint8_t* fVerb; |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 362 | }; |
| 363 | |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 364 | // always constructed with SkTDArray because new edges are inserted |
| 365 | // this may be a inappropriate optimization, suggesting that a separate array of |
| 366 | // ActiveEdge* may be faster to insert and search |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 367 | struct ActiveEdge { |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 368 | void init(const InEdge* edge) { |
| 369 | fWorkEdge.init(edge); |
| 370 | initT(); |
| 371 | } |
| 372 | |
| 373 | void initT() { |
| 374 | fTs = &fWorkEdge.fEdge->fIntercepts[fWorkEdge.verbIndex()].fTs; |
| 375 | fTIndex = 0; |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 376 | } |
| 377 | |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 378 | bool nextT() { |
| 379 | SkASSERT(fTIndex <= fTs->count()); |
| 380 | return ++fTIndex == fTs->count() + 1; |
| 381 | } |
| 382 | |
| 383 | bool next() { |
| 384 | bool result = fWorkEdge.next(); |
| 385 | initT(); |
| 386 | return result; |
| 387 | } |
| 388 | |
| 389 | double t() { |
| 390 | if (fTIndex == 0) { |
| 391 | return 0; |
| 392 | } |
| 393 | if (fTIndex > fTs->count()) { |
| 394 | return 1; |
| 395 | } |
| 396 | return (*fTs)[fTIndex - 1]; |
| 397 | } |
| 398 | |
| 399 | WorkEdge fWorkEdge; |
| 400 | const SkTDArray<double>* fTs; |
| 401 | int fTIndex; |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 402 | }; |
| 403 | |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 404 | static void addToActive(SkTDArray<ActiveEdge>& activeEdges, const InEdge* edge) { |
| 405 | // FIXME: in the pathological case where there is a ton of intercepts, binary search? |
| 406 | size_t count = activeEdges.count(); |
| 407 | for (size_t index = 0; index < count; ++index) { |
| 408 | if (edge < activeEdges[index].fWorkEdge.fEdge) { |
| 409 | ActiveEdge* active = activeEdges.insert(index); |
| 410 | active->init(edge); |
| 411 | return; |
| 412 | } |
| 413 | if (edge == activeEdges[index].fWorkEdge.fEdge) { |
| 414 | return; |
| 415 | } |
| 416 | } |
| 417 | ActiveEdge* active = activeEdges.append(); |
| 418 | active->init(edge); |
| 419 | } |
| 420 | |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 421 | void simplify(const SkPath& path, bool asFill, SkPath& simple) { |
| 422 | // turn path into list of edges increasing in y |
| 423 | // if an edge is a quad or a cubic with a y extrema, note it, but leave it unbroken |
| 424 | // once we have a list, sort it, then walk the list (walk edges twice that have y extrema's on top) |
| 425 | // and detect crossings -- look for raw bounds that cross over, then tight bounds that cross |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 426 | SkTArray<InEdge> edges; |
| 427 | InEdgeBuilder builder(path, asFill, edges); |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 428 | size_t edgeCount = edges.count(); |
| 429 | simple.reset(); |
| 430 | if (edgeCount == 0) { |
| 431 | return; |
| 432 | } |
| 433 | // returns 1 for evenodd, -1 for winding, regardless of inverse-ness |
| 434 | int windingMask = (path.getFillType() & 1) ? 1 : -1; |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 435 | SkTDArray<InEdge*> edgeList; |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 436 | for (size_t index = 0; index < edgeCount; ++index) { |
| 437 | *edgeList.append() = &edges[index]; |
| 438 | } |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 439 | InEdge edgeSentinel; |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 440 | edgeSentinel.fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax); |
| 441 | *edgeList.append() = &edgeSentinel; |
| 442 | ++edgeCount; |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 443 | QSort<InEdge>(edgeList.begin(), edgeCount); |
| 444 | InEdge** currentPtr = edgeList.begin(); |
| 445 | InEdge* current = *currentPtr; |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 446 | SkScalar y = current->fBounds.fTop; |
| 447 | SkScalar bottom = current->fBounds.fBottom; |
| 448 | // walk the sorted edges from top to bottom, computing accumulated winding |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 449 | SkTDArray<ActiveEdge> activeEdges; |
| 450 | OutEdgeBuilder outBuilder; |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 451 | do { |
| 452 | // find the list of edges that cross y |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 453 | InEdge** lastPtr = currentPtr; // find the edge below the bottom of the first set |
| 454 | InEdge* last = *lastPtr; |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 455 | while (lastPtr != edgeList.end()) { |
| 456 | if (bottom <= last->fBounds.fTop) { |
| 457 | break; |
| 458 | } |
| 459 | SkScalar lastTop = last->fBounds.fTop; |
| 460 | // OPTIMIZATION: Shortening the bottom is only interesting when filling |
| 461 | // and when the edge is to the left of a longer edge. If it's a framing |
| 462 | // edge, or part of the right, it won't effect the longer edges. |
| 463 | if (lastTop > y) { |
| 464 | if (bottom > lastTop) { |
| 465 | bottom = lastTop; |
| 466 | break; |
| 467 | } |
| 468 | } else if (bottom > last->fBounds.fBottom) { |
| 469 | bottom = last->fBounds.fBottom; |
| 470 | } |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 471 | addToActive(activeEdges, last); |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 472 | last = *++lastPtr; |
| 473 | } |
| 474 | if (asFill && lastPtr - currentPtr <= 1) { |
| 475 | SkDebugf("expect 2 or more edges\n"); |
| 476 | SkASSERT(0); |
| 477 | return; |
| 478 | } |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 479 | |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 480 | // find any intersections in the range of active edges |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 481 | InEdge** testPtr = currentPtr; |
| 482 | InEdge* test = *testPtr; |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 483 | while (testPtr != lastPtr) { |
| 484 | if (test->fBounds.fBottom > bottom) { |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 485 | WorkEdge wt; |
| 486 | wt.init(test); |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 487 | do { |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 488 | // FIXME: add all curve types |
| 489 | // OPTIMIZATION: if bottom intersection does not change |
| 490 | // the winding on either side of the split, don't intersect |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 491 | if (wt.verb() == SkPath::kLine_Verb) { |
| 492 | double wtTs[2]; |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 493 | int pts = LineIntersect(wt.fPts, bottom, wtTs); |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 494 | if (pts) { |
| 495 | test->add(wtTs, pts, wt.verbIndex()); |
| 496 | } |
| 497 | } |
| 498 | } while (wt.next()); |
| 499 | } |
| 500 | test = *++testPtr; |
| 501 | } |
| 502 | testPtr = currentPtr; |
| 503 | test = *testPtr; |
| 504 | while (testPtr != lastPtr - 1) { |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 505 | InEdge* next = *++testPtr; |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 506 | if (!test->cached(next) |
| 507 | && Bounds::Intersects(test->fBounds, next->fBounds)) { |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 508 | WorkEdge wt, wn; |
| 509 | wt.init(test); |
| 510 | wn.init(next); |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 511 | do { |
| 512 | // FIXME: add all combinations of curve types |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 513 | if (wt.verb() == SkPath::kLine_Verb |
| 514 | && wn.verb() == SkPath::kLine_Verb) { |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 515 | double wtTs[2], wnTs[2]; |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 516 | int pts = LineIntersect(wt.fPts, wn.fPts, wtTs, wnTs); |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 517 | if (pts) { |
| 518 | test->add(wtTs, pts, wt.verbIndex()); |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 519 | test->fContainsIntercepts = true; |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 520 | next->add(wnTs, pts, wn.verbIndex()); |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 521 | next->fContainsIntercepts = true; |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 522 | } |
| 523 | } |
| 524 | } while (wt.bottom() <= wn.bottom() ? wt.next() : wn.next()); |
| 525 | } |
| 526 | test = next; |
| 527 | } |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 528 | |
| 529 | // compute bottom taking into account any intersected edges |
| 530 | ActiveEdge* activePtr = activeEdges.begin() - 1; |
| 531 | ActiveEdge* lastActive = activeEdges.end(); |
| 532 | while (++activePtr != lastActive) { |
| 533 | const InEdge* test = activePtr->fWorkEdge.fEdge; |
| 534 | if (!test->fContainsIntercepts) { |
| 535 | continue; |
| 536 | } |
| 537 | WorkEdge wt; |
| 538 | wt.init(test); |
| 539 | do { |
| 540 | // FIXME: add all curve types |
| 541 | const Intercepts& intercepts = test->fIntercepts[wt.verbIndex()]; |
| 542 | const SkTDArray<double>& fTs = intercepts.fTs; |
| 543 | size_t count = fTs.count(); |
| 544 | for (size_t index = 0; index < count; ++index) { |
| 545 | if (wt.verb() == SkPath::kLine_Verb) { |
| 546 | SkScalar yIntercept = LineYAtT(wt.fPts, fTs[index]); |
| 547 | if (bottom > yIntercept) { |
| 548 | bottom = yIntercept; |
| 549 | } |
| 550 | } |
| 551 | } |
| 552 | } while (wt.next()); |
| 553 | } |
| 554 | |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 555 | // stitch edge and t range that satisfies operation |
| 556 | int winding = 0; |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 557 | activePtr = activeEdges.begin() - 1; |
| 558 | lastActive = activeEdges.end(); |
| 559 | SkDebugf("%s y=%g bottom=%g\n", __FUNCTION__, y, bottom); |
| 560 | while (++activePtr != lastActive) { |
| 561 | const WorkEdge& wt = activePtr->fWorkEdge; |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 562 | int lastWinding = winding; |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 563 | winding += wt.winding(); |
| 564 | if (!(lastWinding & windingMask) && !(winding & windingMask)) { |
| 565 | continue; |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 566 | } |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 567 | do { |
| 568 | double currentT = activePtr->t(); |
| 569 | const SkPoint* points = wt.fPts; |
| 570 | bool last; |
| 571 | do { |
| 572 | last = activePtr->nextT(); |
| 573 | double nextT = activePtr->t(); |
| 574 | // FIXME: add all combinations of curve types |
| 575 | if (wt.verb() == SkPath::kLine_Verb) { |
| 576 | SkPoint clippedPts[2]; |
| 577 | const SkPoint* clipped; |
| 578 | if (currentT * nextT != 0 || currentT + nextT != 1) { |
| 579 | LineSubDivide(points, currentT, nextT, clippedPts); |
| 580 | clipped = clippedPts; |
| 581 | } else { |
| 582 | clipped = points; |
| 583 | } |
| 584 | SkDebugf("%s line %g,%g %g,%g\n", __FUNCTION__, |
| 585 | clipped[0].fX, clipped[0].fY, |
| 586 | clipped[1].fX, clipped[1].fY); |
| 587 | outBuilder->addLine(clipped); |
| 588 | if (clipped[1].fY >= bottom) { |
| 589 | goto nextEdge; |
| 590 | } |
| 591 | } |
| 592 | currentT = nextT; |
| 593 | } while (!last); |
| 594 | } while (activePtr->next()); |
| 595 | nextEdge: |
| 596 | ; |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 597 | } |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 598 | |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 599 | y = bottom; |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 600 | while ((*currentPtr)->fBounds.fBottom <= y) { |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 601 | ++currentPtr; |
| 602 | } |
| 603 | } while (*currentPtr != &edgeSentinel); |
caryclark@google.com | 6680fb1 | 2012-02-07 22:10:51 +0000 | [diff] [blame^] | 604 | |
caryclark@google.com | c682590 | 2012-02-03 22:07:47 +0000 | [diff] [blame] | 605 | // assemble output path from string of pts, verbs |
| 606 | ; |
| 607 | } |
| 608 | |
| 609 | void testSimplify(); |
| 610 | |
| 611 | void testSimplify() { |
| 612 | SkPath path, out; |
| 613 | path.setFillType(SkPath::kWinding_FillType); |
| 614 | path.addRect(10, 10, 30, 30); |
| 615 | path.addRect(20, 20, 40, 40); |
| 616 | simplify(path, true, out); |
| 617 | path = out; |
| 618 | path.addRect(30, 10, 40, 20); |
| 619 | path.addRect(10, 30, 20, 40); |
| 620 | simplify(path, true, out); |
| 621 | path = out; |
| 622 | path.addRect(10, 10, 40, 40, SkPath::kCCW_Direction); |
| 623 | simplify(path, true, out); |
| 624 | if (!out.isEmpty()) { |
| 625 | SkDebugf("expected empty\n"); |
| 626 | } |
| 627 | } |