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 GrStyle_DEFINED
9#define GrStyle_DEFINED
10
11#include "GrTypes.h"
12#include "SkPathEffect.h"
13#include "SkStrokeRec.h"
14#include "SkTemplates.h"
15
16/**
17 * Represents the various ways that a GrShape can be styled. It has fill/stroking information
18 * as well as an optional path effect. If the path effect represents dashing, the dashing
19 * information is extracted from the path effect and stored explicitly.
20 *
21 * This will replace GrStrokeInfo as GrShape is deployed.
22 */
23class GrStyle {
24public:
25    /**
26     * A style object that represents a fill with no path effect.
27     * TODO: constexpr with C++14
28     */
29    static const GrStyle& SimpleFill() {
30        static const GrStyle kFill(SkStrokeRec::kFill_InitStyle);
31        return kFill;
32        }
33
34    /**
35     * A style object that represents a hairline stroke with no path effect.
36     * TODO: constexpr with C++14
37     */
38    static const GrStyle& SimpleHairline() {
39        static const GrStyle kHairline(SkStrokeRec::kHairline_InitStyle);
40        return kHairline;
41    }
42
43    enum class Apply {
44        kPathEffectOnly,
45        kPathEffectAndStrokeRec
46    };
47
48    /**
49     * Optional flags for computing keys that may remove unnecessary variation in the key due to
50     * style settings that don't affect particular classes of geometry.
51     */
52    enum KeyFlags {
53        // The shape being styled has no open contours.
54        kClosed_KeyFlag = 0x1,
55        // The shape being styled doesn't have any joins and so isn't affected by join type.
56        kNoJoins_KeyFlag = 0x2
57    };
58
59    /**
60     * Computes the key length for a GrStyle. The return will be negative if it cannot be turned
61     * into a key. This occurs when there is a path effect that is not a dash. The key can
62     * either reflect just the path effect (if one) or the path effect and the strokerec. Note
63     * that a simple fill has a zero sized key.
64     */
65    static int KeySize(const GrStyle&, Apply, uint32_t flags = 0);
66
67    /**
68     * Writes a unique key for the style into the provided buffer. This function assumes the buffer
69     * has room for at least KeySize() values. It assumes that KeySize() returns a non-negative
70     * value for the combination of GrStyle, Apply and flags params. This is written so that the key
71     * for just dash application followed by the key for the remaining SkStrokeRec is the same as
72     * the key for applying dashing and SkStrokeRec all at once.
73     */
74    static void WriteKey(uint32_t*, const GrStyle&, Apply, SkScalar scale, uint32_t flags = 0);
75
76    GrStyle() : GrStyle(SkStrokeRec::kFill_InitStyle) {}
77
78    explicit GrStyle(SkStrokeRec::InitStyle initStyle) : fStrokeRec(initStyle) {}
79
80    GrStyle(const SkStrokeRec& strokeRec, sk_sp<SkPathEffect> pe) : fStrokeRec(strokeRec) {
81        this->initPathEffect(std::move(pe));
82    }
83
84    GrStyle(const GrStyle& that) : fStrokeRec(SkStrokeRec::kFill_InitStyle) { *this = that; }
85
86    explicit GrStyle(const SkPaint& paint) : fStrokeRec(paint) {
87        this->initPathEffect(paint.refPathEffect());
88    }
89
90    explicit GrStyle(const SkPaint& paint, SkPaint::Style overrideStyle)
91            : fStrokeRec(paint, overrideStyle) {
92        this->initPathEffect(paint.refPathEffect());
93    }
94
95    GrStyle& operator=(const GrStyle& that) {
96        fPathEffect = that.fPathEffect;
97        fDashInfo = that.fDashInfo;
98        fStrokeRec = that.fStrokeRec;
99        return *this;
100    }
101
102    void resetToInitStyle(SkStrokeRec::InitStyle fillOrHairline) {
103        fDashInfo.reset();
104        fPathEffect.reset(nullptr);
105        if (SkStrokeRec::kFill_InitStyle == fillOrHairline) {
106            fStrokeRec.setFillStyle();
107        } else {
108            fStrokeRec.setHairlineStyle();
109        }
110    }
111
112    /** Is this style a fill with no path effect? */
113    bool isSimpleFill() const { return fStrokeRec.isFillStyle() && !fPathEffect; }
114
115    /** Is this style a hairline with no path effect? */
116    bool isSimpleHairline() const { return fStrokeRec.isHairlineStyle() && !fPathEffect; }
117
118    SkPathEffect* pathEffect() const { return fPathEffect.get(); }
119    sk_sp<SkPathEffect> refPathEffect() const { return fPathEffect; }
120
121    bool hasPathEffect() const { return SkToBool(fPathEffect.get()); }
122
123    bool hasNonDashPathEffect() const { return fPathEffect.get() && !this->isDashed(); }
124
125    bool isDashed() const { return SkPathEffect::kDash_DashType == fDashInfo.fType; }
126    SkScalar dashPhase() const {
127        SkASSERT(this->isDashed());
128        return fDashInfo.fPhase;
129    }
130    int dashIntervalCnt() const {
131        SkASSERT(this->isDashed());
132        return fDashInfo.fIntervals.count();
133    }
134    const SkScalar* dashIntervals() const {
135        SkASSERT(this->isDashed());
136        return fDashInfo.fIntervals.get();
137    }
138
139    const SkStrokeRec& strokeRec() const { return fStrokeRec; }
140
141    /** Hairline or fill styles without path effects make no alterations to a geometry. */
142    bool applies() const {
143        return this->pathEffect() || (!fStrokeRec.isFillStyle() && !fStrokeRec.isHairlineStyle());
144    }
145
146    static SkScalar MatrixToScaleFactor(const SkMatrix& matrix) {
147        // getMaxScale will return -1 if the matrix has perspective. In that case we can use a scale
148        // factor of 1. This isn't necessarily a good choice and in the future we might consider
149        // taking a bounds here for the perspective case.
150        return SkScalarAbs(matrix.getMaxScale());
151    }
152    /**
153     * Applies just the path effect and returns remaining stroke information. This will fail if
154     * there is no path effect. dst may or may not have been overwritten on failure. Scale controls
155     * geometric approximations made by the path effect. It is typically computed from the view
156     * matrix.
157     */
158    bool SK_WARN_UNUSED_RESULT applyPathEffectToPath(SkPath* dst, SkStrokeRec* remainingStoke,
159                                                     const SkPath& src, SkScalar scale) const;
160
161    /**
162     * If this succeeds then the result path should be filled or hairlined as indicated by the
163     * returned SkStrokeRec::InitStyle value. Will fail if there is no path effect and the
164     * strokerec doesn't change the geometry. When this fails the outputs may or may not have
165     * been overwritten. Scale controls geometric approximations made by the path effect and
166     * stroker. It is typically computed from the view matrix.
167     */
168    bool SK_WARN_UNUSED_RESULT applyToPath(SkPath* dst, SkStrokeRec::InitStyle* fillOrHairline,
169                                           const SkPath& src, SkScalar scale) const;
170
171    /** Given bounds of a path compute the bounds of path with the style applied. */
172    void adjustBounds(SkRect* dst, const SkRect& src) const {
173        if (this->pathEffect()) {
174            this->pathEffect()->computeFastBounds(dst, src);
175            // This may not be the correct SkStrokeRec to use. skbug.com/5299
176            // It happens to work for dashing.
177            SkScalar radius = fStrokeRec.getInflationRadius();
178            dst->outset(radius, radius);
179        } else {
180            SkScalar radius = fStrokeRec.getInflationRadius();
181            *dst = src.makeOutset(radius, radius);
182        }
183    }
184
185private:
186    void initPathEffect(sk_sp<SkPathEffect> pe);
187
188    struct DashInfo {
189        DashInfo() : fType(SkPathEffect::kNone_DashType) {}
190        DashInfo& operator=(const DashInfo& that) {
191            fType = that.fType;
192            fPhase = that.fPhase;
193            fIntervals.reset(that.fIntervals.count());
194            sk_careful_memcpy(fIntervals.get(), that.fIntervals.get(),
195                              sizeof(SkScalar) * that.fIntervals.count());
196            return *this;
197        }
198        void reset() {
199            fType = SkPathEffect::kNone_DashType;
200            fIntervals.reset(0);
201        }
202        SkPathEffect::DashType      fType;
203        SkScalar                    fPhase;
204        SkAutoSTArray<4, SkScalar>  fIntervals;
205    };
206
207    bool applyPathEffect(SkPath* dst, SkStrokeRec* strokeRec, const SkPath& src) const;
208
209    SkStrokeRec         fStrokeRec;
210    sk_sp<SkPathEffect> fPathEffect;
211    DashInfo            fDashInfo;
212};
213
214#endif
215