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