1 /* 2 * Copyright 2015 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 8 #ifndef SkPathPriv_DEFINED 9 #define SkPathPriv_DEFINED 10 11 #include "include/core/SkArc.h" 12 #include "include/core/SkPath.h" 13 #include "include/core/SkPathBuilder.h" 14 #include "include/core/SkPathTypes.h" 15 #include "include/core/SkPoint.h" 16 #include "include/core/SkRect.h" 17 #include "include/core/SkRefCnt.h" 18 #include "include/core/SkScalar.h" 19 #include "include/core/SkTypes.h" 20 #include "include/private/SkIDChangeListener.h" 21 #include "include/private/SkPathRef.h" 22 #include "include/private/base/SkDebug.h" 23 #include "src/core/SkPathEnums.h" 24 25 #include <cstdint> 26 #include <iterator> 27 #include <utility> 28 29 class SkMatrix; 30 class SkRRect; 31 32 static_assert(0 == static_cast<int>(SkPathFillType::kWinding), "fill_type_mismatch"); 33 static_assert(1 == static_cast<int>(SkPathFillType::kEvenOdd), "fill_type_mismatch"); 34 static_assert(2 == static_cast<int>(SkPathFillType::kInverseWinding), "fill_type_mismatch"); 35 static_assert(3 == static_cast<int>(SkPathFillType::kInverseEvenOdd), "fill_type_mismatch"); 36 37 class SkPathPriv { 38 public: 39 // skbug.com/9906: Not a perfect solution for W plane clipping, but 1/16384 is a 40 // reasonable limit (roughly 5e-5) 41 inline static constexpr SkScalar kW0PlaneDistance = 1.f / (1 << 14); 42 AsFirstDirection(SkPathDirection dir)43 static SkPathFirstDirection AsFirstDirection(SkPathDirection dir) { 44 // since we agree numerically for the values in Direction, we can just cast. 45 return (SkPathFirstDirection)dir; 46 } 47 48 /** 49 * Return the opposite of the specified direction. kUnknown is its own 50 * opposite. 51 */ OppositeFirstDirection(SkPathFirstDirection dir)52 static SkPathFirstDirection OppositeFirstDirection(SkPathFirstDirection dir) { 53 static const SkPathFirstDirection gOppositeDir[] = { 54 SkPathFirstDirection::kCCW, SkPathFirstDirection::kCW, SkPathFirstDirection::kUnknown, 55 }; 56 return gOppositeDir[(unsigned)dir]; 57 } 58 59 /** 60 * Tries to compute the direction of the outer-most non-degenerate 61 * contour. If it can be computed, return that direction. If it cannot be determined, 62 * or the contour is known to be convex, return kUnknown. If the direction was determined, 63 * it is cached to make subsequent calls return quickly. 64 */ 65 static SkPathFirstDirection ComputeFirstDirection(const SkPath&); 66 IsClosedSingleContour(const SkPath & path)67 static bool IsClosedSingleContour(const SkPath& path) { 68 int verbCount = path.countVerbs(); 69 if (verbCount == 0) 70 return false; 71 int moveCount = 0; 72 auto verbs = path.fPathRef->verbsBegin(); 73 for (int i = 0; i < verbCount; i++) { 74 switch (verbs[i]) { 75 case SkPath::Verb::kMove_Verb: 76 moveCount += 1; 77 if (moveCount > 1) { 78 return false; 79 } 80 break; 81 case SkPath::Verb::kClose_Verb: 82 if (i == verbCount - 1) { 83 return true; 84 } 85 return false; 86 default: break; 87 } 88 } 89 return false; 90 } 91 92 // In some scenarios (e.g. fill or convexity checking all but the last leading move to are 93 // irrelevant to behavior). SkPath::injectMoveToIfNeeded should ensure that this is always at 94 // least 1. LeadingMoveToCount(const SkPath & path)95 static int LeadingMoveToCount(const SkPath& path) { 96 int verbCount = path.countVerbs(); 97 auto verbs = path.fPathRef->verbsBegin(); 98 for (int i = 0; i < verbCount; i++) { 99 if (verbs[i] != SkPath::Verb::kMove_Verb) { 100 return i; 101 } 102 } 103 return verbCount; // path is all move verbs 104 } 105 AddGenIDChangeListener(const SkPath & path,sk_sp<SkIDChangeListener> listener)106 static void AddGenIDChangeListener(const SkPath& path, sk_sp<SkIDChangeListener> listener) { 107 path.fPathRef->addGenIDChangeListener(std::move(listener)); 108 } 109 110 /** 111 * This returns true for a rect that has a move followed by 3 or 4 lines and a close. If 112 * 'isSimpleFill' is true, an uncloseed rect will also be accepted as long as it starts and 113 * ends at the same corner. This does not permit degenerate line or point rectangles. 114 */ 115 static bool IsSimpleRect(const SkPath& path, bool isSimpleFill, SkRect* rect, 116 SkPathDirection* direction, unsigned* start); 117 118 /** 119 * Creates a path from arc params using the semantics of SkCanvas::drawArc. This function 120 * assumes empty ovals and zero sweeps have already been filtered out. 121 */ 122 static void CreateDrawArcPath(SkPath* path, const SkArc& arc, bool isFillNoPathEffect); 123 124 /** 125 * Determines whether an arc produced by CreateDrawArcPath will be convex. Assumes a non-empty 126 * oval. 127 */ 128 static bool DrawArcIsConvex(SkScalar sweepAngle, SkArc::Type arcType, bool isFillNoPathEffect); 129 ShrinkToFit(SkPath * path)130 static void ShrinkToFit(SkPath* path) { 131 path->shrinkToFit(); 132 } 133 134 /** 135 * Returns a C++11-iterable object that traverses a path's verbs in order. e.g: 136 * 137 * for (SkPath::Verb verb : SkPathPriv::Verbs(path)) { 138 * ... 139 * } 140 */ 141 struct Verbs { 142 public: VerbsVerbs143 Verbs(const SkPath& path) : fPathRef(path.fPathRef.get()) {} 144 struct Iter { 145 void operator++() { fVerb++; } 146 bool operator!=(const Iter& b) { return fVerb != b.fVerb; } 147 SkPath::Verb operator*() { return static_cast<SkPath::Verb>(*fVerb); } 148 const uint8_t* fVerb; 149 }; beginVerbs150 Iter begin() { return Iter{fPathRef->verbsBegin()}; } endVerbs151 Iter end() { return Iter{fPathRef->verbsEnd()}; } 152 private: 153 Verbs(const Verbs&) = delete; 154 Verbs& operator=(const Verbs&) = delete; 155 SkPathRef* fPathRef; 156 }; 157 158 /** 159 * Iterates through a raw range of path verbs, points, and conics. All values are returned 160 * unaltered. 161 * 162 * NOTE: This class's definition will be moved into SkPathPriv once RangeIter is removed. 163 */ 164 using RangeIter = SkPath::RangeIter; 165 166 /** 167 * Iterable object for traversing verbs, points, and conic weights in a path: 168 * 169 * for (auto [verb, pts, weights] : SkPathPriv::Iterate(skPath)) { 170 * ... 171 * } 172 */ 173 struct Iterate { 174 public: IterateIterate175 Iterate(const SkPath& path) 176 : Iterate(path.fPathRef->verbsBegin(), 177 // Don't allow iteration through non-finite points. 178 (!path.isFinite()) ? path.fPathRef->verbsBegin() 179 : path.fPathRef->verbsEnd(), 180 path.fPathRef->points(), path.fPathRef->conicWeights()) { 181 } IterateIterate182 Iterate(const uint8_t* verbsBegin, const uint8_t* verbsEnd, const SkPoint* points, 183 const SkScalar* weights) 184 : fVerbsBegin(verbsBegin), fVerbsEnd(verbsEnd), fPoints(points), fWeights(weights) { 185 } beginIterate186 SkPath::RangeIter begin() { return {fVerbsBegin, fPoints, fWeights}; } endIterate187 SkPath::RangeIter end() { return {fVerbsEnd, nullptr, nullptr}; } 188 private: 189 const uint8_t* fVerbsBegin; 190 const uint8_t* fVerbsEnd; 191 const SkPoint* fPoints; 192 const SkScalar* fWeights; 193 }; 194 195 /** 196 * Returns a pointer to the verb data. 197 */ VerbData(const SkPath & path)198 static const uint8_t* VerbData(const SkPath& path) { 199 return path.fPathRef->verbsBegin(); 200 } 201 202 /** Returns a raw pointer to the path points */ PointData(const SkPath & path)203 static const SkPoint* PointData(const SkPath& path) { 204 return path.fPathRef->points(); 205 } 206 207 /** Returns the number of conic weights in the path */ ConicWeightCnt(const SkPath & path)208 static int ConicWeightCnt(const SkPath& path) { 209 return path.fPathRef->countWeights(); 210 } 211 212 /** Returns a raw pointer to the path conic weights. */ ConicWeightData(const SkPath & path)213 static const SkScalar* ConicWeightData(const SkPath& path) { 214 return path.fPathRef->conicWeights(); 215 } 216 217 /** Returns true if the underlying SkPathRef has one single owner. */ TestingOnly_unique(const SkPath & path)218 static bool TestingOnly_unique(const SkPath& path) { 219 return path.fPathRef->unique(); 220 } 221 222 // Won't be needed once we can make path's immutable (with their bounds always computed) HasComputedBounds(const SkPath & path)223 static bool HasComputedBounds(const SkPath& path) { 224 return path.hasComputedBounds(); 225 } 226 227 /** Returns true if constructed by addCircle(), addOval(); and in some cases, 228 addRoundRect(), addRRect(). SkPath constructed with conicTo() or rConicTo() will not 229 return true though SkPath draws oval. 230 231 rect receives bounds of oval. 232 dir receives SkPathDirection of oval: kCW_Direction if clockwise, kCCW_Direction if 233 counterclockwise. 234 start receives start of oval: 0 for top, 1 for right, 2 for bottom, 3 for left. 235 236 rect, dir, and start are unmodified if oval is not found. 237 238 Triggers performance optimizations on some GPU surface implementations. 239 240 @param rect storage for bounding SkRect of oval; may be nullptr 241 @param dir storage for SkPathDirection; may be nullptr 242 @param start storage for start of oval; may be nullptr 243 @return true if SkPath was constructed by method that reduces to oval 244 */ IsOval(const SkPath & path,SkRect * rect,SkPathDirection * dir,unsigned * start)245 static bool IsOval(const SkPath& path, SkRect* rect, SkPathDirection* dir, unsigned* start) { 246 bool isCCW = false; 247 bool result = path.fPathRef->isOval(rect, &isCCW, start); 248 if (dir && result) { 249 *dir = isCCW ? SkPathDirection::kCCW : SkPathDirection::kCW; 250 } 251 return result; 252 } 253 254 /** Returns true if constructed by addRoundRect(), addRRect(); and if construction 255 is not empty, not SkRect, and not oval. SkPath constructed with other calls 256 will not return true though SkPath draws SkRRect. 257 258 rrect receives bounds of SkRRect. 259 dir receives SkPathDirection of oval: kCW_Direction if clockwise, kCCW_Direction if 260 counterclockwise. 261 start receives start of SkRRect: 0 for top, 1 for right, 2 for bottom, 3 for left. 262 263 rrect, dir, and start are unmodified if SkRRect is not found. 264 265 Triggers performance optimizations on some GPU surface implementations. 266 267 @param rrect storage for bounding SkRect of SkRRect; may be nullptr 268 @param dir storage for SkPathDirection; may be nullptr 269 @param start storage for start of SkRRect; may be nullptr 270 @return true if SkPath contains only SkRRect 271 */ IsRRect(const SkPath & path,SkRRect * rrect,SkPathDirection * dir,unsigned * start)272 static bool IsRRect(const SkPath& path, SkRRect* rrect, SkPathDirection* dir, 273 unsigned* start) { 274 bool isCCW = false; 275 bool result = path.fPathRef->isRRect(rrect, &isCCW, start); 276 if (dir && result) { 277 *dir = isCCW ? SkPathDirection::kCCW : SkPathDirection::kCW; 278 } 279 return result; 280 } 281 282 /** 283 * Sometimes in the drawing pipeline, we have to perform math on path coordinates, even after 284 * the path is in device-coordinates. Tessellation and clipping are two examples. Usually this 285 * is pretty modest, but it can involve subtracting/adding coordinates, or multiplying by 286 * small constants (e.g. 2,3,4). To try to preflight issues where these optionations could turn 287 * finite path values into infinities (or NaNs), we allow the upper drawing code to reject 288 * the path if its bounds (in device coordinates) is too close to max float. 289 */ TooBigForMath(const SkRect & bounds)290 static bool TooBigForMath(const SkRect& bounds) { 291 // This value is just a guess. smaller is safer, but we don't want to reject largish paths 292 // that we don't have to. 293 constexpr SkScalar scale_down_to_allow_for_small_multiplies = 0.25f; 294 constexpr SkScalar max = SK_ScalarMax * scale_down_to_allow_for_small_multiplies; 295 296 // use ! expression so we return true if bounds contains NaN 297 return !(bounds.fLeft >= -max && bounds.fTop >= -max && 298 bounds.fRight <= max && bounds.fBottom <= max); 299 } TooBigForMath(const SkPath & path)300 static bool TooBigForMath(const SkPath& path) { 301 return TooBigForMath(path.getBounds()); 302 } 303 304 // Returns number of valid points for each SkPath::Iter verb PtsInIter(unsigned verb)305 static int PtsInIter(unsigned verb) { 306 static const uint8_t gPtsInVerb[] = { 307 1, // kMove pts[0] 308 2, // kLine pts[0..1] 309 3, // kQuad pts[0..2] 310 3, // kConic pts[0..2] 311 4, // kCubic pts[0..3] 312 0, // kClose 313 0 // kDone 314 }; 315 316 SkASSERT(verb < std::size(gPtsInVerb)); 317 return gPtsInVerb[verb]; 318 } 319 320 // Returns number of valid points for each verb, not including the "starter" 321 // point that the Iterator adds for line/quad/conic/cubic PtsInVerb(unsigned verb)322 static int PtsInVerb(unsigned verb) { 323 static const uint8_t gPtsInVerb[] = { 324 1, // kMove pts[0] 325 1, // kLine pts[0..1] 326 2, // kQuad pts[0..2] 327 2, // kConic pts[0..2] 328 3, // kCubic pts[0..3] 329 0, // kClose 330 0 // kDone 331 }; 332 333 SkASSERT(verb < std::size(gPtsInVerb)); 334 return gPtsInVerb[verb]; 335 } 336 337 static bool IsAxisAligned(const SkPath& path); 338 AllPointsEq(const SkPoint pts[],int count)339 static bool AllPointsEq(const SkPoint pts[], int count) { 340 for (int i = 1; i < count; ++i) { 341 if (pts[0] != pts[i]) { 342 return false; 343 } 344 } 345 return true; 346 } 347 LastMoveToIndex(const SkPath & path)348 static int LastMoveToIndex(const SkPath& path) { return path.fLastMoveToIndex; } 349 350 static bool IsRectContour(const SkPath&, bool allowPartial, int* currVerb, 351 const SkPoint** ptsPtr, bool* isClosed, SkPathDirection* direction, 352 SkRect* rect); 353 354 /** Returns true if SkPath is equivalent to nested SkRect pair when filled. 355 If false, rect and dirs are unchanged. 356 If true, rect and dirs are written to if not nullptr: 357 setting rect[0] to outer SkRect, and rect[1] to inner SkRect; 358 setting dirs[0] to SkPathDirection of outer SkRect, and dirs[1] to SkPathDirection of 359 inner SkRect. 360 361 @param rect storage for SkRect pair; may be nullptr 362 @param dirs storage for SkPathDirection pair; may be nullptr 363 @return true if SkPath contains nested SkRect pair 364 */ 365 static bool IsNestedFillRects(const SkPath&, SkRect rect[2], 366 SkPathDirection dirs[2] = nullptr); 367 IsInverseFillType(SkPathFillType fill)368 static bool IsInverseFillType(SkPathFillType fill) { 369 return (static_cast<int>(fill) & 2) != 0; 370 } 371 372 /** Returns equivalent SkPath::FillType representing SkPath fill inside its bounds. 373 . 374 375 @param fill one of: kWinding_FillType, kEvenOdd_FillType, 376 kInverseWinding_FillType, kInverseEvenOdd_FillType 377 @return fill, or kWinding_FillType or kEvenOdd_FillType if fill is inverted 378 */ ConvertToNonInverseFillType(SkPathFillType fill)379 static SkPathFillType ConvertToNonInverseFillType(SkPathFillType fill) { 380 return (SkPathFillType)(static_cast<int>(fill) & 1); 381 } 382 383 /** 384 * If needed (to not blow-up under a perspective matrix), clip the path, returning the 385 * answer in "result", and return true. 386 * 387 * Note result might be empty (if the path was completely clipped out). 388 * 389 * If no clipping is needed, returns false and "result" is left unchanged. 390 */ 391 static bool PerspectiveClip(const SkPath& src, const SkMatrix&, SkPath* result); 392 393 /** 394 * Gets the number of GenIDChangeListeners. If another thread has access to this path then 395 * this may be stale before return and only indicates that the count was the return value 396 * at some point during the execution of the function. 397 */ 398 static int GenIDChangeListenersCount(const SkPath&); 399 UpdatePathPoint(SkPath * path,int index,const SkPoint & pt)400 static void UpdatePathPoint(SkPath* path, int index, const SkPoint& pt) { 401 SkASSERT(index < path->countPoints()); 402 SkPathRef::Editor ed(&path->fPathRef); 403 ed.writablePoints()[index] = pt; 404 path->dirtyAfterEdit(); 405 } 406 GetConvexity(const SkPath & path)407 static SkPathConvexity GetConvexity(const SkPath& path) { 408 return path.getConvexity(); 409 } GetConvexityOrUnknown(const SkPath & path)410 static SkPathConvexity GetConvexityOrUnknown(const SkPath& path) { 411 return path.getConvexityOrUnknown(); 412 } SetConvexity(const SkPath & path,SkPathConvexity c)413 static void SetConvexity(const SkPath& path, SkPathConvexity c) { 414 path.setConvexity(c); 415 } ForceComputeConvexity(const SkPath & path)416 static void ForceComputeConvexity(const SkPath& path) { 417 path.setConvexity(SkPathConvexity::kUnknown); 418 (void)path.isConvex(); 419 } 420 ReverseAddPath(SkPathBuilder * builder,const SkPath & reverseMe)421 static void ReverseAddPath(SkPathBuilder* builder, const SkPath& reverseMe) { 422 builder->privateReverseAddPath(reverseMe); 423 } 424 MakePath(const SkPathVerbAnalysis & analysis,const SkPoint points[],const uint8_t verbs[],int verbCount,const SkScalar conics[],SkPathFillType fillType,bool isVolatile)425 static SkPath MakePath(const SkPathVerbAnalysis& analysis, 426 const SkPoint points[], 427 const uint8_t verbs[], 428 int verbCount, 429 const SkScalar conics[], 430 SkPathFillType fillType, 431 bool isVolatile) { 432 return SkPath::MakeInternal(analysis, points, verbs, verbCount, conics, fillType, 433 isVolatile); 434 } 435 }; 436 437 // Lightweight variant of SkPath::Iter that only returns segments (e.g. lines/conics). 438 // Does not return kMove or kClose. 439 // Always "auto-closes" each contour. 440 // Roughly the same as SkPath::Iter(path, true), but does not return moves or closes 441 // 442 class SkPathEdgeIter { 443 const uint8_t* fVerbs; 444 const uint8_t* fVerbsStop; 445 const SkPoint* fPts; 446 const SkPoint* fMoveToPtr; 447 const SkScalar* fConicWeights; 448 SkPoint fScratch[2]; // for auto-close lines 449 bool fNeedsCloseLine; 450 bool fNextIsNewContour; 451 SkDEBUGCODE(bool fIsConic;) 452 453 enum { 454 kIllegalEdgeValue = 99 455 }; 456 457 public: 458 SkPathEdgeIter(const SkPath& path); 459 conicWeight()460 SkScalar conicWeight() const { 461 SkASSERT(fIsConic); 462 return *fConicWeights; 463 } 464 465 enum class Edge { 466 kLine = SkPath::kLine_Verb, 467 kQuad = SkPath::kQuad_Verb, 468 kConic = SkPath::kConic_Verb, 469 kCubic = SkPath::kCubic_Verb, 470 }; 471 EdgeToVerb(Edge e)472 static SkPath::Verb EdgeToVerb(Edge e) { 473 return SkPath::Verb(e); 474 } 475 476 struct Result { 477 const SkPoint* fPts; // points for the segment, or null if done 478 Edge fEdge; 479 bool fIsNewContour; 480 481 // Returns true when it holds an Edge, false when the path is done. 482 explicit operator bool() { return fPts != nullptr; } 483 }; 484 next()485 Result next() { 486 auto closeline = [&]() { 487 fScratch[0] = fPts[-1]; 488 fScratch[1] = *fMoveToPtr; 489 fNeedsCloseLine = false; 490 fNextIsNewContour = true; 491 return Result{ fScratch, Edge::kLine, false }; 492 }; 493 494 for (;;) { 495 SkASSERT(fVerbs <= fVerbsStop); 496 if (fVerbs == fVerbsStop) { 497 return fNeedsCloseLine 498 ? closeline() 499 : Result{ nullptr, Edge(kIllegalEdgeValue), false }; 500 } 501 502 SkDEBUGCODE(fIsConic = false;) 503 504 const auto v = *fVerbs++; 505 switch (v) { 506 case SkPath::kMove_Verb: { 507 if (fNeedsCloseLine) { 508 auto res = closeline(); 509 fMoveToPtr = fPts++; 510 return res; 511 } 512 fMoveToPtr = fPts++; 513 fNextIsNewContour = true; 514 } break; 515 case SkPath::kClose_Verb: 516 if (fNeedsCloseLine) return closeline(); 517 break; 518 default: { 519 // Actual edge. 520 const int pts_count = (v+2) / 2, 521 cws_count = (v & (v-1)) / 2; 522 SkASSERT(pts_count == SkPathPriv::PtsInIter(v) - 1); 523 524 fNeedsCloseLine = true; 525 fPts += pts_count; 526 fConicWeights += cws_count; 527 528 SkDEBUGCODE(fIsConic = (v == SkPath::kConic_Verb);) 529 SkASSERT(fIsConic == (cws_count > 0)); 530 531 bool isNewContour = fNextIsNewContour; 532 fNextIsNewContour = false; 533 return { &fPts[-(pts_count + 1)], Edge(v), isNewContour }; 534 } 535 } 536 } 537 } 538 }; 539 540 #endif 541