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