• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2016 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 GrShape_DEFINED
9 #define GrShape_DEFINED
10 
11 #include "GrStyle.h"
12 #include "SkPath.h"
13 #include "SkPathPriv.h"
14 #include "SkRRect.h"
15 #include "SkTemplates.h"
16 #include "SkTLazy.h"
17 
18 /**
19  * Represents a geometric shape (rrect or path) and the GrStyle that it should be rendered with.
20  * It is possible to apply the style to the GrShape to produce a new GrShape where the geometry
21  * reflects the styling information (e.g. is stroked). It is also possible to apply just the
22  * path effect from the style. In this case the resulting shape will include any remaining
23  * stroking information that is to be applied after the path effect.
24  *
25  * Shapes can produce keys that represent only the geometry information, not the style. Note that
26  * when styling information is applied to produce a new shape then the style has been converted
27  * to geometric information and is included in the new shape's key. When the same style is applied
28  * to two shapes that reflect the same underlying geometry the computed keys of the stylized shapes
29  * will be the same.
30  *
31  * Currently this can only be constructed from a path, rect, or rrect though it can become a path
32  * applying style to the geometry. The idea is to expand this to cover most or all of the geometries
33  * that have SkCanvas::draw APIs.
34  */
35 class GrShape {
36 public:
37     // Keys for paths may be extracted from the path data for small paths. Clients aren't supposed
38     // to have to worry about this. This value is exposed for unit tests.
39     static constexpr int kMaxKeyFromDataVerbCnt = 10;
40 
GrShape()41     GrShape() { this->initType(Type::kEmpty); }
42 
GrShape(const SkPath & path)43     explicit GrShape(const SkPath& path) : GrShape(path, GrStyle::SimpleFill()) {}
44 
GrShape(const SkRRect & rrect)45     explicit GrShape(const SkRRect& rrect) : GrShape(rrect, GrStyle::SimpleFill()) {}
46 
GrShape(const SkRect & rect)47     explicit GrShape(const SkRect& rect) : GrShape(rect, GrStyle::SimpleFill()) {}
48 
GrShape(const SkPath & path,const GrStyle & style)49     GrShape(const SkPath& path, const GrStyle& style) : fStyle(style) {
50         this->initType(Type::kPath, &path);
51         this->attemptToSimplifyPath();
52     }
53 
GrShape(const SkRRect & rrect,const GrStyle & style)54     GrShape(const SkRRect& rrect, const GrStyle& style)
55         : fStyle(style) {
56         this->initType(Type::kRRect);
57         fRRectData.fRRect = rrect;
58         fRRectData.fInverted = false;
59         fRRectData.fStart = DefaultRRectDirAndStartIndex(rrect, style.hasPathEffect(),
60                                                          &fRRectData.fDir);
61         this->attemptToSimplifyRRect();
62     }
63 
GrShape(const SkRRect & rrect,SkPath::Direction dir,unsigned start,bool inverted,const GrStyle & style)64     GrShape(const SkRRect& rrect, SkPath::Direction dir, unsigned start, bool inverted,
65             const GrStyle& style)
66         : fStyle(style) {
67         this->initType(Type::kRRect);
68         fRRectData.fRRect = rrect;
69         fRRectData.fInverted = inverted;
70         if (style.pathEffect()) {
71             fRRectData.fDir = dir;
72             fRRectData.fStart = start;
73             if (fRRectData.fRRect.getType() == SkRRect::kRect_Type) {
74                 fRRectData.fStart = (fRRectData.fStart + 1) & 0b110;
75             } else if (fRRectData.fRRect.getType() == SkRRect::kOval_Type) {
76                 fRRectData.fStart &= 0b110;
77             }
78         } else {
79             fRRectData.fStart = DefaultRRectDirAndStartIndex(rrect, false, &fRRectData.fDir);
80         }
81         this->attemptToSimplifyRRect();
82     }
83 
GrShape(const SkRect & rect,const GrStyle & style)84     GrShape(const SkRect& rect, const GrStyle& style)
85         : fStyle(style) {
86         this->initType(Type::kRRect);
87         fRRectData.fRRect = SkRRect::MakeRect(rect);
88         fRRectData.fInverted = false;
89         fRRectData.fStart = DefaultRectDirAndStartIndex(rect, style.hasPathEffect(),
90                                                         &fRRectData.fDir);
91         this->attemptToSimplifyRRect();
92     }
93 
GrShape(const SkPath & path,const SkPaint & paint)94     GrShape(const SkPath& path, const SkPaint& paint) : fStyle(paint) {
95         this->initType(Type::kPath, &path);
96         this->attemptToSimplifyPath();
97     }
98 
GrShape(const SkRRect & rrect,const SkPaint & paint)99     GrShape(const SkRRect& rrect, const SkPaint& paint)
100         : fStyle(paint) {
101         this->initType(Type::kRRect);
102         fRRectData.fRRect = rrect;
103         fRRectData.fInverted = false;
104         fRRectData.fStart = DefaultRRectDirAndStartIndex(rrect, fStyle.hasPathEffect(),
105                                                          &fRRectData.fDir);
106         this->attemptToSimplifyRRect();
107     }
108 
GrShape(const SkRect & rect,const SkPaint & paint)109     GrShape(const SkRect& rect, const SkPaint& paint)
110         : fStyle(paint) {
111         this->initType(Type::kRRect);
112         fRRectData.fRRect = SkRRect::MakeRect(rect);
113         fRRectData.fInverted = false;
114         fRRectData.fStart = DefaultRectDirAndStartIndex(rect, fStyle.hasPathEffect(),
115                                                         &fRRectData.fDir);
116         this->attemptToSimplifyRRect();
117     }
118 
119     GrShape(const GrShape&);
120     GrShape& operator=(const GrShape& that);
121 
~GrShape()122     ~GrShape() { this->changeType(Type::kEmpty); }
123 
style()124     const GrStyle& style() const { return fStyle; }
125 
126     /**
127      * Returns a shape that has either applied the path effect or path effect and stroking
128      * information from this shape's style to its geometry. Scale is used when approximating the
129      * output geometry and typically is computed from the view matrix
130      */
applyStyle(GrStyle::Apply apply,SkScalar scale)131     GrShape applyStyle(GrStyle::Apply apply, SkScalar scale) const {
132         return GrShape(*this, apply, scale);
133     }
134 
135     /** Returns the unstyled geometry as a rrect if possible. */
asRRect(SkRRect * rrect,SkPath::Direction * dir,unsigned * start,bool * inverted)136     bool asRRect(SkRRect* rrect, SkPath::Direction* dir, unsigned* start, bool* inverted) const {
137         if (Type::kRRect != fType) {
138             return false;
139         }
140         if (rrect) {
141             *rrect = fRRectData.fRRect;
142         }
143         if (dir) {
144             *dir = fRRectData.fDir;
145         }
146         if (start) {
147             *start = fRRectData.fStart;
148         }
149         if (inverted) {
150             *inverted = fRRectData.fInverted;
151         }
152         return true;
153     }
154 
155     /**
156      * If the unstyled shape is a straight line segment, returns true and sets pts to the endpoints.
157      * An inverse filled line path is still considered a line.
158      */
asLine(SkPoint pts[2],bool * inverted)159     bool asLine(SkPoint pts[2], bool* inverted) const {
160         if (fType != Type::kLine) {
161             return false;
162         }
163         if (pts) {
164             pts[0] = fLineData.fPts[0];
165             pts[1] = fLineData.fPts[1];
166         }
167         if (inverted) {
168             *inverted = fLineData.fInverted;
169         }
170         return true;
171     }
172 
173     /** Returns the unstyled geometry as a path. */
asPath(SkPath * out)174     void asPath(SkPath* out) const {
175         switch (fType) {
176             case Type::kEmpty:
177                 out->reset();
178                 break;
179             case Type::kRRect:
180                 out->reset();
181                 out->addRRect(fRRectData.fRRect, fRRectData.fDir, fRRectData.fStart);
182                 // Below matches the fill type that attemptToSimplifyPath uses.
183                 if (fRRectData.fInverted) {
184                     out->setFillType(kDefaultPathInverseFillType);
185                 } else {
186                     out->setFillType(kDefaultPathFillType);
187                 }
188                 break;
189             case Type::kLine:
190                 out->reset();
191                 out->moveTo(fLineData.fPts[0]);
192                 out->lineTo(fLineData.fPts[1]);
193                 if (fLineData.fInverted) {
194                     out->setFillType(kDefaultPathInverseFillType);
195                 } else {
196                     out->setFillType(kDefaultPathFillType);
197                 }
198                 break;
199             case Type::kPath:
200                 *out = this->path();
201                 break;
202         }
203     }
204 
205     /**
206      * Returns whether the geometry is empty. Note that applying the style could produce a
207      * non-empty shape.
208      */
isEmpty()209     bool isEmpty() const { return Type::kEmpty == fType; }
210 
211     /**
212      * Gets the bounds of the geometry without reflecting the shape's styling. This ignores
213      * the inverse fill nature of the geometry.
214      */
215     SkRect bounds() const;
216 
217     /**
218      * Gets the bounds of the geometry reflecting the shape's styling (ignoring inverse fill
219      * status).
220      */
221     SkRect styledBounds() const;
222 
223     /**
224      * Is this shape known to be convex, before styling is applied. An unclosed but otherwise
225      * convex path is considered to be closed if they styling reflects a fill and not otherwise.
226      * This is because filling closes all contours in the path.
227      */
knownToBeConvex()228     bool knownToBeConvex() const {
229         switch (fType) {
230             case Type::kEmpty:
231                 return true;
232             case Type::kRRect:
233                 return true;
234             case Type::kLine:
235                 return true;
236             case Type::kPath:
237                 // SkPath.isConvex() really means "is this path convex were it to be closed" and
238                 // thus doesn't give the correct answer for stroked paths, hence we also check
239                 // whether the path is either filled or closed. Convex paths may only have one
240                 // contour hence isLastContourClosed() is a sufficient for a convex path.
241                 return (this->style().isSimpleFill() || this->path().isLastContourClosed()) &&
242                         this->path().isConvex();
243         }
244         return false;
245     }
246 
247     /** Is the pre-styled geometry inverse filled? */
inverseFilled()248     bool inverseFilled() const {
249         bool ret = false;
250         switch (fType) {
251             case Type::kEmpty:
252                 ret = false;
253                 break;
254             case Type::kRRect:
255                 ret = fRRectData.fInverted;
256                 break;
257             case Type::kLine:
258                 ret = fLineData.fInverted;
259                 break;
260             case Type::kPath:
261                 ret = this->path().isInverseFillType();
262                 break;
263         }
264         // Dashing ignores inverseness. We should have caught this earlier. skbug.com/5421
265         SkASSERT(!(ret && this->style().isDashed()));
266         return ret;
267     }
268 
269     /**
270      * Might applying the styling to the geometry produce an inverse fill. The "may" part comes in
271      * because an arbitrary path effect could produce an inverse filled path. In other cases this
272      * can be thought of as "inverseFilledAfterStyling()".
273      */
mayBeInverseFilledAfterStyling()274     bool mayBeInverseFilledAfterStyling() const {
275          // An arbitrary path effect can produce an arbitrary output path, which may be inverse
276          // filled.
277         if (this->style().hasNonDashPathEffect()) {
278             return true;
279         }
280         return this->inverseFilled();
281     }
282 
283     /**
284      * Is it known that the unstyled geometry has no unclosed contours. This means that it will
285      * not have any caps if stroked (modulo the effect of any path effect).
286      */
knownToBeClosed()287     bool knownToBeClosed() const {
288         switch (fType) {
289             case Type::kEmpty:
290                 return true;
291             case Type::kRRect:
292                 return true;
293             case Type::kLine:
294                 return false;
295             case Type::kPath:
296                 // SkPath doesn't keep track of the closed status of each contour.
297                 return SkPathPriv::IsClosedSingleContour(this->path());
298         }
299         return false;
300     }
301 
segmentMask()302     uint32_t segmentMask() const {
303         switch (fType) {
304             case Type::kEmpty:
305                 return 0;
306             case Type::kRRect:
307                 if (fRRectData.fRRect.getType() == SkRRect::kOval_Type) {
308                     return SkPath::kConic_SegmentMask;
309                 } else if (fRRectData.fRRect.getType() == SkRRect::kRect_Type) {
310                     return SkPath::kLine_SegmentMask;
311                 }
312                 return SkPath::kLine_SegmentMask | SkPath::kConic_SegmentMask;
313             case Type::kLine:
314                 return SkPath::kLine_SegmentMask;
315             case Type::kPath:
316                 return this->path().getSegmentMasks();
317         }
318         return 0;
319     }
320 
321     /**
322      * Gets the size of the key for the shape represented by this GrShape (ignoring its styling).
323      * A negative value is returned if the shape has no key (shouldn't be cached).
324      */
325     int unstyledKeySize() const;
326 
hasUnstyledKey()327     bool hasUnstyledKey() const { return this->unstyledKeySize() >= 0; }
328 
329     /**
330      * Writes unstyledKeySize() bytes into the provided pointer. Assumes that there is enough
331      * space allocated for the key and that unstyledKeySize() does not return a negative value
332      * for this shape.
333      */
334     void writeUnstyledKey(uint32_t* key) const;
335 
336 private:
337     enum class Type {
338         kEmpty,
339         kRRect,
340         kLine,
341         kPath,
342     };
343 
344     void initType(Type type, const SkPath* path = nullptr) {
345         fType = Type::kEmpty;
346         this->changeType(type, path);
347     }
348 
349     void changeType(Type type, const SkPath* path = nullptr) {
350         bool wasPath = Type::kPath == fType;
351         fType = type;
352         bool isPath = Type::kPath == type;
353         SkASSERT(!path || isPath);
354         if (wasPath && !isPath) {
355             fPathData.fPath.~SkPath();
356         } else if (!wasPath && isPath) {
357             if (path) {
358                 new (&fPathData.fPath) SkPath(*path);
359             } else {
360                 new (&fPathData.fPath) SkPath();
361             }
362         } else if (isPath && path) {
363             fPathData.fPath = *path;
364         }
365         // Whether or not we use the path's gen ID is decided in attemptToSimplifyPath.
366         fPathData.fGenID = 0;
367     }
368 
path()369     SkPath& path() {
370         SkASSERT(Type::kPath == fType);
371         return fPathData.fPath;
372     }
373 
path()374     const SkPath& path() const {
375         SkASSERT(Type::kPath == fType);
376         return fPathData.fPath;
377     }
378 
379     /** Constructor used by the applyStyle() function */
380     GrShape(const GrShape& parentShape, GrStyle::Apply, SkScalar scale);
381 
382     /**
383      * Determines the key we should inherit from the input shape's geometry and style when
384      * we are applying the style to create a new shape.
385      */
386     void setInheritedKey(const GrShape& parentShape, GrStyle::Apply, SkScalar scale);
387 
388     void attemptToSimplifyPath();
389     void attemptToSimplifyRRect();
390     void attemptToSimplifyLine();
391 
392     // Defaults to use when there is no distinction between even/odd and winding fills.
393     static constexpr SkPath::FillType kDefaultPathFillType = SkPath::kEvenOdd_FillType;
394     static constexpr SkPath::FillType kDefaultPathInverseFillType =
395             SkPath::kInverseEvenOdd_FillType;
396 
397     static constexpr SkPath::Direction kDefaultRRectDir = SkPath::kCW_Direction;
398     static constexpr unsigned kDefaultRRectStart = 0;
399 
DefaultRectDirAndStartIndex(const SkRect & rect,bool hasPathEffect,SkPath::Direction * dir)400     static unsigned DefaultRectDirAndStartIndex(const SkRect& rect, bool hasPathEffect,
401                                                 SkPath::Direction* dir) {
402         *dir = kDefaultRRectDir;
403         // This comes from SkPath's interface. The default for adding a SkRect is counter clockwise
404         // beginning at index 0 (which happens to correspond to rrect index 0 or 7).
405         if (!hasPathEffect) {
406             // It doesn't matter what start we use, just be consistent to avoid redundant keys.
407             return kDefaultRRectStart;
408         }
409         // In SkPath a rect starts at index 0 by default. This is the top left corner. However,
410         // we store rects as rrects. RRects don't preserve the invertedness, but rather sort the
411         // rect edges. Thus, we may need to modify the rrect's start index to account for the sort.
412         bool swapX = rect.fLeft > rect.fRight;
413         bool swapY = rect.fTop > rect.fBottom;
414         if (swapX && swapY) {
415             // 0 becomes start index 2 and times 2 to convert from rect the rrect indices.
416             return 2 * 2;
417         } else if (swapX) {
418             *dir = SkPath::kCCW_Direction;
419             // 0 becomes start index 1 and times 2 to convert from rect the rrect indices.
420             return 2 * 1;
421         } else if (swapY) {
422             *dir = SkPath::kCCW_Direction;
423             // 0 becomes start index 3 and times 2 to convert from rect the rrect indices.
424             return 2 * 3;
425         }
426         return 0;
427     }
428 
DefaultRRectDirAndStartIndex(const SkRRect & rrect,bool hasPathEffect,SkPath::Direction * dir)429     static unsigned DefaultRRectDirAndStartIndex(const SkRRect& rrect, bool hasPathEffect,
430                                                  SkPath::Direction* dir) {
431         // This comes from SkPath's interface. The default for adding a SkRRect to a path is
432         // clockwise beginning at starting index 6.
433         static constexpr unsigned kPathRRectStartIdx = 6;
434         *dir = kDefaultRRectDir;
435         if (!hasPathEffect) {
436             // It doesn't matter what start we use, just be consistent to avoid redundant keys.
437             return kDefaultRRectStart;
438         }
439         return kPathRRectStartIdx;
440     }
441 
442     Type                        fType;
443     union {
444         struct {
445             SkRRect                     fRRect;
446             SkPath::Direction           fDir;
447             unsigned                    fStart;
448             bool                        fInverted;
449         } fRRectData;
450         struct {
451             SkPath                      fPath;
452             // Gen ID of the original path (fPath may be modified)
453             int32_t                     fGenID;
454         } fPathData;
455         struct {
456             SkPoint                     fPts[2];
457             bool                        fInverted;
458         } fLineData;
459     };
460     GrStyle                     fStyle;
461     SkAutoSTArray<8, uint32_t>  fInheritedKey;
462 };
463 #endif
464