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