1 | /* |
2 | * Copyright 2020 Google LLC |
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 "include/core/SkPath.h" |
12 | #include "include/core/SkPoint.h" |
13 | #include "include/core/SkRRect.h" |
14 | #include "include/core/SkRect.h" |
15 | |
16 | // Represents an arc along an oval boundary, or a closed wedge of the oval. |
17 | struct GrArc { |
18 | SkRect fOval; // The sorted, bounding box defining the oval the arc is traced along |
19 | SkScalar fStartAngle; // In degrees |
20 | SkScalar fSweepAngle; // In degrees |
21 | bool fUseCenter; // True if the arc includes the center point of the oval |
22 | }; |
23 | |
24 | // Represents a line segment between two points. |
25 | struct GrLineSegment { |
26 | SkPoint fP1; |
27 | SkPoint fP2; |
28 | }; |
29 | |
30 | /** |
31 | * GrShape is a convenience class to represent the many different specialized geometries that |
32 | * Ganesh can handle, including rects, round rects, lines, as well as paths. It is intended as |
33 | * a data-only class where any additional complex behavior is handled by an owning type (e.g. |
34 | * GrStyledShape). However, it does include some basic utilities that unify common functionality |
35 | * (such as contains()) from the underlying shape types. |
36 | * |
37 | * In order to have lossless simplification of the geometry, it also tracks winding direction, start |
38 | * index, and fill inversion. The direction and index are match the SkPath indexing scheme for |
39 | * the shape's type (e.g. rect, rrect, or oval). |
40 | * |
41 | * Regarding GrShape's empty shape: |
42 | * - GrShape uses empty to refer to the absence of any geometric data |
43 | * - SkRect::isEmpty() returns true if the rect is not sorted, even if it has area. GrShape will not |
44 | * simplify these shapes to an empty GrShape. Rects with actual 0 width and height will simplify |
45 | * to a point or line, not empty. This is to preserve geometric data for path effects and strokes. |
46 | * - SkRRect::isEmpty() is true when the bounds have 0 width or height, so GrShape will simplify it |
47 | * to a point or line, just like a rect. SkRRect does not have the concept of unsorted edges. |
48 | */ |
49 | class GrShape { |
50 | public: |
51 | // The current set of types GrShape can represent directly |
52 | enum class Type : uint8_t { |
53 | kEmpty, kPoint, kRect, kRRect, kPath, kArc, kLine, kLast = kLine |
54 | }; |
55 | static constexpr int kTypeCount = static_cast<int>(Type::kLast) + 1; |
56 | |
57 | // The direction and start index used when a shape does not have a representable winding, |
58 | // or when that information was discarded during simplification (kIgnoreWinding_Flag). |
59 | static constexpr SkPathDirection kDefaultDir = SkPathDirection::kCW; |
60 | static constexpr unsigned kDefaultStart = 0; |
61 | // The fill rule that is used by asPath() for shapes that aren't already a path. |
62 | static constexpr SkPathFillType kDefaultFillType = SkPathFillType::kEvenOdd; |
63 | |
64 | GrShape() {} |
65 | explicit GrShape(const SkPoint& point) { this->setPoint(point); } |
66 | explicit GrShape(const SkRect& rect) { this->setRect(rect); } |
67 | explicit GrShape(const SkRRect& rrect) { this->setRRect(rrect); } |
68 | explicit GrShape(const SkPath& path) { this->setPath(path); } |
69 | explicit GrShape(const GrArc& arc) { this->setArc(arc); } |
70 | explicit GrShape(const GrLineSegment& line){ this->setLine(line); } |
71 | |
72 | explicit GrShape(const GrShape& shape) { *this = shape; } |
73 | |
74 | ~GrShape() { this->reset(); } |
75 | |
76 | // NOTE: None of the geometry types benefit from move semantics, so we don't bother |
77 | // defining a move assignment operator for GrShape. |
78 | GrShape& operator=(const GrShape& shape); |
79 | |
80 | // These type queries reflect the shape type provided when assigned, it does not incorporate |
81 | // any potential simplification (e.g. if isRRect() is true and rrect().isRect() is true, |
82 | // isRect() will still be false, until simplify() is called). |
83 | bool isEmpty() const { return this->type() == Type::kEmpty; } |
84 | bool isPoint() const { return this->type() == Type::kPoint; } |
85 | bool isRect() const { return this->type() == Type::kRect; } |
86 | bool isRRect() const { return this->type() == Type::kRRect; } |
87 | bool isPath() const { return this->type() == Type::kPath; } |
88 | bool isArc() const { return this->type() == Type::kArc; } |
89 | bool isLine() const { return this->type() == Type::kLine; } |
90 | |
91 | Type type() const { return fType; } |
92 | |
93 | // Report the shape type, winding direction, start index, and invertedness as a value suitable |
94 | // for use in a resource key. This does not include any geometry coordinates into the key value. |
95 | uint32_t stateKey() const; |
96 | |
97 | // Whether or not the shape is meant to be the inverse of its geometry (i.e. its exterior). |
98 | bool inverted() const { |
99 | return this->isPath() ? fPath.isInverseFillType() : SkToBool(fInverted); |
100 | } |
101 | |
102 | // Returns the path direction extracted from the path during simplification, if the shape's |
103 | // type represents a rrect, rect, or oval. |
104 | SkPathDirection dir() const { return fCW ? SkPathDirection::kCW : SkPathDirection::kCCW; } |
105 | // Returns the start index extracted from the path during simplification, if the shape's |
106 | // type represents a rrect, rect, or oval. |
107 | unsigned startIndex() const { return fStart; } |
108 | |
109 | // Override the direction and start parameters for the simplified contour. These are only |
110 | // meaningful for rects, rrects, and ovals. |
111 | void setPathWindingParams(SkPathDirection dir, unsigned start) { |
112 | SkASSERT((this->isRect() && start < 4) || (this->isRRect() && start < 8) || |
113 | (dir == kDefaultDir && start == kDefaultStart)); |
114 | fCW = dir == SkPathDirection::kCW; |
115 | fStart = static_cast<uint8_t>(start); |
116 | } |
117 | |
118 | void setInverted(bool inverted) { |
119 | if (this->isPath()) { |
120 | if (inverted != fPath.isInverseFillType()) { |
121 | fPath.toggleInverseFillType(); |
122 | } |
123 | } else { |
124 | fInverted = inverted; |
125 | } |
126 | } |
127 | |
128 | // Access the actual geometric description of the shape. May only access the appropriate type |
129 | // based on what was last set. The type may change after simplify() is called. |
130 | SkPoint& point() { SkASSERT(this->isPoint()); return fPoint; } |
131 | const SkPoint& point() const { SkASSERT(this->isPoint()); return fPoint; } |
132 | |
133 | SkRect& rect() { SkASSERT(this->isRect()); return fRect; } |
134 | const SkRect& rect() const { SkASSERT(this->isRect()); return fRect; } |
135 | |
136 | SkRRect& rrect() { SkASSERT(this->isRRect()); return fRRect; } |
137 | const SkRRect& rrect() const { SkASSERT(this->isRRect()); return fRRect; } |
138 | |
139 | SkPath& path() { SkASSERT(this->isPath()); return fPath; } |
140 | const SkPath& path() const { SkASSERT(this->isPath()); return fPath; } |
141 | |
142 | GrArc& arc() { SkASSERT(this->isArc()); return fArc; } |
143 | const GrArc& arc() const { SkASSERT(this->isArc()); return fArc; } |
144 | |
145 | GrLineSegment& line() { SkASSERT(this->isLine()); return fLine; } |
146 | const GrLineSegment& line() const { SkASSERT(this->isLine()); return fLine; } |
147 | |
148 | // Update the geometry stored in the GrShape and update its associated type to match. This |
149 | // performs no simplification, so calling setRRect() with a round rect that has isRect() return |
150 | // true will still be considered an rrect by this shape until simplify() is called. |
151 | // |
152 | // These also reset any extracted direction, start, and inverted state from a prior simplified |
153 | // path, since these functions ared used to describe a new geometry. |
154 | void setPoint(const SkPoint& point) { |
155 | this->reset(Type::kPoint); |
156 | fPoint = point; |
157 | } |
158 | void setRect(const SkRect& rect) { |
159 | this->reset(Type::kRect); |
160 | fRect = rect; |
161 | } |
162 | void setRRect(const SkRRect& rrect) { |
163 | this->reset(Type::kRRect); |
164 | fRRect = rrect; |
165 | } |
166 | void setArc(const GrArc& arc) { |
167 | this->reset(Type::kArc); |
168 | fArc = arc; |
169 | } |
170 | void setLine(const GrLineSegment& line) { |
171 | this->reset(Type::kLine); |
172 | fLine = line; |
173 | } |
174 | void setPath(const SkPath& path) { |
175 | if (this->isPath()) { |
176 | // Assign directly |
177 | fPath = path; |
178 | } else { |
179 | // In-place initialize |
180 | this->setType(Type::kPath); |
181 | new (&fPath) SkPath(path); |
182 | } |
183 | // Must also set these since we didn't call reset() like other setX functions. |
184 | this->setPathWindingParams(kDefaultDir, kDefaultStart); |
185 | fInverted = path.isInverseFillType(); |
186 | } |
187 | void reset() { |
188 | this->reset(Type::kEmpty); |
189 | } |
190 | |
191 | // Flags that enable more aggressive, "destructive" simplifications to the geometry |
192 | enum SimplifyFlags : unsigned { |
193 | // If set, it is assumed the original shape would have been implicitly filled when drawn or |
194 | // clipped, so simpler shape types that are closed can still be considered. Shapes with |
195 | // 0 area (i.e. points and lines) can be turned into empty. |
196 | kSimpleFill_Flag = 0b001, |
197 | // If set, simplifications that would impact a directional stroke or path effect can still |
198 | // be taken (e.g. dir and start are not required, arcs can be converted to ovals). |
199 | kIgnoreWinding_Flag = 0b010, |
200 | // If set, the geometry will be updated to have sorted coordinates (rects, lines), modulated |
201 | // sweep angles (arcs). |
202 | kMakeCanonical_Flag = 0b100, |
203 | |
204 | kAll_Flags = 0b111 |
205 | }; |
206 | // Returns true if the shape was originally closed based on type (or detected type within a |
207 | // path), even if the final simplification results in a point, line, or empty. |
208 | bool simplify(unsigned flags = kAll_Flags); |
209 | |
210 | // True if the given bounding box is completely inside the shape. |
211 | bool contains(const SkRect& rect) const; |
212 | |
213 | // True if the underlying geometry represents a closed shape, without the need for an |
214 | // implicit close (note that if simplified earlier with 'simpleFill' = true, a shape that was |
215 | // not closed may become closed). |
216 | bool closed() const; |
217 | |
218 | // True if the underlying shape is known to be convex, assuming no other styles. If 'simpleFill' |
219 | // is true, it is assumed the contours will be implicitly closed when drawn or used. |
220 | bool convex(bool simpleFill = true) const; |
221 | |
222 | // The bounding box of the shape. |
223 | SkRect bounds() const; |
224 | |
225 | // The segment masks that describe the shape, were it to be converted to an SkPath |
226 | uint32_t segmentMask() const; |
227 | |
228 | // Convert the shape into a path that describes the same geometry. |
229 | void asPath(SkPath* out, bool simpleFill = true) const; |
230 | |
231 | private: |
232 | |
233 | void setType(Type type) { |
234 | if (this->isPath() && type != Type::kPath) { |
235 | fInverted = fPath.isInverseFillType(); |
236 | fPath.~SkPath(); |
237 | } |
238 | fType = type; |
239 | } |
240 | |
241 | void reset(Type type) { |
242 | this->setType(type); |
243 | this->setPathWindingParams(kDefaultDir, kDefaultStart); |
244 | this->setInverted(false); |
245 | } |
246 | |
247 | // Paths and arcs are root shapes, another type will never simplify to them, so they do |
248 | // not take the geometry to simplify as an argument. Since they are root shapes, they also |
249 | // return whether or not they were originally closed before being simplified. |
250 | bool simplifyPath(unsigned flags); |
251 | bool simplifyArc(unsigned flags); |
252 | |
253 | // The simpler type classes do take the geometry because it may represent an in-progress |
254 | // simplification that hasn't been set on the GrShape yet. The simpler types do not report |
255 | // whether or not they were closed because it's implicit in their type. |
256 | void simplifyLine(const SkPoint& p1, const SkPoint& p2, unsigned flags); |
257 | void simplifyPoint(const SkPoint& point, unsigned flags); |
258 | |
259 | // RRects and rects care about winding for path effects and will set the path winding state |
260 | // of the shape as well. |
261 | void simplifyRRect(const SkRRect& rrect, SkPathDirection dir, unsigned start, unsigned flags); |
262 | void simplifyRect(const SkRect& rect, SkPathDirection dir, unsigned start, unsigned flags); |
263 | |
264 | union { |
265 | SkPoint fPoint; |
266 | SkRect fRect; |
267 | SkRRect fRRect; |
268 | SkPath fPath; |
269 | GrArc fArc; |
270 | GrLineSegment fLine; |
271 | }; |
272 | |
273 | Type fType = Type::kEmpty; |
274 | uint8_t fStart; // Restricted to rrects and simpler, so this will be < 8 |
275 | bool fCW; |
276 | bool fInverted; |
277 | }; |
278 | |
279 | #endif |
280 | |