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#include "src/gpu/geometry/GrShape.h"
9
10#include "src/core/SkPathPriv.h"
11
12GrShape& GrShape::operator=(const GrShape& shape) {
13 switch(shape.type()) {
14 case Type::kEmpty:
15 this->reset();
16 break;
17 case Type::kPoint:
18 this->setPoint(shape.fPoint);
19 break;
20 case Type::kRect:
21 this->setRect(shape.fRect);
22 break;
23 case Type::kRRect:
24 this->setRRect(shape.fRRect);
25 break;
26 case Type::kPath:
27 this->setPath(shape.fPath);
28 break;
29 case Type::kArc:
30 this->setArc(shape.fArc);
31 break;
32 case Type::kLine:
33 this->setLine(shape.fLine);
34 break;
35 default:
36 SkUNREACHABLE;
37 }
38
39 fStart = shape.fStart;
40 fCW = shape.fCW;
41 fInverted = shape.fInverted;
42
43 return *this;
44}
45
46uint32_t GrShape::stateKey() const {
47 // Use the path's full fill type instead of just whether or not it's inverted.
48 uint32_t key = this->isPath() ? static_cast<uint32_t>(fPath.getFillType())
49 : (fInverted ? 1 : 0);
50 key |= ((uint32_t) fType) << 2; // fill type was 2 bits
51 key |= fStart << 5; // type was 3 bits, total 5 bits so far
52 key |= (fCW ? 1 : 0) << 8; // start was 3 bits, total 8 bits so far
53 return key;
54}
55
56bool GrShape::simplifyPath(unsigned flags) {
57 SkASSERT(this->isPath());
58
59 SkRect rect;
60 SkRRect rrect;
61 SkPoint pts[2];
62
63 SkPathDirection dir;
64 unsigned start;
65
66 if (fPath.isEmpty()) {
67 this->setType(Type::kEmpty);
68 return false;
69 } else if (fPath.isLine(pts)) {
70 this->simplifyLine(pts[0], pts[1], flags);
71 return false;
72 } else if (SkPathPriv::IsRRect(fPath, &rrect, &dir, &start)) {
73 this->simplifyRRect(rrect, dir, start, flags);
74 return true;
75 } else if (SkPathPriv::IsOval(fPath, &rect, &dir, &start)) {
76 // Convert to rrect indexing since oval is not represented explicitly
77 this->simplifyRRect(SkRRect::MakeOval(rect), dir, start * 2, flags);
78 return true;
79 } else if (SkPathPriv::IsSimpleClosedRect(fPath, &rect, &dir, &start)) {
80 // When there is a path effect we restrict rect detection to the narrower API that
81 // gives us the starting position. Otherwise, we will retry with the more aggressive
82 // isRect().
83 this->simplifyRect(rect, dir, start, flags);
84 return true;
85 } else if (flags & kIgnoreWinding_Flag) {
86 // Attempt isRect() since we don't have to preserve any winding info
87 bool closed;
88 if (fPath.isRect(&rect, &closed) && (closed || (flags & kSimpleFill_Flag))) {
89 this->simplifyRect(rect, kDefaultDir, kDefaultStart, flags);
90 return true;
91 }
92 }
93 // No further simplification for a path. For performance reasons, we don't query the path to
94 // determine it was closed, as whether or not it was closed when it remains a path type is not
95 // important for styling.
96 return false;
97}
98
99bool GrShape::simplifyArc(unsigned flags) {
100 SkASSERT(this->isArc());
101
102 // Arcs can simplify to rrects, lines, points, or empty; regardless of what it simplifies to
103 // it was closed if went through the center point.
104 bool wasClosed = fArc.fUseCenter;
105 if (fArc.fOval.isEmpty() || !fArc.fSweepAngle) {
106 if (flags & kSimpleFill_Flag) {
107 // Go straight to empty, since the other degenerate shapes all have 0 area anyway.
108 this->setType(Type::kEmpty);
109 } else if (!fArc.fSweepAngle) {
110 SkPoint center = {fArc.fOval.centerX(), fArc.fOval.centerY()};
111 SkScalar startRad = SkDegreesToRadians(fArc.fStartAngle);
112 SkPoint start = {center.fX + 0.5f * fArc.fOval.width() * SkScalarCos(startRad),
113 center.fY + 0.5f * fArc.fOval.height() * SkScalarSin(startRad)};
114 // Either just the starting point, or a line from the center to the start
115 if (fArc.fUseCenter) {
116 this->simplifyLine(center, start, flags);
117 } else {
118 this->simplifyPoint(start, flags);
119 }
120 } else {
121 // TODO: Theoretically, we could analyze the arc projected into the empty bounds to
122 // determine a line, but that is somewhat complex for little value (since the arc
123 // can backtrack on itself if the sweep angle is large enough).
124 this->setType(Type::kEmpty);
125 }
126 } else {
127 if ((flags & kSimpleFill_Flag) || ((flags & kIgnoreWinding_Flag) && !fArc.fUseCenter)) {
128 // Eligible to turn into an oval if it sweeps a full circle
129 if (fArc.fSweepAngle <= -360.f || fArc.fSweepAngle >= 360.f) {
130 this->simplifyRRect(SkRRect::MakeOval(fArc.fOval),
131 kDefaultDir, kDefaultStart, flags);
132 return true;
133 }
134 }
135
136 if (flags & kMakeCanonical_Flag) {
137 // Map start to 0 to 360, sweep is always positive
138 if (fArc.fSweepAngle < 0) {
139 fArc.fStartAngle = fArc.fStartAngle + fArc.fSweepAngle;
140 fArc.fSweepAngle = -fArc.fSweepAngle;
141 }
142
143 if (fArc.fStartAngle < 0 || fArc.fStartAngle >= 360.f) {
144 fArc.fStartAngle = SkScalarMod(fArc.fStartAngle, 360.f);
145 }
146 }
147 }
148
149 return wasClosed;
150}
151
152void GrShape::simplifyRRect(const SkRRect& rrect, SkPathDirection dir, unsigned start,
153 unsigned flags) {
154 if (rrect.isEmpty() || rrect.isRect()) {
155 // Change index from rrect to rect
156 start = ((start + 1) / 2) % 4;
157 this->simplifyRect(rrect.rect(), dir, start, flags);
158 } else if (!this->isRRect()) {
159 this->setType(Type::kRRect);
160 fRRect = rrect;
161 this->setPathWindingParams(dir, start);
162 // A round rect is already canonical, so there's nothing more to do
163 } else {
164 // If starting as a round rect, the provided rrect/winding params should be already set
165 SkASSERT(fRRect == rrect && this->dir() == dir && this->startIndex() == start);
166 }
167}
168
169void GrShape::simplifyRect(const SkRect& rect, SkPathDirection dir, unsigned start,
170 unsigned flags) {
171 if (!rect.width() || !rect.height()) {
172 if (flags & kSimpleFill_Flag) {
173 // A zero area, filled shape so go straight to empty
174 this->setType(Type::kEmpty);
175 } else if (!rect.width() ^ !rect.height()) {
176 // A line, choose the first point that best matches the starting index
177 SkPoint p1 = {rect.fLeft, rect.fTop};
178 SkPoint p2 = {rect.fRight, rect.fBottom};
179 if (start >= 2 && !(flags & kIgnoreWinding_Flag)) {
180 using std::swap;
181 swap(p1, p2);
182 }
183 this->simplifyLine(p1, p2, flags);
184 } else {
185 // A point (all edges are equal, so start+dir doesn't affect choice)
186 this->simplifyPoint({rect.fLeft, rect.fTop}, flags);
187 }
188 } else {
189 if (!this->isRect()) {
190 this->setType(Type::kRect);
191 fRect = rect;
192 this->setPathWindingParams(dir, start);
193 } else {
194 // If starting as a rect, the provided rect/winding params should already be set
195 SkASSERT(fRect == rect && this->dir() == dir && this->startIndex() == start);
196 }
197 if (flags & kMakeCanonical_Flag) {
198 fRect.sort();
199 }
200 }
201}
202
203void GrShape::simplifyLine(const SkPoint& p1, const SkPoint& p2, unsigned flags) {
204 if (flags & kSimpleFill_Flag) {
205 this->setType(Type::kEmpty);
206 } else if (p1 == p2) {
207 this->simplifyPoint(p1, false);
208 } else {
209 if (!this->isLine()) {
210 this->setType(Type::kLine);
211 fLine.fP1 = p1;
212 fLine.fP2 = p2;
213 } else {
214 // If starting as a line, the provided points should already be set
215 SkASSERT(fLine.fP1 == p1 && fLine.fP2 == p2);
216 }
217 if (flags & kMakeCanonical_Flag) {
218 // Sort the end points
219 if (fLine.fP2.fY < fLine.fP1.fY ||
220 (fLine.fP2.fY == fLine.fP1.fY && fLine.fP2.fX < fLine.fP1.fX)) {
221 using std::swap;
222 swap(fLine.fP1, fLine.fP2);
223 }
224 }
225 }
226}
227
228void GrShape::simplifyPoint(const SkPoint& point, unsigned flags) {
229 if (flags & kSimpleFill_Flag) {
230 this->setType(Type::kEmpty);
231 } else if (!this->isPoint()) {
232 this->setType(Type::kPoint);
233 fPoint = point;
234 } else {
235 // If starting as a point, the provided position should already be set
236 SkASSERT(point == fPoint);
237 }
238}
239
240bool GrShape::simplify(unsigned flags) {
241 // Verify that winding parameters are valid for the current type.
242 SkASSERT((fType == Type::kRect || fType == Type::kRRect) ||
243 (this->dir() == kDefaultDir && this->startIndex() == kDefaultStart));
244
245 // The type specific functions automatically fall through to the simpler shapes, so
246 // we only need to start in the right place.
247 bool wasClosed = false;
248 switch(fType) {
249 case Type::kEmpty:
250 // do nothing
251 break;
252 case Type::kPoint:
253 this->simplifyPoint(fPoint, flags);
254 break;
255 case Type::kLine:
256 this->simplifyLine(fLine.fP1, fLine.fP2, flags);
257 break;
258 case Type::kRect:
259 this->simplifyRect(fRect, this->dir(), this->startIndex(), flags);
260 wasClosed = true;
261 break;
262 case Type::kRRect:
263 this->simplifyRRect(fRRect, this->dir(), this->startIndex(), flags);
264 wasClosed = true;
265 break;
266 case Type::kPath:
267 wasClosed = this->simplifyPath(flags);
268 break;
269 case Type::kArc:
270 wasClosed = this->simplifyArc(flags);
271 break;
272
273 default:
274 SkUNREACHABLE;
275 }
276
277 if (((flags & kIgnoreWinding_Flag) || (fType != Type::kRect && fType != Type::kRRect))) {
278 // Reset winding parameters if we don't need them anymore
279 this->setPathWindingParams(kDefaultDir, kDefaultStart);
280 }
281
282 return wasClosed;
283}
284
285bool GrShape::contains(const SkRect& rect) const {
286 switch(this->type()) {
287 case Type::kEmpty:
288 case Type::kPoint: // fall through since a point has 0 area
289 case Type::kLine: // fall through, "" (currently choosing not to test if 'rect' == line)
290 return false;
291 case Type::kRect:
292 return fRect.contains(rect);
293 case Type::kRRect:
294 return fRRect.contains(rect);
295 case Type::kPath:
296 return fPath.conservativelyContainsRect(rect);
297 case Type::kArc:
298 if (fArc.fUseCenter) {
299 SkPath arc;
300 this->asPath(&arc);
301 return arc.conservativelyContainsRect(rect);
302 } else {
303 return false;
304 }
305 default:
306 SkUNREACHABLE;
307 }
308}
309
310bool GrShape::closed() const {
311 switch(this->type()) {
312 case Type::kEmpty: // fall through
313 case Type::kRect: // fall through
314 case Type::kRRect:
315 return true;
316 case Type::kPath:
317 // SkPath doesn't keep track of the closed status of each contour.
318 return SkPathPriv::IsClosedSingleContour(fPath);
319 case Type::kArc:
320 return fArc.fUseCenter;
321 case Type::kPoint: // fall through
322 case Type::kLine:
323 return false;
324 default:
325 SkUNREACHABLE;
326 }
327}
328
329bool GrShape::convex(bool simpleFill) const {
330 switch(this->type()) {
331 case Type::kEmpty: // fall through
332 case Type::kRect: // fall through
333 case Type::kRRect:
334 return true;
335 case Type::kPath:
336 // SkPath.isConvex() really means "is this path convex were it to be closed".
337 // Convex paths may only have one contour hence isLastContourClosed() is sufficient.
338 return (simpleFill || fPath.isLastContourClosed()) && fPath.isConvex();
339 case Type::kArc:
340 return SkPathPriv::DrawArcIsConvex(fArc.fSweepAngle, fArc.fUseCenter, simpleFill);
341 case Type::kPoint: // fall through
342 case Type::kLine:
343 return false;
344 default:
345 SkUNREACHABLE;
346 }
347}
348
349SkRect GrShape::bounds() const {
350 // Bounds where left == bottom or top == right can indicate a line or point shape. We return
351 // inverted bounds for a truly empty shape.
352 static constexpr SkRect kInverted = SkRect::MakeLTRB(1, 1, -1, -1);
353 switch(this->type()) {
354 case Type::kEmpty:
355 return kInverted;
356 case Type::kPoint:
357 return {fPoint.fX, fPoint.fY, fPoint.fX, fPoint.fY};
358 case Type::kRect:
359 return fRect.makeSorted();
360 case Type::kRRect:
361 return fRRect.getBounds();
362 case Type::kPath:
363 return fPath.getBounds();
364 case Type::kArc:
365 return fArc.fOval;
366 case Type::kLine: {
367 SkRect b = SkRect::MakeLTRB(fLine.fP1.fX, fLine.fP1.fY,
368 fLine.fP2.fX, fLine.fP2.fY);
369 b.sort();
370 return b; }
371 default:
372 SkUNREACHABLE;
373 }
374}
375
376uint32_t GrShape::segmentMask() const {
377 // In order to match what a path would report, this has to inspect the shapes slightly
378 // to reflect what they might simplify to.
379 switch(this->type()) {
380 case Type::kEmpty:
381 return 0;
382 case Type::kRRect:
383 if (fRRect.isEmpty() || fRRect.isRect()) {
384 return SkPath::kLine_SegmentMask;
385 } else if (fRRect.isOval()) {
386 return SkPath::kConic_SegmentMask;
387 } else {
388 return SkPath::kConic_SegmentMask | SkPath::kLine_SegmentMask;
389 }
390 case Type::kPath:
391 return fPath.getSegmentMasks();
392 case Type::kArc:
393 if (fArc.fUseCenter) {
394 return SkPath::kConic_SegmentMask | SkPath::kLine_SegmentMask;
395 } else {
396 return SkPath::kConic_SegmentMask;
397 }
398 case Type::kPoint: // fall through
399 case Type::kLine: // ""
400 case Type::kRect:
401 return SkPath::kLine_SegmentMask;
402 default:
403 SkUNREACHABLE;
404 }
405}
406
407void GrShape::asPath(SkPath* out, bool simpleFill) const {
408 if (!this->isPath() && !this->isArc()) {
409 // When not a path, we need to set fill type on the path to match invertedness.
410 // All the non-path geometries produce equivalent shapes with either even-odd or winding
411 // so we can use the default fill type.
412 out->reset();
413 out->setFillType(kDefaultFillType);
414 if (fInverted) {
415 out->toggleInverseFillType();
416 }
417 } // Else when we're already a path, that will assign the fill type directly to 'out'.
418
419 switch(this->type()) {
420 case Type::kEmpty:
421 return;
422 case Type::kPoint:
423 // A plain moveTo() or moveTo+close() does not match the expected path for a
424 // point that is being dashed (see SkDashPath's handling of zero-length segments).
425 out->moveTo(fPoint);
426 out->lineTo(fPoint);
427 return;
428 case Type::kRect:
429 out->addRect(fRect, this->dir(), this->startIndex());
430 return;
431 case Type::kRRect:
432 out->addRRect(fRRect, this->dir(), this->startIndex());
433 return;
434 case Type::kPath:
435 *out = fPath;
436 return;
437 case Type::kArc:
438 SkPathPriv::CreateDrawArcPath(out, fArc.fOval, fArc.fStartAngle, fArc.fSweepAngle,
439 fArc.fUseCenter, simpleFill);
440 // CreateDrawArcPath resets the output path and configures its fill type, so we just
441 // have to ensure invertedness is correct.
442 if (fInverted) {
443 out->toggleInverseFillType();
444 }
445 return;
446 case Type::kLine:
447 out->moveTo(fLine.fP1);
448 out->lineTo(fLine.fP2);
449 return;
450 default:
451 SkUNREACHABLE;
452 }
453}
454