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/SkPath.h" 12 13 class SkPathPriv { 14 public: 15 #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK 16 static const int kPathRefGenIDBitCnt = 30; // leave room for the fill type (skbug.com/1762) 17 #else 18 static const int kPathRefGenIDBitCnt = 32; 19 #endif 20 21 enum FirstDirection : int { 22 kCW_FirstDirection, // == SkPath::kCW_Direction 23 kCCW_FirstDirection, // == SkPath::kCCW_Direction 24 kUnknown_FirstDirection, 25 }; 26 AsFirstDirection(SkPath::Direction dir)27 static FirstDirection AsFirstDirection(SkPath::Direction dir) { 28 // since we agree numerically for the values in Direction, we can just cast. 29 return (FirstDirection)dir; 30 } 31 32 /** 33 * Return the opposite of the specified direction. kUnknown is its own 34 * opposite. 35 */ OppositeFirstDirection(FirstDirection dir)36 static FirstDirection OppositeFirstDirection(FirstDirection dir) { 37 static const FirstDirection gOppositeDir[] = { 38 kCCW_FirstDirection, kCW_FirstDirection, kUnknown_FirstDirection, 39 }; 40 return gOppositeDir[dir]; 41 } 42 43 /** 44 * Tries to quickly compute the direction of the first non-degenerate 45 * contour. If it can be computed, return true and set dir to that 46 * direction. If it cannot be (quickly) determined, return false and ignore 47 * the dir parameter. If the direction was determined, it is cached to make 48 * subsequent calls return quickly. 49 */ 50 static bool CheapComputeFirstDirection(const SkPath&, FirstDirection* dir); 51 52 /** 53 * Returns true if the path's direction can be computed via 54 * cheapComputDirection() and if that computed direction matches the 55 * specified direction. If dir is kUnknown, returns true if the direction 56 * cannot be computed. 57 */ CheapIsFirstDirection(const SkPath & path,FirstDirection dir)58 static bool CheapIsFirstDirection(const SkPath& path, FirstDirection dir) { 59 FirstDirection computedDir = kUnknown_FirstDirection; 60 (void)CheapComputeFirstDirection(path, &computedDir); 61 return computedDir == dir; 62 } 63 IsClosedSingleContour(const SkPath & path)64 static bool IsClosedSingleContour(const SkPath& path) { 65 int verbCount = path.countVerbs(); 66 if (verbCount == 0) 67 return false; 68 int moveCount = 0; 69 auto verbs = path.fPathRef->verbsBegin(); 70 for (int i = 0; i < verbCount; i++) { 71 switch (verbs[i]) { 72 case SkPath::Verb::kMove_Verb: 73 moveCount += 1; 74 if (moveCount > 1) { 75 return false; 76 } 77 break; 78 case SkPath::Verb::kClose_Verb: 79 if (i == verbCount - 1) { 80 return true; 81 } 82 return false; 83 default: break; 84 } 85 } 86 return false; 87 } 88 AddGenIDChangeListener(const SkPath & path,sk_sp<SkPathRef::GenIDChangeListener> listener)89 static void AddGenIDChangeListener(const SkPath& path, 90 sk_sp<SkPathRef::GenIDChangeListener> listener) { 91 path.fPathRef->addGenIDChangeListener(std::move(listener)); 92 } 93 94 /** 95 * This returns true for a rect that begins and ends at the same corner and has either a move 96 * followed by four lines or a move followed by 3 lines and a close. None of the parameters are 97 * optional. This does not permit degenerate line or point rectangles. 98 */ 99 static bool IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction, 100 unsigned* start); 101 102 /** 103 * Creates a path from arc params using the semantics of SkCanvas::drawArc. This function 104 * assumes empty ovals and zero sweeps have already been filtered out. 105 */ 106 static void CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle, 107 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect); 108 109 /** 110 * Determines whether an arc produced by CreateDrawArcPath will be convex. Assumes a non-empty 111 * oval. 112 */ 113 static bool DrawArcIsConvex(SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect); 114 115 /** 116 * Returns a C++11-iterable object that traverses a path's verbs in order. e.g: 117 * 118 * for (SkPath::Verb verb : SkPathPriv::Verbs(path)) { 119 * ... 120 * } 121 */ 122 struct Verbs { 123 public: VerbsVerbs124 Verbs(const SkPath& path) : fPathRef(path.fPathRef.get()) {} 125 struct Iter { 126 void operator++() { fVerb++; } 127 bool operator!=(const Iter& b) { return fVerb != b.fVerb; } 128 SkPath::Verb operator*() { return static_cast<SkPath::Verb>(*fVerb); } 129 const uint8_t* fVerb; 130 }; beginVerbs131 Iter begin() { return Iter{fPathRef->verbsBegin()}; } endVerbs132 Iter end() { return Iter{fPathRef->verbsEnd()}; } 133 private: 134 Verbs(const Verbs&) = delete; 135 Verbs& operator=(const Verbs&) = delete; 136 SkPathRef* fPathRef; 137 }; 138 139 /** 140 * Returns a pointer to the verb data. 141 */ VerbData(const SkPath & path)142 static const uint8_t* VerbData(const SkPath& path) { 143 return path.fPathRef->verbsBegin(); 144 } 145 146 /** Returns a raw pointer to the path points */ PointData(const SkPath & path)147 static const SkPoint* PointData(const SkPath& path) { 148 return path.fPathRef->points(); 149 } 150 151 /** Returns the number of conic weights in the path */ ConicWeightCnt(const SkPath & path)152 static int ConicWeightCnt(const SkPath& path) { 153 return path.fPathRef->countWeights(); 154 } 155 156 /** Returns a raw pointer to the path conic weights. */ ConicWeightData(const SkPath & path)157 static const SkScalar* ConicWeightData(const SkPath& path) { 158 return path.fPathRef->conicWeights(); 159 } 160 161 #ifndef SK_LEGACY_PATH_CONVEXITY 162 /** Returns true if path formed by pts is convex. 163 164 @param pts SkPoint array of path 165 @param count number of entries in array 166 167 @return true if pts represent a convex geometry 168 */ 169 static bool IsConvex(const SkPoint pts[], int count); 170 #endif 171 172 /** Returns true if the underlying SkPathRef has one single owner. */ TestingOnly_unique(const SkPath & path)173 static bool TestingOnly_unique(const SkPath& path) { 174 return path.fPathRef->unique(); 175 } 176 177 /** Returns true if constructed by addCircle(), addOval(); and in some cases, 178 addRoundRect(), addRRect(). SkPath constructed with conicTo() or rConicTo() will not 179 return true though SkPath draws oval. 180 181 rect receives bounds of oval. 182 dir receives SkPath::Direction of oval: kCW_Direction if clockwise, kCCW_Direction if 183 counterclockwise. 184 start receives start of oval: 0 for top, 1 for right, 2 for bottom, 3 for left. 185 186 rect, dir, and start are unmodified if oval is not found. 187 188 Triggers performance optimizations on some GPU surface implementations. 189 190 @param rect storage for bounding SkRect of oval; may be nullptr 191 @param dir storage for SkPath::Direction; may be nullptr 192 @param start storage for start of oval; may be nullptr 193 @return true if SkPath was constructed by method that reduces to oval 194 */ IsOval(const SkPath & path,SkRect * rect,SkPath::Direction * dir,unsigned * start)195 static bool IsOval(const SkPath& path, SkRect* rect, SkPath::Direction* dir, unsigned* start) { 196 bool isCCW = false; 197 bool result = path.fPathRef->isOval(rect, &isCCW, start); 198 if (dir && result) { 199 *dir = isCCW ? SkPath::kCCW_Direction : SkPath::kCW_Direction; 200 } 201 return result; 202 } 203 204 /** Returns true if constructed by addRoundRect(), addRRect(); and if construction 205 is not empty, not SkRect, and not oval. SkPath constructed with other calls 206 will not return true though SkPath draws SkRRect. 207 208 rrect receives bounds of SkRRect. 209 dir receives SkPath::Direction of oval: kCW_Direction if clockwise, kCCW_Direction if 210 counterclockwise. 211 start receives start of SkRRect: 0 for top, 1 for right, 2 for bottom, 3 for left. 212 213 rrect, dir, and start are unmodified if SkRRect is not found. 214 215 Triggers performance optimizations on some GPU surface implementations. 216 217 @param rrect storage for bounding SkRect of SkRRect; may be nullptr 218 @param dir storage for SkPath::Direction; may be nullptr 219 @param start storage for start of SkRRect; may be nullptr 220 @return true if SkPath contains only SkRRect 221 */ IsRRect(const SkPath & path,SkRRect * rrect,SkPath::Direction * dir,unsigned * start)222 static bool IsRRect(const SkPath& path, SkRRect* rrect, SkPath::Direction* dir, 223 unsigned* start) { 224 bool isCCW = false; 225 bool result = path.fPathRef->isRRect(rrect, &isCCW, start); 226 if (dir && result) { 227 *dir = isCCW ? SkPath::kCCW_Direction : SkPath::kCW_Direction; 228 } 229 return result; 230 } 231 232 /** 233 * Sometimes in the drawing pipeline, we have to perform math on path coordinates, even after 234 * the path is in device-coordinates. Tessellation and clipping are two examples. Usually this 235 * is pretty modest, but it can involve subtracting/adding coordinates, or multiplying by 236 * small constants (e.g. 2,3,4). To try to preflight issues where these optionations could turn 237 * finite path values into infinities (or NaNs), we allow the upper drawing code to reject 238 * the path if its bounds (in device coordinates) is too close to max float. 239 */ TooBigForMath(const SkRect & bounds)240 static bool TooBigForMath(const SkRect& bounds) { 241 // This value is just a guess. smaller is safer, but we don't want to reject largish paths 242 // that we don't have to. 243 constexpr SkScalar scale_down_to_allow_for_small_multiplies = 0.25f; 244 constexpr SkScalar max = SK_ScalarMax * scale_down_to_allow_for_small_multiplies; 245 246 // use ! expression so we return true if bounds contains NaN 247 return !(bounds.fLeft >= -max && bounds.fTop >= -max && 248 bounds.fRight <= max && bounds.fBottom <= max); 249 } TooBigForMath(const SkPath & path)250 static bool TooBigForMath(const SkPath& path) { 251 return TooBigForMath(path.getBounds()); 252 } 253 254 // Returns number of valid points for each SkPath::Iter verb PtsInIter(unsigned verb)255 static int PtsInIter(unsigned verb) { 256 static const uint8_t gPtsInVerb[] = { 257 1, // kMove pts[0] 258 2, // kLine pts[0..1] 259 3, // kQuad pts[0..2] 260 3, // kConic pts[0..2] 261 4, // kCubic pts[0..3] 262 0, // kClose 263 0 // kDone 264 }; 265 266 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb)); 267 return gPtsInVerb[verb]; 268 } 269 IsAxisAligned(const SkPath & path)270 static bool IsAxisAligned(const SkPath& path) { 271 SkRect tmp; 272 return (path.fPathRef->fIsRRect | path.fPathRef->fIsOval) || path.isRect(&tmp); 273 } 274 AllPointsEq(const SkPoint pts[],int count)275 static bool AllPointsEq(const SkPoint pts[], int count) { 276 for (int i = 1; i < count; ++i) { 277 if (pts[0] != pts[i]) { 278 return false; 279 } 280 } 281 return true; 282 } 283 284 static bool IsRectContour(const SkPath&, bool allowPartial, int* currVerb, 285 const SkPoint** ptsPtr, bool* isClosed, SkPath::Direction* direction, 286 SkRect* rect); 287 288 /** Returns true if SkPath is equivalent to nested SkRect pair when filled. 289 If false, rect and dirs are unchanged. 290 If true, rect and dirs are written to if not nullptr: 291 setting rect[0] to outer SkRect, and rect[1] to inner SkRect; 292 setting dirs[0] to SkPath::Direction of outer SkRect, and dirs[1] to SkPath::Direction of 293 inner SkRect. 294 295 @param rect storage for SkRect pair; may be nullptr 296 @param dirs storage for SkPath::Direction pair; may be nullptr 297 @return true if SkPath contains nested SkRect pair 298 */ 299 static bool IsNestedFillRects(const SkPath&, SkRect rect[2], 300 SkPath::Direction dirs[2] = nullptr); 301 }; 302 303 // Lightweight variant of SkPath::Iter that only returns segments (e.g. lines/conics). 304 // Does not return kMove or kClose. 305 // Always "auto-closes" each contour. 306 // Roughly the same as SkPath::Iter(path, true), but does not return moves or closes 307 // 308 class SkPathEdgeIter { 309 const uint8_t* fVerbs; 310 const uint8_t* fVerbsStop; 311 const SkPoint* fPts; 312 const SkPoint* fMoveToPtr; 313 const SkScalar* fConicWeights; 314 SkPoint fScratch[2]; // for auto-close lines 315 bool fNeedsCloseLine; 316 SkDEBUGCODE(bool fIsConic); 317 318 enum { 319 kIllegalEdgeValue = 99 320 }; 321 322 public: 323 SkPathEdgeIter(const SkPath& path); 324 conicWeight()325 SkScalar conicWeight() const { 326 SkASSERT(fIsConic); 327 return *fConicWeights; 328 } 329 330 enum class Edge { 331 kLine = SkPath::kLine_Verb, 332 kQuad = SkPath::kQuad_Verb, 333 kConic = SkPath::kConic_Verb, 334 kCubic = SkPath::kCubic_Verb, 335 }; 336 EdgeToVerb(Edge e)337 static SkPath::Verb EdgeToVerb(Edge e) { 338 return SkPath::Verb(e); 339 } 340 341 struct Result { 342 const SkPoint* fPts; // points for the segment, or null if done 343 Edge fEdge; 344 345 // Returns true when it holds an Edge, false when the path is done. 346 MOZ_IMPLICIT operator bool() { return fPts != nullptr; } 347 }; 348 next()349 Result next() { 350 auto closeline = [&]() { 351 fScratch[0] = fPts[-1]; 352 fScratch[1] = *fMoveToPtr; 353 fNeedsCloseLine = false; 354 return Result{ fScratch, Edge::kLine }; 355 }; 356 357 for (;;) { 358 SkASSERT(fVerbs <= fVerbsStop); 359 if (fVerbs == fVerbsStop) { 360 return fNeedsCloseLine 361 ? closeline() 362 : Result{ nullptr, Edge(kIllegalEdgeValue) }; 363 } 364 365 SkDEBUGCODE(fIsConic = false;) 366 367 const auto v = *fVerbs++; 368 switch (v) { 369 case SkPath::kMove_Verb: { 370 if (fNeedsCloseLine) { 371 auto res = closeline(); 372 fMoveToPtr = fPts++; 373 return res; 374 } 375 fMoveToPtr = fPts++; 376 } break; 377 case SkPath::kClose_Verb: 378 if (fNeedsCloseLine) return closeline(); 379 break; 380 default: { 381 // Actual edge. 382 const int pts_count = (v+2) / 2, 383 cws_count = (v & (v-1)) / 2; 384 SkASSERT(pts_count == SkPathPriv::PtsInIter(v) - 1); 385 386 fNeedsCloseLine = true; 387 fPts += pts_count; 388 fConicWeights += cws_count; 389 390 SkDEBUGCODE(fIsConic = (v == SkPath::kConic_Verb);) 391 SkASSERT(fIsConic == (cws_count > 0)); 392 393 return { &fPts[-(pts_count + 1)], Edge(v) }; 394 } 395 } 396 } 397 } 398 }; 399 400 #endif 401