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 "include/core/SkPathEffect.h"
12#include "include/core/SkStrokeRec.h"
13#include "include/gpu/GrTypes.h"
14#include "include/private/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) = default;
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(const DashInfo& that) { *this = that; }
191 DashInfo& operator=(const DashInfo& that) {
192 fType = that.fType;
193 fPhase = that.fPhase;
194 fIntervals.reset(that.fIntervals.count());
195 sk_careful_memcpy(fIntervals.get(), that.fIntervals.get(),
196 sizeof(SkScalar) * that.fIntervals.count());
197 return *this;
198 }
199 void reset() {
200 fType = SkPathEffect::kNone_DashType;
201 fIntervals.reset(0);
202 }
203 SkPathEffect::DashType fType;
204 SkScalar fPhase{0};
205 SkAutoSTArray<4, SkScalar> fIntervals;
206 };
207
208 bool applyPathEffect(SkPath* dst, SkStrokeRec* strokeRec, const SkPath& src) const;
209
210 SkStrokeRec fStrokeRec;
211 sk_sp<SkPathEffect> fPathEffect;
212 DashInfo fDashInfo;
213};
214
215#endif
216