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