Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
GrAAConvexTessellator.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2015 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
9
11#include "include/core/SkPath.h"
13#include "include/core/SkRect.h"
17#include "src/core/SkPathPriv.h"
19
20#include <algorithm>
21
22// Next steps:
23// add an interactive sample app slide
24// add debug check that all points are suitably far apart
25// test more degenerate cases
26
27// The tolerance for fusing vertices and eliminating colinear lines (It is in device space).
28static constexpr SkScalar kClose = (SK_Scalar1 / 16);
29static constexpr SkScalar kCloseSqd = kClose * kClose;
30
31// tesselation tolerance values, in device space pixels
32static constexpr SkScalar kQuadTolerance = 0.2f;
33static constexpr SkScalar kCubicTolerance = 0.2f;
36static constexpr SkScalar kConicTolerance = 0.25f;
37
38// dot product below which we use a round cap between curve segments
39static constexpr SkScalar kRoundCapThreshold = 0.8f;
40
41// dot product above which we consider two adjacent curves to be part of the "same" curve
42static constexpr SkScalar kCurveConnectionThreshold = 0.8f;
43
44static bool intersect(const SkPoint& p0, const SkPoint& n0,
45 const SkPoint& p1, const SkPoint& n1,
46 SkScalar* t) {
47 const SkPoint v = p1 - p0;
48 SkScalar perpDot = n0.fX * n1.fY - n0.fY * n1.fX;
49 if (SkScalarNearlyZero(perpDot)) {
50 return false;
51 }
52 *t = (v.fX * n1.fY - v.fY * n1.fX) / perpDot;
53 return SkIsFinite(*t);
54}
55
56// This is a special case version of intersect where we have the vector
57// perpendicular to the second line rather than the vector parallel to it.
58static bool perp_intersect(const SkPoint& p0, const SkPoint& n0,
59 const SkPoint& p1, const SkPoint& perp,
60 SkScalar* t) {
61 const SkPoint v = p1 - p0;
62 SkScalar perpDot = n0.dot(perp);
63 if (SkScalarNearlyZero(perpDot)) {
64 return false;
65 }
66 *t = v.dot(perp) / perpDot;
67 return SkIsFinite(*t);
68}
69
70static bool duplicate_pt(const SkPoint& p0, const SkPoint& p1) {
71 SkScalar distSq = SkPointPriv::DistanceToSqd(p0, p1);
72 return distSq < kCloseSqd;
73}
74
76 const SkPoint& c, float* accumError) {
77 // First check distance from b to the infinite line through a, c
78 SkVector aToC = c - a;
79 SkVector n = {aToC.fY, -aToC.fX};
80 n.normalize();
81
82 SkScalar distBToLineAC = SkScalarAbs(n.dot(b) - n.dot(a));
83 if (*accumError + distBToLineAC >= kClose || aToC.dot(b - a) <= 0.f || aToC.dot(c - b) <= 0.f) {
84 // Too far from the line or not between the line segment from a to c
85 return false;
86 } else {
87 // Accumulate the distance from b to |ac| that goes "away" when this near-colinear point
88 // is removed to simplify the path.
89 *accumError += distBToLineAC;
90 return true;
91 }
92}
93
94int GrAAConvexTessellator::addPt(const SkPoint& pt,
95 SkScalar depth,
96 SkScalar coverage,
97 bool movable,
98 CurveState curve) {
99 SkASSERT(pt.isFinite());
100 this->validate();
101
102 int index = fPts.size();
103 *fPts.append() = pt;
104 *fCoverages.append() = coverage;
105 *fMovable.append() = movable;
106 *fCurveState.append() = curve;
107
108 this->validate();
109 return index;
110}
111
112void GrAAConvexTessellator::popLastPt() {
113 this->validate();
114
115 fPts.pop_back();
116 fCoverages.pop_back();
117 fMovable.pop_back();
118 fCurveState.pop_back();
119
120 this->validate();
121}
122
123void GrAAConvexTessellator::popFirstPtShuffle() {
124 this->validate();
125
126 fPts.removeShuffle(0);
127 fCoverages.removeShuffle(0);
128 fMovable.removeShuffle(0);
129 fCurveState.removeShuffle(0);
130
131 this->validate();
132}
133
134void GrAAConvexTessellator::updatePt(int index,
135 const SkPoint& pt,
136 SkScalar depth,
137 SkScalar coverage) {
138 this->validate();
139 SkASSERT(fMovable[index]);
140
141 fPts[index] = pt;
142 fCoverages[index] = coverage;
143}
144
145void GrAAConvexTessellator::addTri(int i0, int i1, int i2) {
146 if (i0 == i1 || i1 == i2 || i2 == i0) {
147 return;
148 }
149
150 *fIndices.append() = i0;
151 *fIndices.append() = i1;
152 *fIndices.append() = i2;
153}
154
156 fPts.clear();
157 fCoverages.clear();
158 fMovable.clear();
159 fIndices.clear();
160 fNorms.clear();
161 fCurveState.clear();
162 fInitialRing.rewind();
163 fCandidateVerts.rewind();
164#if GR_AA_CONVEX_TESSELLATOR_VIZ
165 fRings.rewind(); // TODO: leak in this case!
166#else
167 fRings[0].rewind();
168 fRings[1].rewind();
169#endif
170}
171
172void GrAAConvexTessellator::computeNormals() {
173 auto normalToVector = [this](SkVector v) {
174 SkVector n = SkPointPriv::MakeOrthog(v, fSide);
177 return n;
178 };
179
180 // Check the cross product of the final trio
181 fNorms.append(fPts.size());
182 fNorms[0] = fPts[1] - fPts[0];
183 fNorms.back() = fPts[0] - fPts.back();
184 SkScalar cross = SkPoint::CrossProduct(fNorms[0], fNorms.back());
185 fSide = (cross > 0.0f) ? SkPointPriv::kRight_Side : SkPointPriv::kLeft_Side;
186 fNorms[0] = normalToVector(fNorms[0]);
187 for (int cur = 1; cur < fNorms.size() - 1; ++cur) {
188 fNorms[cur] = normalToVector(fPts[cur + 1] - fPts[cur]);
189 }
190 fNorms.back() = normalToVector(fNorms.back());
191}
192
193void GrAAConvexTessellator::computeBisectors() {
194 fBisectors.resize(fNorms.size());
195
196 int prev = fBisectors.size() - 1;
197 for (int cur = 0; cur < fBisectors.size(); prev = cur, ++cur) {
198 fBisectors[cur] = fNorms[cur] + fNorms[prev];
199 if (!fBisectors[cur].normalize()) {
200 fBisectors[cur] = SkPointPriv::MakeOrthog(fNorms[cur], (SkPointPriv::Side)-fSide) +
201 SkPointPriv::MakeOrthog(fNorms[prev], fSide);
202 SkAssertResult(fBisectors[cur].normalize());
203 } else {
204 fBisectors[cur].negate(); // make the bisector face in
205 }
206 if (fCurveState[prev] == kIndeterminate_CurveState) {
207 if (fCurveState[cur] == kSharp_CurveState) {
208 fCurveState[prev] = kSharp_CurveState;
209 } else {
210 if (SkScalarAbs(fNorms[cur].dot(fNorms[prev])) > kCurveConnectionThreshold) {
211 fCurveState[prev] = kCurve_CurveState;
212 fCurveState[cur] = kCurve_CurveState;
213 } else {
214 fCurveState[prev] = kSharp_CurveState;
215 fCurveState[cur] = kSharp_CurveState;
216 }
217 }
218 }
219
220 SkASSERT(SkScalarNearlyEqual(1.0f, fBisectors[cur].length()));
221 }
222}
223
224// Create as many rings as we need to (up to a predefined limit) to reach the specified target
225// depth. If we are in fill mode, the final ring will automatically be fanned.
226bool GrAAConvexTessellator::createInsetRings(Ring& previousRing, SkScalar initialDepth,
227 SkScalar initialCoverage, SkScalar targetDepth,
228 SkScalar targetCoverage, Ring** finalRing) {
229 static const int kMaxNumRings = 8;
230
231 if (previousRing.numPts() < 3) {
232 return false;
233 }
234 Ring* currentRing = &previousRing;
235 int i;
236 for (i = 0; i < kMaxNumRings; ++i) {
237 Ring* nextRing = this->getNextRing(currentRing);
238 SkASSERT(nextRing != currentRing);
239
240 bool done = this->createInsetRing(*currentRing, nextRing, initialDepth, initialCoverage,
241 targetDepth, targetCoverage, i == 0);
242 currentRing = nextRing;
243 if (done) {
244 break;
245 }
246 currentRing->init(*this);
247 }
248
249 if (kMaxNumRings == i) {
250 // Bail if we've exceeded the amount of time we want to throw at this.
251 this->terminate(*currentRing);
252 return false;
253 }
254 bool done = currentRing->numPts() >= 3;
255 if (done) {
256 currentRing->init(*this);
257 }
258 *finalRing = currentRing;
259 return done;
260}
261
262// The general idea here is to, conceptually, start with the original polygon and slide
263// the vertices along the bisectors until the first intersection. At that
264// point two of the edges collapse and the process repeats on the new polygon.
265// The polygon state is captured in the Ring class while the GrAAConvexTessellator
266// controls the iteration. The CandidateVerts holds the formative points for the
267// next ring.
269 if (!this->extractFromPath(m, path)) {
270 return false;
271 }
272
273 SkScalar coverage = 1.0f;
274 SkScalar scaleFactor = 0.0f;
275
276 if (SkStrokeRec::kStrokeAndFill_Style == fStyle) {
277 SkASSERT(m.isSimilarity());
278 scaleFactor = m.getMaxScale(); // x and y scale are the same
279 SkScalar effectiveStrokeWidth = scaleFactor * fStrokeWidth;
280 Ring outerStrokeAndAARing;
281 this->createOuterRing(fInitialRing,
282 effectiveStrokeWidth / 2 + kAntialiasingRadius, 0.0,
283 &outerStrokeAndAARing);
284
285 // discard all the triangles added between the originating ring and the new outer ring
286 fIndices.clear();
287
288 outerStrokeAndAARing.init(*this);
289
290 outerStrokeAndAARing.makeOriginalRing();
291
292 // Add the outer stroke ring's normals to the originating ring's normals
293 // so it can also act as an originating ring
294 fNorms.resize(fNorms.size() + outerStrokeAndAARing.numPts());
295 for (int i = 0; i < outerStrokeAndAARing.numPts(); ++i) {
296 SkASSERT(outerStrokeAndAARing.index(i) < fNorms.size());
297 fNorms[outerStrokeAndAARing.index(i)] = outerStrokeAndAARing.norm(i);
298 }
299
300 // the bisectors are only needed for the computation of the outer ring
301 fBisectors.clear();
302
303 Ring* insetAARing;
304 this->createInsetRings(outerStrokeAndAARing,
305 0.0f, 0.0f, 2*kAntialiasingRadius, 1.0f,
306 &insetAARing);
307
308 SkDEBUGCODE(this->validate();)
309 return true;
310 }
311
312 if (SkStrokeRec::kStroke_Style == fStyle) {
313 SkASSERT(fStrokeWidth >= 0.0f);
314 SkASSERT(m.isSimilarity());
315 scaleFactor = m.getMaxScale(); // x and y scale are the same
316 SkScalar effectiveStrokeWidth = scaleFactor * fStrokeWidth;
317 Ring outerStrokeRing;
318 this->createOuterRing(fInitialRing, effectiveStrokeWidth / 2 - kAntialiasingRadius,
319 coverage, &outerStrokeRing);
320 outerStrokeRing.init(*this);
321 Ring outerAARing;
322 this->createOuterRing(outerStrokeRing, kAntialiasingRadius * 2, 0.0f, &outerAARing);
323 } else {
324 Ring outerAARing;
325 this->createOuterRing(fInitialRing, kAntialiasingRadius, 0.0f, &outerAARing);
326 }
327
328 // the bisectors are only needed for the computation of the outer ring
329 fBisectors.clear();
330 if (SkStrokeRec::kStroke_Style == fStyle && fInitialRing.numPts() > 2) {
331 SkASSERT(fStrokeWidth >= 0.0f);
332 SkScalar effectiveStrokeWidth = scaleFactor * fStrokeWidth;
333 Ring* insetStrokeRing;
334 SkScalar strokeDepth = effectiveStrokeWidth / 2 - kAntialiasingRadius;
335 if (this->createInsetRings(fInitialRing, 0.0f, coverage, strokeDepth, coverage,
336 &insetStrokeRing)) {
337 Ring* insetAARing;
338 this->createInsetRings(*insetStrokeRing, strokeDepth, coverage, strokeDepth +
339 kAntialiasingRadius * 2, 0.0f, &insetAARing);
340 }
341 } else {
342 Ring* insetAARing;
343 this->createInsetRings(fInitialRing, 0.0f, 0.5f, kAntialiasingRadius, 1.0f, &insetAARing);
344 }
345
346 SkDEBUGCODE(this->validate();)
347 return true;
348}
349
350SkScalar GrAAConvexTessellator::computeDepthFromEdge(int edgeIdx, const SkPoint& p) const {
351 SkASSERT(edgeIdx < fNorms.size());
352
353 SkPoint v = p - fPts[edgeIdx];
354 SkScalar depth = -fNorms[edgeIdx].dot(v);
355 return depth;
356}
357
358// Find a point that is 'desiredDepth' away from the 'edgeIdx'-th edge and lies
359// along the 'bisector' from the 'startIdx'-th point.
360bool GrAAConvexTessellator::computePtAlongBisector(int startIdx,
361 const SkVector& bisector,
362 int edgeIdx,
363 SkScalar desiredDepth,
364 SkPoint* result) const {
365 const SkPoint& norm = fNorms[edgeIdx];
366
367 // First find the point where the edge and the bisector intersect
368 SkPoint newP;
369
370 SkScalar t;
371 if (!perp_intersect(fPts[startIdx], bisector, fPts[edgeIdx], norm, &t)) {
372 return false;
373 }
374 if (SkScalarNearlyEqual(t, 0.0f)) {
375 // the start point was one of the original ring points
376 SkASSERT(startIdx < fPts.size());
377 newP = fPts[startIdx];
378 } else if (t < 0.0f) {
379 newP = bisector;
380 newP.scale(t);
381 newP += fPts[startIdx];
382 } else {
383 return false;
384 }
385
386 // Then offset along the bisector from that point the correct distance
387 SkScalar dot = bisector.dot(norm);
388 t = -desiredDepth / dot;
389 *result = bisector;
390 result->scale(t);
391 *result += newP;
392
393 return true;
394}
395
396bool GrAAConvexTessellator::extractFromPath(const SkMatrix& m, const SkPath& path) {
397 SkASSERT(path.isConvex());
398
399 SkRect bounds = path.getBounds();
400 m.mapRect(&bounds);
401 if (!bounds.isFinite()) {
402 // We could do something smarter here like clip the path based on the bounds of the dst.
403 // We'd have to be careful about strokes to ensure we don't draw something wrong.
404 return false;
405 }
406
407 // Outer ring: 3*numPts
408 // Middle ring: numPts
409 // Presumptive inner ring: numPts
410 this->reservePts(5*path.countPoints());
411 // Outer ring: 12*numPts
412 // Middle ring: 0
413 // Presumptive inner ring: 6*numPts + 6
414 fIndices.reserve(18*path.countPoints() + 6);
415
416 // Reset the accumulated error for all the future lineTo() calls when iterating over the path.
417 fAccumLinearError = 0.f;
418 // TODO: is there a faster way to extract the points from the path? Perhaps
419 // get all the points via a new entry point, transform them all in bulk
420 // and then walk them to find duplicates?
421 SkPathEdgeIter iter(path);
422 while (auto e = iter.next()) {
423 switch (e.fEdge) {
424 case SkPathEdgeIter::Edge::kLine:
425 if (!SkPathPriv::AllPointsEq(e.fPts, 2)) {
426 this->lineTo(m, e.fPts[1], kSharp_CurveState);
427 }
428 break;
429 case SkPathEdgeIter::Edge::kQuad:
430 if (!SkPathPriv::AllPointsEq(e.fPts, 3)) {
431 this->quadTo(m, e.fPts);
432 }
433 break;
434 case SkPathEdgeIter::Edge::kCubic:
435 if (!SkPathPriv::AllPointsEq(e.fPts, 4)) {
436 this->cubicTo(m, e.fPts);
437 }
438 break;
439 case SkPathEdgeIter::Edge::kConic:
440 if (!SkPathPriv::AllPointsEq(e.fPts, 3)) {
441 this->conicTo(m, e.fPts, iter.conicWeight());
442 }
443 break;
444 }
445 }
446
447 if (this->numPts() < 2) {
448 return false;
449 }
450
451 // check if last point is a duplicate of the first point. If so, remove it.
452 if (duplicate_pt(fPts[this->numPts()-1], fPts[0])) {
453 this->popLastPt();
454 }
455
456 // Remove any lingering colinear points where the path wraps around
457 fAccumLinearError = 0.f;
458 bool noRemovalsToDo = false;
459 while (!noRemovalsToDo && this->numPts() >= 3) {
460 if (points_are_colinear_and_b_is_middle(fPts[fPts.size() - 2], fPts.back(), fPts[0],
461 &fAccumLinearError)) {
462 this->popLastPt();
463 } else if (points_are_colinear_and_b_is_middle(fPts.back(), fPts[0], fPts[1],
464 &fAccumLinearError)) {
465 this->popFirstPtShuffle();
466 } else {
467 noRemovalsToDo = true;
468 }
469 }
470
471 // Compute the normals and bisectors.
472 SkASSERT(fNorms.empty());
473 if (this->numPts() >= 3) {
474 this->computeNormals();
475 this->computeBisectors();
476 } else if (this->numPts() == 2) {
477 // We've got two points, so we're degenerate.
478 if (fStyle == SkStrokeRec::kFill_Style) {
479 // it's a fill, so we don't need to worry about degenerate paths
480 return false;
481 }
482 // For stroking, we still need to process the degenerate path, so fix it up
484
485 fNorms.append(2);
486 fNorms[0] = SkPointPriv::MakeOrthog(fPts[1] - fPts[0], fSide);
487 fNorms[0].normalize();
488 fNorms[1] = -fNorms[0];
489 SkASSERT(SkScalarNearlyEqual(1.0f, fNorms[0].length()));
490 // we won't actually use the bisectors, so just push zeroes
491 fBisectors.push_back(SkPoint::Make(0.0, 0.0));
492 fBisectors.push_back(SkPoint::Make(0.0, 0.0));
493 } else {
494 return false;
495 }
496
497 fCandidateVerts.setReserve(this->numPts());
498 fInitialRing.setReserve(this->numPts());
499 for (int i = 0; i < this->numPts(); ++i) {
500 fInitialRing.addIdx(i, i);
501 }
502 fInitialRing.init(fNorms, fBisectors);
503
504 this->validate();
505 return true;
506}
507
508GrAAConvexTessellator::Ring* GrAAConvexTessellator::getNextRing(Ring* lastRing) {
509#if GR_AA_CONVEX_TESSELLATOR_VIZ
510 Ring* ring = *fRings.push() = new Ring;
511 ring->setReserve(fInitialRing.numPts());
512 ring->rewind();
513 return ring;
514#else
515 // Flip flop back and forth between fRings[0] & fRings[1]
516 int nextRing = (lastRing == &fRings[0]) ? 1 : 0;
517 fRings[nextRing].setReserve(fInitialRing.numPts());
518 fRings[nextRing].rewind();
519 return &fRings[nextRing];
520#endif
521}
522
523void GrAAConvexTessellator::fanRing(const Ring& ring) {
524 // fan out from point 0
525 int startIdx = ring.index(0);
526 for (int cur = ring.numPts() - 2; cur >= 0; --cur) {
527 this->addTri(startIdx, ring.index(cur), ring.index(cur + 1));
528 }
529}
530
531void GrAAConvexTessellator::createOuterRing(const Ring& previousRing, SkScalar outset,
532 SkScalar coverage, Ring* nextRing) {
533 const int numPts = previousRing.numPts();
534 if (numPts == 0) {
535 return;
536 }
537
538 int prev = numPts - 1;
539 int lastPerpIdx = -1, firstPerpIdx = -1;
540
541 const SkScalar outsetSq = outset * outset;
542 SkScalar miterLimitSq = outset * fMiterLimit;
543 miterLimitSq = miterLimitSq * miterLimitSq;
544 for (int cur = 0; cur < numPts; ++cur) {
545 int originalIdx = previousRing.index(cur);
546 // For each vertex of the original polygon we add at least two points to the
547 // outset polygon - one extending perpendicular to each impinging edge. Connecting these
548 // two points yields a bevel join. We need one additional point for a mitered join, and
549 // a round join requires one or more points depending upon curvature.
550
551 // The perpendicular point for the last edge
552 SkPoint normal1 = previousRing.norm(prev);
553 SkPoint perp1 = normal1;
554 perp1.scale(outset);
555 perp1 += this->point(originalIdx);
556
557 // The perpendicular point for the next edge.
558 SkPoint normal2 = previousRing.norm(cur);
559 SkPoint perp2 = normal2;
560 perp2.scale(outset);
561 perp2 += fPts[originalIdx];
562
563 CurveState curve = fCurveState[originalIdx];
564
565 // We know it isn't a duplicate of the prior point (since it and this
566 // one are just perpendicular offsets from the non-merged polygon points)
567 int perp1Idx = this->addPt(perp1, -outset, coverage, false, curve);
568 nextRing->addIdx(perp1Idx, originalIdx);
569
570 int perp2Idx;
571 // For very shallow angles all the corner points could fuse.
572 if (duplicate_pt(perp2, this->point(perp1Idx))) {
573 perp2Idx = perp1Idx;
574 } else {
575 perp2Idx = this->addPt(perp2, -outset, coverage, false, curve);
576 }
577
578 if (perp2Idx != perp1Idx) {
579 if (curve == kCurve_CurveState) {
580 // bevel or round depending upon curvature
581 SkScalar dotProd = normal1.dot(normal2);
582 if (dotProd < kRoundCapThreshold) {
583 // Currently we "round" by creating a single extra point, which produces
584 // good results for common cases. For thick strokes with high curvature, we will
585 // need to add more points; for the time being we simply fall back to software
586 // rendering for thick strokes.
587 SkPoint miter = previousRing.bisector(cur);
588 miter.setLength(-outset);
589 miter += fPts[originalIdx];
590
591 // For very shallow angles all the corner points could fuse
592 if (!duplicate_pt(miter, this->point(perp1Idx))) {
593 int miterIdx;
594 miterIdx = this->addPt(miter, -outset, coverage, false, kSharp_CurveState);
595 nextRing->addIdx(miterIdx, originalIdx);
596 // The two triangles for the corner
597 this->addTri(originalIdx, perp1Idx, miterIdx);
598 this->addTri(originalIdx, miterIdx, perp2Idx);
599 }
600 } else {
601 this->addTri(originalIdx, perp1Idx, perp2Idx);
602 }
603 } else {
604 switch (fJoin) {
606 // The bisector outset point
607 SkPoint miter = previousRing.bisector(cur);
608 SkScalar dotProd = normal1.dot(normal2);
609 // The max is because this could go slightly negative if precision causes
610 // us to become slightly concave.
611 SkScalar sinHalfAngleSq = std::max(SkScalarHalf(SK_Scalar1 + dotProd), 0.f);
612 SkScalar lengthSq = sk_ieee_float_divide(outsetSq, sinHalfAngleSq);
613 if (lengthSq > miterLimitSq) {
614 // just bevel it
615 this->addTri(originalIdx, perp1Idx, perp2Idx);
616 break;
617 }
618 miter.setLength(-SkScalarSqrt(lengthSq));
619 miter += fPts[originalIdx];
620
621 // For very shallow angles all the corner points could fuse
622 if (!duplicate_pt(miter, this->point(perp1Idx))) {
623 int miterIdx;
624 miterIdx = this->addPt(miter, -outset, coverage, false,
625 kSharp_CurveState);
626 nextRing->addIdx(miterIdx, originalIdx);
627 // The two triangles for the corner
628 this->addTri(originalIdx, perp1Idx, miterIdx);
629 this->addTri(originalIdx, miterIdx, perp2Idx);
630 } else {
631 // ignore the miter point as it's so close to perp1/perp2 and simply
632 // bevel.
633 this->addTri(originalIdx, perp1Idx, perp2Idx);
634 }
635 break;
636 }
638 this->addTri(originalIdx, perp1Idx, perp2Idx);
639 break;
640 default:
641 // kRound_Join is unsupported for now. AALinearizingConvexPathRenderer is
642 // only willing to draw mitered or beveled, so we should never get here.
643 SkASSERT(false);
644 }
645 }
646
647 nextRing->addIdx(perp2Idx, originalIdx);
648 }
649
650 if (0 == cur) {
651 // Store the index of the first perpendicular point to finish up
652 firstPerpIdx = perp1Idx;
653 SkASSERT(-1 == lastPerpIdx);
654 } else {
655 // The triangles for the previous edge
656 int prevIdx = previousRing.index(prev);
657 this->addTri(prevIdx, perp1Idx, originalIdx);
658 this->addTri(prevIdx, lastPerpIdx, perp1Idx);
659 }
660
661 // Track the last perpendicular outset point so we can construct the
662 // trailing edge triangles.
663 lastPerpIdx = perp2Idx;
664 prev = cur;
665 }
666
667 // pick up the final edge rect
668 int lastIdx = previousRing.index(numPts - 1);
669 this->addTri(lastIdx, firstPerpIdx, previousRing.index(0));
670 this->addTri(lastIdx, lastPerpIdx, firstPerpIdx);
671
672 this->validate();
673}
674
675// Something went wrong in the creation of the next ring. If we're filling the shape, just go ahead
676// and fan it.
677void GrAAConvexTessellator::terminate(const Ring& ring) {
678 if (fStyle != SkStrokeRec::kStroke_Style && ring.numPts() > 0) {
679 this->fanRing(ring);
680 }
681}
682
683static SkScalar compute_coverage(SkScalar depth, SkScalar initialDepth, SkScalar initialCoverage,
684 SkScalar targetDepth, SkScalar targetCoverage) {
685 if (SkScalarNearlyEqual(initialDepth, targetDepth)) {
686 return targetCoverage;
687 }
688 SkScalar result = (depth - initialDepth) / (targetDepth - initialDepth) *
689 (targetCoverage - initialCoverage) + initialCoverage;
690 return SkTPin(result, 0.0f, 1.0f);
691}
692
693// return true when processing is complete
694bool GrAAConvexTessellator::createInsetRing(const Ring& lastRing, Ring* nextRing,
695 SkScalar initialDepth, SkScalar initialCoverage,
696 SkScalar targetDepth, SkScalar targetCoverage,
697 bool forceNew) {
698 bool done = false;
699
700 fCandidateVerts.rewind();
701
702 // Loop through all the points in the ring and find the intersection with the smallest depth
703 SkScalar minDist = SK_ScalarMax, minT = 0.0f;
704 int minEdgeIdx = -1;
705
706 for (int cur = 0; cur < lastRing.numPts(); ++cur) {
707 int next = (cur + 1) % lastRing.numPts();
708
709 SkScalar t;
710 bool result = intersect(this->point(lastRing.index(cur)), lastRing.bisector(cur),
711 this->point(lastRing.index(next)), lastRing.bisector(next),
712 &t);
713 // The bisectors may be parallel (!result) or the previous ring may have become slightly
714 // concave due to accumulated error (t <= 0).
715 if (!result || t <= 0) {
716 continue;
717 }
718 SkScalar dist = -t * lastRing.norm(cur).dot(lastRing.bisector(cur));
719
720 if (minDist > dist) {
721 minDist = dist;
722 minT = t;
723 minEdgeIdx = cur;
724 }
725 }
726
727 if (minEdgeIdx == -1) {
728 return false;
729 }
730 SkPoint newPt = lastRing.bisector(minEdgeIdx);
731 newPt.scale(minT);
732 newPt += this->point(lastRing.index(minEdgeIdx));
733
734 SkScalar depth = this->computeDepthFromEdge(lastRing.origEdgeID(minEdgeIdx), newPt);
735 if (depth >= targetDepth) {
736 // None of the bisectors intersect before reaching the desired depth.
737 // Just step them all to the desired depth
738 depth = targetDepth;
739 done = true;
740 }
741
742 // 'dst' stores where each point in the last ring maps to/transforms into
743 // in the next ring.
745 dst.resize(lastRing.numPts());
746
747 // Create the first point (who compares with no one)
748 if (!this->computePtAlongBisector(lastRing.index(0),
749 lastRing.bisector(0),
750 lastRing.origEdgeID(0),
751 depth, &newPt)) {
752 this->terminate(lastRing);
753 return true;
754 }
755 dst[0] = fCandidateVerts.addNewPt(newPt,
756 lastRing.index(0), lastRing.origEdgeID(0),
757 !this->movable(lastRing.index(0)));
758
759 // Handle the middle points (who only compare with the prior point)
760 for (int cur = 1; cur < lastRing.numPts()-1; ++cur) {
761 if (!this->computePtAlongBisector(lastRing.index(cur),
762 lastRing.bisector(cur),
763 lastRing.origEdgeID(cur),
764 depth, &newPt)) {
765 this->terminate(lastRing);
766 return true;
767 }
768 if (!duplicate_pt(newPt, fCandidateVerts.lastPoint())) {
769 dst[cur] = fCandidateVerts.addNewPt(newPt,
770 lastRing.index(cur), lastRing.origEdgeID(cur),
771 !this->movable(lastRing.index(cur)));
772 } else {
773 dst[cur] = fCandidateVerts.fuseWithPrior(lastRing.origEdgeID(cur));
774 }
775 }
776
777 // Check on the last point (handling the wrap around)
778 int cur = lastRing.numPts()-1;
779 if (!this->computePtAlongBisector(lastRing.index(cur),
780 lastRing.bisector(cur),
781 lastRing.origEdgeID(cur),
782 depth, &newPt)) {
783 this->terminate(lastRing);
784 return true;
785 }
786 bool dupPrev = duplicate_pt(newPt, fCandidateVerts.lastPoint());
787 bool dupNext = duplicate_pt(newPt, fCandidateVerts.firstPoint());
788
789 if (!dupPrev && !dupNext) {
790 dst[cur] = fCandidateVerts.addNewPt(newPt,
791 lastRing.index(cur), lastRing.origEdgeID(cur),
792 !this->movable(lastRing.index(cur)));
793 } else if (dupPrev && !dupNext) {
794 dst[cur] = fCandidateVerts.fuseWithPrior(lastRing.origEdgeID(cur));
795 } else if (!dupPrev && dupNext) {
796 dst[cur] = fCandidateVerts.fuseWithNext();
797 } else {
798 bool dupPrevVsNext = duplicate_pt(fCandidateVerts.firstPoint(), fCandidateVerts.lastPoint());
799
800 if (!dupPrevVsNext) {
801 dst[cur] = fCandidateVerts.fuseWithPrior(lastRing.origEdgeID(cur));
802 } else {
803 const int fused = fCandidateVerts.fuseWithBoth();
804 dst[cur] = fused;
805 const int targetIdx = dst[cur - 1];
806 for (int i = cur - 1; i >= 0 && dst[i] == targetIdx; i--) {
807 dst[i] = fused;
808 }
809 }
810 }
811
812 // Fold the new ring's points into the global pool
813 for (int i = 0; i < fCandidateVerts.numPts(); ++i) {
814 int newIdx;
815 if (fCandidateVerts.needsToBeNew(i) || forceNew) {
816 // if the originating index is still valid then this point wasn't
817 // fused (and is thus movable)
818 SkScalar coverage = compute_coverage(depth, initialDepth, initialCoverage,
819 targetDepth, targetCoverage);
820 newIdx = this->addPt(fCandidateVerts.point(i), depth, coverage,
821 fCandidateVerts.originatingIdx(i) != -1, kSharp_CurveState);
822 } else {
823 SkASSERT(fCandidateVerts.originatingIdx(i) != -1);
824 this->updatePt(fCandidateVerts.originatingIdx(i), fCandidateVerts.point(i), depth,
825 targetCoverage);
826 newIdx = fCandidateVerts.originatingIdx(i);
827 }
828
829 nextRing->addIdx(newIdx, fCandidateVerts.origEdge(i));
830 }
831
832 // 'dst' currently has indices into the ring. Remap these to be indices
833 // into the global pool since the triangulation operates in that space.
834 for (int i = 0; i < dst.size(); ++i) {
835 dst[i] = nextRing->index(dst[i]);
836 }
837
838 for (int i = 0; i < lastRing.numPts(); ++i) {
839 int next = (i + 1) % lastRing.numPts();
840
841 this->addTri(lastRing.index(i), lastRing.index(next), dst[next]);
842 this->addTri(lastRing.index(i), dst[next], dst[i]);
843 }
844
845 if (done && fStyle != SkStrokeRec::kStroke_Style) {
846 // fill or stroke-and-fill
847 this->fanRing(*nextRing);
848 }
849
850 if (nextRing->numPts() < 3) {
851 done = true;
852 }
853 return done;
854}
855
856void GrAAConvexTessellator::validate() const {
857 SkASSERT(fPts.size() == fMovable.size());
858 SkASSERT(fPts.size() == fCoverages.size());
859 SkASSERT(fPts.size() == fCurveState.size());
860 SkASSERT(0 == (fIndices.size() % 3));
861 SkASSERT(fBisectors.empty() || fBisectors.size() == fNorms.size());
862}
863
864//////////////////////////////////////////////////////////////////////////////
865void GrAAConvexTessellator::Ring::init(const GrAAConvexTessellator& tess) {
866 this->computeNormals(tess);
867 this->computeBisectors(tess);
868}
869
870void GrAAConvexTessellator::Ring::init(const SkTDArray<SkVector>& norms,
871 const SkTDArray<SkVector>& bisectors) {
872 for (int i = 0; i < fPts.size(); ++i) {
873 fPts[i].fNorm = norms[i];
874 fPts[i].fBisector = bisectors[i];
875 }
876}
877
878// Compute the outward facing normal at each vertex.
879void GrAAConvexTessellator::Ring::computeNormals(const GrAAConvexTessellator& tess) {
880 for (int cur = 0; cur < fPts.size(); ++cur) {
881 int next = (cur + 1) % fPts.size();
882
883 fPts[cur].fNorm = tess.point(fPts[next].fIndex) - tess.point(fPts[cur].fIndex);
884 SkPoint::Normalize(&fPts[cur].fNorm);
885 fPts[cur].fNorm = SkPointPriv::MakeOrthog(fPts[cur].fNorm, tess.side());
886 }
887}
888
889void GrAAConvexTessellator::Ring::computeBisectors(const GrAAConvexTessellator& tess) {
890 int prev = fPts.size() - 1;
891 for (int cur = 0; cur < fPts.size(); prev = cur, ++cur) {
892 fPts[cur].fBisector = fPts[cur].fNorm + fPts[prev].fNorm;
893 if (!fPts[cur].fBisector.normalize()) {
894 fPts[cur].fBisector =
895 SkPointPriv::MakeOrthog(fPts[cur].fNorm, (SkPointPriv::Side)-tess.side()) +
896 SkPointPriv::MakeOrthog(fPts[prev].fNorm, tess.side());
897 SkAssertResult(fPts[cur].fBisector.normalize());
898 } else {
899 fPts[cur].fBisector.negate(); // make the bisector face in
900 }
901 }
902}
903
904//////////////////////////////////////////////////////////////////////////////
905#ifdef SK_DEBUG
906// Is this ring convex?
907bool GrAAConvexTessellator::Ring::isConvex(const GrAAConvexTessellator& tess) const {
908 if (fPts.size() < 3) {
909 return true;
910 }
911
912 SkPoint prev = tess.point(fPts[0].fIndex) - tess.point(fPts.back().fIndex);
913 SkPoint cur = tess.point(fPts[1].fIndex) - tess.point(fPts[0].fIndex);
914 SkScalar minDot = prev.fX * cur.fY - prev.fY * cur.fX;
915 SkScalar maxDot = minDot;
916
917 prev = cur;
918 for (int i = 1; i < fPts.size(); ++i) {
919 int next = (i + 1) % fPts.size();
920
921 cur = tess.point(fPts[next].fIndex) - tess.point(fPts[i].fIndex);
922 SkScalar dot = prev.fX * cur.fY - prev.fY * cur.fX;
923
924 minDot = std::min(minDot, dot);
925 maxDot = std::max(maxDot, dot);
926
927 prev = cur;
928 }
929
930 if (SkScalarNearlyEqual(maxDot, 0.0f, 0.005f)) {
931 maxDot = 0;
932 }
933 if (SkScalarNearlyEqual(minDot, 0.0f, 0.005f)) {
934 minDot = 0;
935 }
936 return (maxDot >= 0.0f) == (minDot >= 0.0f);
937}
938
939#endif
940
941void GrAAConvexTessellator::lineTo(const SkPoint& p, CurveState curve) {
942 if (this->numPts() > 0 && duplicate_pt(p, this->lastPoint())) {
943 return;
944 }
945
946 if (this->numPts() >= 2 &&
947 points_are_colinear_and_b_is_middle(fPts[fPts.size() - 2], fPts.back(), p,
948 &fAccumLinearError)) {
949 // The old last point is on the line from the second to last to the new point
950 this->popLastPt();
951 // double-check that the new last point is not a duplicate of the new point. In an ideal
952 // world this wouldn't be necessary (since it's only possible for non-convex paths), but
953 // floating point precision issues mean it can actually happen on paths that were
954 // determined to be convex.
955 if (duplicate_pt(p, this->lastPoint())) {
956 return;
957 }
958 } else {
959 fAccumLinearError = 0.f;
960 }
961 SkScalar initialRingCoverage = (SkStrokeRec::kFill_Style == fStyle) ? 0.5f : 1.0f;
962 this->addPt(p, 0.0f, initialRingCoverage, false, curve);
963}
964
965void GrAAConvexTessellator::lineTo(const SkMatrix& m, const SkPoint& p, CurveState curve) {
966 this->lineTo(m.mapXY(p.fX, p.fY), curve);
967}
968
969void GrAAConvexTessellator::quadTo(const SkPoint pts[3]) {
971 fPointBuffer.resize(maxCount);
972 SkPoint* target = fPointBuffer.begin();
973 int count = GrPathUtils::generateQuadraticPoints(pts[0], pts[1], pts[2],
974 kQuadToleranceSqd, &target, maxCount);
975 fPointBuffer.resize(count);
976 for (int i = 0; i < count - 1; i++) {
977 this->lineTo(fPointBuffer[i], kCurve_CurveState);
978 }
979 this->lineTo(fPointBuffer[count - 1],
980 count == 1 ? kSharp_CurveState : kIndeterminate_CurveState);
981}
982
983void GrAAConvexTessellator::quadTo(const SkMatrix& m, const SkPoint srcPts[3]) {
984 SkPoint pts[3];
985 m.mapPoints(pts, srcPts, 3);
986 this->quadTo(pts);
987}
988
989void GrAAConvexTessellator::cubicTo(const SkMatrix& m, const SkPoint srcPts[4]) {
990 SkPoint pts[4];
991 m.mapPoints(pts, srcPts, 4);
993 fPointBuffer.resize(maxCount);
994 SkPoint* target = fPointBuffer.begin();
995 int count = GrPathUtils::generateCubicPoints(pts[0], pts[1], pts[2], pts[3],
996 kCubicToleranceSqd, &target, maxCount);
997 fPointBuffer.resize(count);
998 for (int i = 0; i < count - 1; i++) {
999 this->lineTo(fPointBuffer[i], kCurve_CurveState);
1000 }
1001 this->lineTo(fPointBuffer[count - 1],
1002 count == 1 ? kSharp_CurveState : kIndeterminate_CurveState);
1003}
1004
1005// include down here to avoid compilation errors caused by "-" overload in SkGeometry.h
1006#include "src/core/SkGeometry.h"
1007
1008void GrAAConvexTessellator::conicTo(const SkMatrix& m, const SkPoint srcPts[3], SkScalar w) {
1009 SkPoint pts[3];
1010 m.mapPoints(pts, srcPts, 3);
1011 SkAutoConicToQuads quadder;
1012 const SkPoint* quads = quadder.computeQuads(pts, w, kConicTolerance);
1013 SkPoint lastPoint = *(quads++);
1014 int count = quadder.countQuads();
1015 for (int i = 0; i < count; ++i) {
1016 SkPoint quadPts[3];
1017 quadPts[0] = lastPoint;
1018 quadPts[1] = quads[0];
1019 quadPts[2] = i == count - 1 ? pts[2] : quads[1];
1020 this->quadTo(quadPts);
1021 lastPoint = quadPts[2];
1022 quads += 2;
1023 }
1024}
1025
1026//////////////////////////////////////////////////////////////////////////////
1027#if GR_AA_CONVEX_TESSELLATOR_VIZ
1028static const SkScalar kPointRadius = 0.02f;
1029static const SkScalar kArrowStrokeWidth = 0.0f;
1030static const SkScalar kArrowLength = 0.2f;
1031static const SkScalar kEdgeTextSize = 0.1f;
1032static const SkScalar kPointTextSize = 0.02f;
1033
1034static void draw_point(SkCanvas* canvas, const SkPoint& p, SkScalar paramValue, bool stroke) {
1035 SkPaint paint;
1036 SkASSERT(paramValue <= 1.0f);
1037 int gs = int(255*paramValue);
1038 paint.setARGB(255, gs, gs, gs);
1039
1040 canvas->drawCircle(p.fX, p.fY, kPointRadius, paint);
1041
1042 if (stroke) {
1043 SkPaint stroke;
1044 stroke.setColor(SK_ColorYELLOW);
1046 stroke.setStrokeWidth(kPointRadius/3.0f);
1047 canvas->drawCircle(p.fX, p.fY, kPointRadius, stroke);
1048 }
1049}
1050
1051static void draw_line(SkCanvas* canvas, const SkPoint& p0, const SkPoint& p1, SkColor color) {
1052 SkPaint p;
1053 p.setColor(color);
1054
1055 canvas->drawLine(p0.fX, p0.fY, p1.fX, p1.fY, p);
1056}
1057
1058static void draw_arrow(SkCanvas*canvas, const SkPoint& p, const SkPoint &n,
1059 SkScalar len, SkColor color) {
1060 SkPaint paint;
1061 paint.setColor(color);
1062 paint.setStrokeWidth(kArrowStrokeWidth);
1063 paint.setStyle(SkPaint::kStroke_Style);
1064
1065 canvas->drawLine(p.fX, p.fY,
1066 p.fX + len * n.fX, p.fY + len * n.fY,
1067 paint);
1068}
1069
1070void GrAAConvexTessellator::Ring::draw(SkCanvas* canvas, const GrAAConvexTessellator& tess) const {
1071 SkPaint paint;
1072 paint.setTextSize(kEdgeTextSize);
1073
1074 for (int cur = 0; cur < fPts.count(); ++cur) {
1075 int next = (cur + 1) % fPts.count();
1076
1077 draw_line(canvas,
1078 tess.point(fPts[cur].fIndex),
1079 tess.point(fPts[next].fIndex),
1081
1082 SkPoint mid = tess.point(fPts[cur].fIndex) + tess.point(fPts[next].fIndex);
1083 mid.scale(0.5f);
1084
1085 if (fPts.count()) {
1086 draw_arrow(canvas, mid, fPts[cur].fNorm, kArrowLength, SK_ColorRED);
1087 mid.fX += (kArrowLength/2) * fPts[cur].fNorm.fX;
1088 mid.fY += (kArrowLength/2) * fPts[cur].fNorm.fY;
1089 }
1090
1091 SkString num;
1092 num.printf("%d", this->origEdgeID(cur));
1093 canvas->drawString(num, mid.fX, mid.fY, paint);
1094
1095 if (fPts.count()) {
1096 draw_arrow(canvas, tess.point(fPts[cur].fIndex), fPts[cur].fBisector,
1097 kArrowLength, SK_ColorBLUE);
1098 }
1099 }
1100}
1101
1102void GrAAConvexTessellator::draw(SkCanvas* canvas) const {
1103 for (int i = 0; i < fIndices.count(); i += 3) {
1104 SkASSERT(fIndices[i] < this->numPts()) ;
1105 SkASSERT(fIndices[i+1] < this->numPts()) ;
1106 SkASSERT(fIndices[i+2] < this->numPts()) ;
1107
1108 draw_line(canvas,
1109 this->point(this->fIndices[i]), this->point(this->fIndices[i+1]),
1111 draw_line(canvas,
1112 this->point(this->fIndices[i+1]), this->point(this->fIndices[i+2]),
1114 draw_line(canvas,
1115 this->point(this->fIndices[i+2]), this->point(this->fIndices[i]),
1117 }
1118
1119 fInitialRing.draw(canvas, *this);
1120 for (int i = 0; i < fRings.count(); ++i) {
1121 fRings[i]->draw(canvas, *this);
1122 }
1123
1124 for (int i = 0; i < this->numPts(); ++i) {
1125 draw_point(canvas,
1126 this->point(i), 0.5f + (this->depth(i)/(2 * kAntialiasingRadius)),
1127 !this->movable(i));
1128
1129 SkPaint paint;
1130 paint.setTextSize(kPointTextSize);
1131 if (this->depth(i) <= -kAntialiasingRadius) {
1132 paint.setColor(SK_ColorWHITE);
1133 }
1134
1135 SkString num;
1136 num.printf("%d", i);
1137 canvas->drawString(num,
1138 this->point(i).fX, this->point(i).fY+(kPointRadius/2.0f),
1139 paint);
1140 }
1141}
1142
1143#endif
SkPoint fPts[2]
SkVector fNorms[2]
static const int outset
Definition BlurTest.cpp:58
static void done(const char *config, const char *src, const char *srcOptions, const char *name)
Definition DM.cpp:263
int count
static bool duplicate_pt(const SkPoint &p0, const SkPoint &p1)
static constexpr SkScalar kCurveConnectionThreshold
static bool intersect(const SkPoint &p0, const SkPoint &n0, const SkPoint &p1, const SkPoint &n1, SkScalar *t)
static SkScalar compute_coverage(SkScalar depth, SkScalar initialDepth, SkScalar initialCoverage, SkScalar targetDepth, SkScalar targetCoverage)
static constexpr SkScalar kConicTolerance
static constexpr SkScalar kRoundCapThreshold
static constexpr SkScalar kClose
static constexpr SkScalar kQuadTolerance
static constexpr SkScalar kQuadToleranceSqd
static constexpr SkScalar kCubicToleranceSqd
static bool perp_intersect(const SkPoint &p0, const SkPoint &n0, const SkPoint &p1, const SkPoint &perp, SkScalar *t)
static constexpr SkScalar kCubicTolerance
static bool points_are_colinear_and_b_is_middle(const SkPoint &a, const SkPoint &b, const SkPoint &c, float *accumError)
static constexpr SkScalar kCloseSqd
static const SkScalar kAntialiasingRadius
SkColor4f color
static float next(float f)
static float prev(float f)
#define SkAssertResult(cond)
Definition SkAssert.h:123
#define SkASSERT(cond)
Definition SkAssert.h:116
constexpr SkColor SK_ColorYELLOW
Definition SkColor.h:139
uint32_t SkColor
Definition SkColor.h:37
constexpr SkColor SK_ColorBLUE
Definition SkColor.h:135
constexpr SkColor SK_ColorRED
Definition SkColor.h:126
constexpr SkColor SK_ColorBLACK
Definition SkColor.h:103
constexpr SkColor SK_ColorGREEN
Definition SkColor.h:131
constexpr SkColor SK_ColorWHITE
Definition SkColor.h:122
#define SkDEBUGCODE(...)
Definition SkDebug.h:23
static bool SkIsFinite(T x, Pack... values)
static constexpr float sk_ieee_float_divide(float numer, float denom)
static void normalize(int n, double *gauss)
static bool SkScalarNearlyZero(SkScalar x, SkScalar tolerance=SK_ScalarNearlyZero)
Definition SkScalar.h:101
#define SK_ScalarMax
Definition SkScalar.h:24
static bool SkScalarNearlyEqual(SkScalar x, SkScalar y, SkScalar tolerance=SK_ScalarNearlyZero)
Definition SkScalar.h:107
#define SK_Scalar1
Definition SkScalar.h:18
#define SkScalarHalf(a)
Definition SkScalar.h:75
#define SkScalarSqrt(x)
Definition SkScalar.h:42
#define SkScalarAbs(x)
Definition SkScalar.h:39
static constexpr const T & SkTPin(const T &x, const T &lo, const T &hi)
Definition SkTPin.h:19
Type::kYUV Type::kRGBA() int(0.7 *637)
bool tessellate(const SkMatrix &m, const SkPath &path)
const SkPoint & lastPoint() const
SkScalar coverage(int index) const
SkPointPriv::Side side() const
const SkPoint & point(int index) const
int index(int index) const
const SkPoint * computeQuads(const SkConic &conic, SkScalar tol)
Definition SkGeometry.h:524
int countQuads() const
Definition SkGeometry.h:539
void drawLine(SkScalar x0, SkScalar y0, SkScalar x1, SkScalar y1, const SkPaint &paint)
void drawString(const char str[], SkScalar x, SkScalar y, const SkFont &font, const SkPaint &paint)
Definition SkCanvas.h:1803
void drawCircle(SkScalar cx, SkScalar cy, SkScalar radius, const SkPaint &paint)
void setStyle(Style style)
Definition SkPaint.cpp:105
void setColor(SkColor color)
Definition SkPaint.cpp:119
@ kStroke_Style
set to stroke geometry
Definition SkPaint.h:194
@ kMiter_Join
extends to miter limit
Definition SkPaint.h:359
@ kBevel_Join
connects outside edges
Definition SkPaint.h:361
void setStrokeWidth(SkScalar width)
Definition SkPaint.cpp:159
static bool AllPointsEq(const SkPoint pts[], int count)
Definition SkPathPriv.h:339
static SkPoint MakeOrthog(const SkPoint &vec, Side side=kLeft_Side)
Definition SkPointPriv.h:96
static SkScalar DistanceToSqd(const SkPoint &pt, const SkPoint &a)
Definition SkPointPriv.h:48
void printf(const char format[],...) SK_PRINTF_LIKE(2
Definition SkString.cpp:534
@ kStrokeAndFill_Style
Definition SkStrokeRec.h:36
int size() const
Definition SkTDArray.h:138
void reserve(int n)
Definition SkTDArray.h:187
T * begin()
Definition SkTDArray.h:150
T * append()
Definition SkTDArray.h:191
void resize(int count)
Definition SkTDArray.h:183
void clear()
Definition SkTDArray.h:175
void removeShuffle(int index)
Definition SkTDArray.h:214
void pop_back()
Definition SkTDArray.h:223
const Paint & paint
float SkScalar
Definition extension.cpp:12
static bool b
struct MyStruct a[10]
GAsyncResult * result
uint32_t * target
static void draw_line(SkCanvas *canvas, SkImage *, const SkRect &r, sk_sp< SkImageFilter > imf)
size_t length
uint32_t generateCubicPoints(const SkPoint &p0, const SkPoint &p1, const SkPoint &p2, const SkPoint &p3, SkScalar tolSqd, SkPoint **points, uint32_t pointsLeft)
uint32_t quadraticPointCount(const SkPoint points[], SkScalar tol)
uint32_t cubicPointCount(const SkPoint points[], SkScalar tol)
uint32_t generateQuadraticPoints(const SkPoint &p0, const SkPoint &p1, const SkPoint &p2, SkScalar tolSqd, SkPoint **points, uint32_t pointsLeft)
Optional< SkRect > bounds
Definition SkRecords.h:189
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition switches.h:57
dst
Definition cp.py:12
int64_t cross(Point d0, Point d1)
Definition Myers.cpp:55
SINT T dot(const Vec< N, T > &a, const Vec< N, T > &b)
Definition SkVx.h:964
SkScalar w
void negate()
static float CrossProduct(const SkVector &a, const SkVector &b)
bool setLength(float length)
Definition SkPoint.cpp:30
float fX
x-axis value
static float Normalize(SkVector *vec)
Definition SkPoint.cpp:71
bool isFinite() const
float dot(const SkVector &vec) const
static constexpr SkPoint Make(float x, float y)
float length() const
void scale(float scale, SkPoint *dst) const
Definition SkPoint.cpp:17
float fY
y-axis value
bool normalize()
Definition SkPoint.cpp:22