GrShape.h revision 7049396b65660907af5292d899053280430d929a
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 */
35class GrShape {
36public:
37    GrShape() : fType(Type::kEmpty) {}
38
39    explicit GrShape(const SkPath& path)
40        : fType(Type::kPath)
41        , fPath(&path) {
42        this->attemptToReduceFromPath();
43    }
44
45    explicit GrShape(const SkRRect& rrect)
46        : fType(Type::kRRect)
47        , fRRect(rrect)
48        , fRRectIsInverted(false) {
49        fRRectStart = DefaultRRectDirAndStartIndex(rrect, false, &fRRectDir);
50        this->attemptToReduceFromRRect();
51    }
52
53    explicit GrShape(const SkRect& rect)
54        : fType(Type::kRRect)
55        , fRRect(SkRRect::MakeRect(rect))
56        , fRRectIsInverted(false) {
57        fRRectStart = DefaultRectDirAndStartIndex(rect, false, &fRRectDir);
58        this->attemptToReduceFromRRect();
59    }
60
61    GrShape(const SkPath& path, const GrStyle& style)
62        : fType(Type::kPath)
63        , fPath(&path)
64        , fStyle(style) {
65        this->attemptToReduceFromPath();
66    }
67
68    GrShape(const SkRRect& rrect, const GrStyle& style)
69        : fType(Type::kRRect)
70        , fRRect(rrect)
71        , fRRectIsInverted(false)
72        , fStyle(style) {
73        fRRectStart = DefaultRRectDirAndStartIndex(rrect, style.hasPathEffect(), &fRRectDir);
74        this->attemptToReduceFromRRect();
75    }
76
77    GrShape(const SkRRect& rrect, SkPath::Direction dir, unsigned start, bool inverted,
78            const GrStyle& style)
79        : fType(Type::kRRect)
80        , fRRect(rrect)
81        , fRRectIsInverted(inverted)
82        , fStyle(style) {
83        if (style.pathEffect()) {
84            fRRectDir = dir;
85            fRRectStart = start;
86            if (fRRect.getType() == SkRRect::kRect_Type) {
87                fRRectStart = (fRRectStart + 1) & 0b110;
88            } else if (fRRect.getType() == SkRRect::kOval_Type) {
89                fRRectStart &= 0b110;
90            }
91        } else {
92            fRRectStart = DefaultRRectDirAndStartIndex(rrect, false, &fRRectDir);
93        }
94        this->attemptToReduceFromRRect();
95    }
96
97    GrShape(const SkRect& rect, const GrStyle& style)
98        : fType(Type::kRRect)
99        , fRRect(SkRRect::MakeRect(rect))
100        , fRRectIsInverted(false)
101        , fStyle(style) {
102        fRRectStart = DefaultRectDirAndStartIndex(rect, style.hasPathEffect(), &fRRectDir);
103        this->attemptToReduceFromRRect();
104    }
105
106    GrShape(const SkPath& path, const SkPaint& paint)
107        : fType(Type::kPath)
108        , fPath(&path)
109        , fStyle(paint) {
110        this->attemptToReduceFromPath();
111    }
112
113    GrShape(const SkRRect& rrect, const SkPaint& paint)
114        : fType(Type::kRRect)
115        , fRRect(rrect)
116        , fRRectIsInverted(false)
117        , fStyle(paint) {
118        fRRectStart = DefaultRRectDirAndStartIndex(rrect, fStyle.hasPathEffect(), &fRRectDir);
119        this->attemptToReduceFromRRect();
120    }
121
122    GrShape(const SkRect& rect, const SkPaint& paint)
123        : fType(Type::kRRect)
124        , fRRect(SkRRect::MakeRect(rect))
125        , fRRectIsInverted(false)
126        , fStyle(paint) {
127        fRRectStart = DefaultRectDirAndStartIndex(rect, fStyle.hasPathEffect(), &fRRectDir);
128        this->attemptToReduceFromRRect();
129    }
130
131    GrShape(const GrShape&);
132    GrShape& operator=(const GrShape& that);
133
134    ~GrShape() {
135        if (Type::kPath == fType) {
136            fPath.reset();
137        }
138    }
139
140    const GrStyle& style() const { return fStyle; }
141
142    /**
143     * Returns a shape that has either applied the path effect or path effect and stroking
144     * information from this shape's style to its geometry. Scale is used when approximating the
145     * output geometry and typically is computed from the view matrix
146     */
147    GrShape applyStyle(GrStyle::Apply apply, SkScalar scale) {
148        return GrShape(*this, apply, scale);
149    }
150
151    /** Returns the unstyled geometry as a rrect if possible. */
152    bool asRRect(SkRRect* rrect, SkPath::Direction* dir, unsigned* start, bool* inverted) const {
153        if (Type::kRRect != fType) {
154            return false;
155        }
156        if (rrect) {
157            *rrect = fRRect;
158        }
159        if (dir) {
160            *dir = fRRectDir;
161        }
162        if (start) {
163            *start = fRRectStart;
164        }
165        if (inverted) {
166            *inverted = fRRectIsInverted;
167        }
168        return true;
169    }
170
171    /** Returns the unstyled geometry as a path. */
172    void asPath(SkPath* out) const {
173        switch (fType) {
174            case Type::kEmpty:
175                out->reset();
176                break;
177            case Type::kRRect:
178                out->reset();
179                out->addRRect(fRRect, fRRectDir, fRRectStart);
180                if (fRRectIsInverted) {
181                    out->setFillType(SkPath::kInverseWinding_FillType);
182                }
183                break;
184            case Type::kPath:
185                *out = *fPath.get();
186                break;
187        }
188    }
189
190    /**
191     * Returns whether the geometry is empty. Note that applying the style could produce a
192     * non-empty shape.
193     */
194    bool isEmpty() const { return Type::kEmpty == fType; }
195
196    /**
197     * Gets the bounds of the geometry without reflecting the shape's styling. This ignores
198     * the inverse fill nature of the geometry.
199     */
200    const SkRect& bounds() const;
201
202    /**
203     * Gets the bounds of the geometry reflecting the shape's styling (ignoring inverse fill
204     * status).
205     */
206    void styledBounds(SkRect* bounds) const;
207
208    /**
209     * Is it known that the unstyled geometry has no unclosed contours. This means that it will
210     * not have any caps if stroked (modulo the effect of any path effect).
211     */
212    bool knownToBeClosed() const {
213        switch (fType) {
214            case Type::kEmpty:
215                return true;
216            case Type::kRRect:
217                return true;
218            case Type::kPath:
219                return false;
220        }
221        return false;
222    }
223
224    uint32_t segmentMask() const {
225        switch (fType) {
226            case Type::kEmpty:
227                return 0;
228            case Type::kRRect:
229                if (fRRect.getType() == SkRRect::kOval_Type) {
230                    return SkPath::kConic_SegmentMask;
231                } else if (fRRect.getType() == SkRRect::kRect_Type) {
232                    return SkPath::kLine_SegmentMask;
233                }
234                return SkPath::kLine_SegmentMask | SkPath::kConic_SegmentMask;
235            case Type::kPath:
236                return fPath.get()->getSegmentMasks();
237        }
238        return 0;
239    }
240
241    /**
242     * Gets the size of the key for the shape represented by this GrShape (ignoring its styling).
243     * A negative value is returned if the shape has no key (shouldn't be cached).
244     */
245    int unstyledKeySize() const;
246
247    /**
248     * Writes unstyledKeySize() bytes into the provided pointer. Assumes that there is enough
249     * space allocated for the key and that unstyledKeySize() does not return a negative value
250     * for this shape.
251     */
252    void writeUnstyledKey(uint32_t* key) const;
253
254private:
255    enum class Type {
256        kEmpty,
257        kRRect,
258        kPath,
259    };
260
261    /** Constructor used by the applyStyle() function */
262    GrShape(const GrShape& parentShape, GrStyle::Apply, SkScalar scale);
263
264    /**
265     * Determines the key we should inherit from the input shape's geometry and style when
266     * we are applying the style to create a new shape.
267     */
268    void setInheritedKey(const GrShape& parentShape, GrStyle::Apply, SkScalar scale);
269
270    void attemptToReduceFromPath() {
271        SkASSERT(Type::kPath == fType);
272        fType = AttemptToReduceFromPathImpl(*fPath.get(), &fRRect, &fRRectDir, &fRRectStart,
273                                            &fRRectIsInverted, fStyle.pathEffect(),
274                                            fStyle.strokeRec());
275        if (Type::kPath != fType) {
276            fPath.reset();
277            fInheritedKey.reset(0);
278        }
279    }
280
281    void attemptToReduceFromRRect() {
282        SkASSERT(Type::kRRect == fType);
283        SkASSERT(!fInheritedKey.count());
284        if (fRRectIsInverted) {
285            if (!fStyle.hasNonDashPathEffect()) {
286                SkStrokeRec::Style recStyle = fStyle.strokeRec().getStyle();
287                if (SkStrokeRec::kStroke_Style == recStyle ||
288                    SkStrokeRec::kHairline_Style == recStyle) {
289                    // stroking ignores the path fill rule.
290                    fRRectIsInverted = false;
291                }
292            }
293        } else if (fRRect.isEmpty()) {
294            fType = Type::kEmpty;
295        }
296    }
297
298    static Type AttemptToReduceFromPathImpl(const SkPath& path, SkRRect* rrect,
299                                            SkPath::Direction* rrectDir, unsigned* rrectStart,
300                                            bool* rrectIsInverted, const SkPathEffect* pe,
301                                            const SkStrokeRec& strokeRec);
302
303    static constexpr SkPath::Direction kDefaultRRectDir = SkPath::kCW_Direction;
304    static constexpr unsigned kDefaultRRectStart = 0;
305
306    static unsigned DefaultRectDirAndStartIndex(const SkRect& rect, bool hasPathEffect,
307                                                SkPath::Direction* dir) {
308        *dir = kDefaultRRectDir;
309        // This comes from SkPath's interface. The default for adding a SkRect is counter clockwise
310        // beginning at index 0 (which happens to correspond to rrect index 0 or 7).
311        if (!hasPathEffect) {
312            // It doesn't matter what start we use, just be consistent to avoid redundant keys.
313            return kDefaultRRectStart;
314        }
315        // In SkPath a rect starts at index 0 by default. This is the top left corner. However,
316        // we store rects as rrects. RRects don't preserve the invertedness, but rather sort the
317        // rect edges. Thus, we may need to modify the rrect's start index to account for the sort.
318        bool swapX = rect.fLeft > rect.fRight;
319        bool swapY = rect.fTop > rect.fBottom;
320        if (swapX && swapY) {
321            // 0 becomes start index 2 and times 2 to convert from rect the rrect indices.
322            return 2 * 2;
323        } else if (swapX) {
324            *dir = SkPath::kCCW_Direction;
325            // 0 becomes start index 1 and times 2 to convert from rect the rrect indices.
326            return 2 * 1;
327        } else if (swapY) {
328            *dir = SkPath::kCCW_Direction;
329            // 0 becomes start index 3 and times 2 to convert from rect the rrect indices.
330            return 2 * 3;
331        }
332        return 0;
333    }
334
335    static unsigned DefaultRRectDirAndStartIndex(const SkRRect& rrect, bool hasPathEffect,
336                                                 SkPath::Direction* dir) {
337        // This comes from SkPath's interface. The default for adding a SkRRect to a path is
338        // clockwise beginning at starting index 6.
339        static constexpr unsigned kPathRRectStartIdx = 6;
340        *dir = kDefaultRRectDir;
341        if (!hasPathEffect) {
342            // It doesn't matter what start we use, just be consistent to avoid redundant keys.
343            return kDefaultRRectStart;
344        }
345        return kPathRRectStartIdx;
346    }
347
348    Type                        fType;
349    SkRRect                     fRRect;
350    SkPath::Direction           fRRectDir;
351    unsigned                    fRRectStart;
352    bool                        fRRectIsInverted;
353    SkTLazy<SkPath>             fPath;
354    GrStyle                     fStyle;
355    SkAutoSTArray<8, uint32_t>  fInheritedKey;
356};
357#endif
358