Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
SkShadowTessellator.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2017 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
10
13#include "include/core/SkPath.h"
16#include "include/core/SkRect.h"
24#include "src/core/SkGeometry.h"
26#include "src/core/SkRectPriv.h"
28
29#include <algorithm>
30#include <cstdint>
31
32
33#if defined(SK_GANESH)
35#endif
36
37using namespace skia_private;
38
39#if !defined(SK_ENABLE_OPTIMIZE_SIZE)
40
41/**
42 * Base class
43 */
45public:
46 SkBaseShadowTessellator(const SkPoint3& zPlaneParams, const SkRect& bounds, bool transparent);
48
50 if (!fSucceeded) {
51 return nullptr;
52 }
54 fPositions.begin(), nullptr, fColors.begin(),
55 this->indexCount(), fIndices.begin());
56 }
57
58protected:
59 inline static constexpr auto kMinHeight = 0.1f;
60 inline static constexpr auto kPenumbraColor = SK_ColorTRANSPARENT;
61 inline static constexpr auto kUmbraColor = SK_ColorBLACK;
62
63 int vertexCount() const { return fPositions.size(); }
64 int indexCount() const { return fIndices.size(); }
65
66 // initialization methods
67 bool accumulateCentroid(const SkPoint& c, const SkPoint& n);
68 bool checkConvexity(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2);
69 void finishPathPolygon();
70
71 // convex shadow methods
74 bool clipUmbraPoint(const SkPoint& umbraPoint, const SkPoint& centroid, SkPoint* clipPoint);
75 void addEdge(const SkVector& nextPoint, const SkVector& nextNormal, SkColor umbraColor,
76 const SkTDArray<SkPoint>& umbraPolygon, bool lastEdge, bool doClip);
77 bool addInnerPoint(const SkPoint& pathPoint, SkColor umbraColor,
78 const SkTDArray<SkPoint>& umbraPolygon, int* currUmbraIndex);
79 int getClosestUmbraIndex(const SkPoint& point, const SkTDArray<SkPoint>& umbraPolygon);
80
81 // concave shadow methods
83 void stitchConcaveRings(const SkTDArray<SkPoint>& umbraPolygon,
84 SkTDArray<int>* umbraIndices,
85 const SkTDArray<SkPoint>& penumbraPolygon,
86 SkTDArray<int>* penumbraIndices);
87
88 void handleLine(const SkPoint& p);
89 void handleLine(const SkMatrix& m, SkPoint* p);
90
91 void handleQuad(const SkPoint pts[3]);
92 void handleQuad(const SkMatrix& m, SkPoint pts[3]);
93
94 void handleCubic(const SkMatrix& m, SkPoint pts[4]);
95
96 void handleConic(const SkMatrix& m, SkPoint pts[3], SkScalar w);
97
98 bool addArc(const SkVector& nextNormal, SkScalar offset, bool finishArc);
99
100 void appendTriangle(uint16_t index0, uint16_t index1, uint16_t index2);
101 void appendQuad(uint16_t index0, uint16_t index1, uint16_t index2, uint16_t index3);
102
106
108
109 // temporary buffer
111
115
119
125
129
134
143};
144
145static bool compute_normal(const SkPoint& p0, const SkPoint& p1, SkScalar dir,
146 SkVector* newNormal) {
147 SkVector normal;
148 // compute perpendicular
149 normal.fX = p0.fY - p1.fY;
150 normal.fY = p1.fX - p0.fX;
151 normal *= dir;
152 if (!normal.normalize()) {
153 return false;
154 }
155 *newNormal = normal;
156 return true;
157}
158
159static bool duplicate_pt(const SkPoint& p0, const SkPoint& p1) {
160 static constexpr SkScalar kClose = (SK_Scalar1 / 16);
161 static constexpr SkScalar kCloseSqd = kClose * kClose;
162
163 SkScalar distSq = SkPointPriv::DistanceToSqd(p0, p1);
164 return distSq < kCloseSqd;
165}
166
167static SkScalar perp_dot(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
168 SkVector v0 = p1 - p0;
169 SkVector v1 = p2 - p1;
170 return v0.cross(v1);
171}
172
174 bool transparent)
175 : fZPlaneParams(zPlaneParams)
176 , fPathBounds(bounds)
177 , fCentroid({0, 0})
178 , fArea(0)
179 , fLastArea(0)
180 , fLastCross(0)
181 , fFirstVertexIndex(-1)
182 , fSucceeded(false)
183 , fTransparent(transparent)
184 , fIsConvex(true)
185 , fValidUmbra(true)
186 , fDirection(1)
187 , fPrevUmbraIndex(-1)
188 , fCurrUmbraIndex(0)
189 , fCurrClipIndex(0)
190 , fPrevUmbraOutside(false)
191 , fFirstUmbraOutside(false) {
192 // child classes will set reserve for positions, colors and indices
193}
194
196 if (duplicate_pt(curr, next)) {
197 return false;
198 }
199
201 SkVector v0 = curr - fPathPolygon[0];
202 SkVector v1 = next - fPathPolygon[0];
203 SkScalar quadArea = v0.cross(v1);
204 fCentroid.fX += (v0.fX + v1.fX) * quadArea;
205 fCentroid.fY += (v0.fY + v1.fY) * quadArea;
206 fArea += quadArea;
207 // convexity check
208 if (quadArea*fLastArea < 0) {
209 fIsConvex = false;
210 }
211 if (0 != quadArea) {
212 fLastArea = quadArea;
213 }
214
215 return true;
216}
217
219 const SkPoint& p1,
220 const SkPoint& p2) {
221 SkScalar cross = perp_dot(p0, p1, p2);
222 // skip collinear point
223 if (SkScalarNearlyZero(cross)) {
224 return false;
225 }
226
227 // check for convexity
228 if (fLastCross*cross < 0) {
229 fIsConvex = false;
230 }
231 if (0 != cross) {
232 fLastCross = cross;
233 }
234
235 return true;
236}
237
239 if (fPathPolygon.size() > 1) {
241 // remove coincident point
243 }
244 }
245
246 if (fPathPolygon.size() > 2) {
247 // do this before the final convexity check, so we use the correct fPathPolygon[0]
252 fPathPolygon[0])) {
253 // remove collinear point
256 }
257 }
258
259 // if area is positive, winding is ccw
260 fDirection = fArea > 0 ? -1 : 1;
261}
262
264 if (doClip) {
266 }
267
268 // adjust inset distance and umbra color if necessary
269 auto umbraColor = kUmbraColor;
271 fPathPolygon[0],
272 fPathPolygon[1]);
273 SkRect bounds;
274 bounds.setBounds(&fPathPolygon[0], fPathPolygon.size());
275 for (int i = 1; i < fPathPolygon.size(); ++i) {
276 int j = i + 1;
277 if (i == fPathPolygon.size() - 1) {
278 j = 0;
279 }
280 SkPoint currPoint = fPathPolygon[i];
281 SkPoint nextPoint = fPathPolygon[j];
283 nextPoint);
284 if (distSq < minDistSq) {
285 minDistSq = distSq;
286 }
287 }
288
289 SkTDArray<SkPoint> insetPolygon;
291 static constexpr auto kTolerance = 1.0e-2f;
292 if (minDistSq < (inset + kTolerance)*(inset + kTolerance)) {
293 // if the umbra would collapse, we back off a bit on inner blur and adjust the alpha
294 auto newInset = SkScalarSqrt(minDistSq) - kTolerance;
295 auto ratio = 128 * (newInset / inset + 1);
296 SkASSERT(SkIsFinite(ratio));
297 // they aren't PMColors, but the interpolation algorithm is the same
298 umbraColor = SkPMLerp(kUmbraColor, kPenumbraColor, (unsigned)ratio);
299 inset = newInset;
300 }
301
302 // generate inner ring
304 &insetPolygon)) {
305 // not ideal, but in this case we'll inset using the centroid
306 fValidUmbra = false;
307 }
308 }
309 const SkTDArray<SkPoint>& umbraPolygon = (inset > SK_ScalarNearlyZero) ? insetPolygon
310 : fPathPolygon;
311
312 // walk around the path polygon, generate outer ring and connect to inner ring
313 if (fTransparent) {
315 fColors.push_back(umbraColor);
316 }
317 fCurrUmbraIndex = 0;
318
319 // initial setup
320 // add first quad
321 int polyCount = fPathPolygon.size();
322 if (!compute_normal(fPathPolygon[polyCount - 1], fPathPolygon[0], fDirection, &fFirstOutset)) {
323 // polygon should be sanitized by this point, so this is unrecoverable
324 return false;
325 }
326
328 fFirstPoint = fPathPolygon[polyCount - 1];
332 fPrevUmbraIndex = -1;
333
334 this->addInnerPoint(fFirstPoint, umbraColor, umbraPolygon, &fPrevUmbraIndex);
335
336 if (!fTransparent && doClip) {
337 SkPoint clipPoint;
338 bool isOutside = this->clipUmbraPoint(fPositions[fFirstVertexIndex],
339 fCentroid, &clipPoint);
340 if (isOutside) {
341 fPositions.push_back(clipPoint);
342 fColors.push_back(umbraColor);
343 }
344 fPrevUmbraOutside = isOutside;
345 fFirstUmbraOutside = isOutside;
346 }
347
348 SkPoint newPoint = fFirstPoint + fFirstOutset;
349 fPositions.push_back(newPoint);
351 this->addEdge(fPathPolygon[0], fFirstOutset, umbraColor, umbraPolygon, false, doClip);
352
353 for (int i = 1; i < polyCount; ++i) {
354 SkVector normal;
355 if (!compute_normal(fPrevPoint, fPathPolygon[i], fDirection, &normal)) {
356 return false;
357 }
358 normal *= outset;
359 this->addArc(normal, outset, true);
360 this->addEdge(fPathPolygon[i], normal, umbraColor, umbraPolygon,
361 i == polyCount - 1, doClip);
362 }
363 SkASSERT(this->indexCount());
364
365 // final fan
366 SkASSERT(fPositions.size() >= 3);
367 if (this->addArc(fFirstOutset, outset, false)) {
368 if (fFirstUmbraOutside) {
371 } else {
374 }
375 } else {
376 // no arc added, fix up by setting first penumbra point position to last one
377 if (fFirstUmbraOutside) {
379 } else {
381 }
382 }
383
384 return true;
385}
386
388 SkASSERT(fClipPolygon.size() >= 3);
390
391 // init clip vectors
395
396 // init centroid check
397 bool hiddenCentroid = true;
398 v1 = fCentroid - fClipPolygon[0];
399 SkScalar initCross = v0.cross(v1);
400
401 for (int p = 1; p < fClipPolygon.size(); ++p) {
402 // add to clip vectors
403 v0 = fClipPolygon[(p + 1) % fClipPolygon.size()] - fClipPolygon[p];
405 // Determine if transformed centroid is inside clipPolygon.
406 v1 = fCentroid - fClipPolygon[p];
407 if (initCross*v0.cross(v1) <= 0) {
408 hiddenCentroid = false;
409 }
410 }
412
413 fTransparent = fTransparent || !hiddenCentroid;
414}
415
416void SkBaseShadowTessellator::addEdge(const SkPoint& nextPoint, const SkVector& nextNormal,
417 SkColor umbraColor, const SkTDArray<SkPoint>& umbraPolygon,
418 bool lastEdge, bool doClip) {
419 // add next umbra point
420 int currUmbraIndex;
421 bool duplicate;
422 if (lastEdge) {
423 duplicate = false;
424 currUmbraIndex = fFirstVertexIndex;
425 fPrevPoint = nextPoint;
426 } else {
427 duplicate = this->addInnerPoint(nextPoint, umbraColor, umbraPolygon, &currUmbraIndex);
428 }
429 int prevPenumbraIndex = duplicate || (currUmbraIndex == fFirstVertexIndex)
430 ? fPositions.size() - 1
431 : fPositions.size() - 2;
432 if (!duplicate) {
433 // add to center fan if transparent or centroid showing
434 if (fTransparent) {
435 this->appendTriangle(0, fPrevUmbraIndex, currUmbraIndex);
436 // otherwise add to clip ring
437 } else if (doClip) {
438 SkPoint clipPoint;
439 bool isOutside = lastEdge ? fFirstUmbraOutside
440 : this->clipUmbraPoint(fPositions[currUmbraIndex], fCentroid,
441 &clipPoint);
442 if (isOutside) {
443 if (!lastEdge) {
444 fPositions.push_back(clipPoint);
445 fColors.push_back(umbraColor);
446 }
447 this->appendTriangle(fPrevUmbraIndex, currUmbraIndex, currUmbraIndex + 1);
448 if (fPrevUmbraOutside) {
449 // fill out quad
450 this->appendTriangle(fPrevUmbraIndex, currUmbraIndex + 1,
451 fPrevUmbraIndex + 1);
452 }
453 } else if (fPrevUmbraOutside) {
454 // add tri
455 this->appendTriangle(fPrevUmbraIndex, currUmbraIndex, fPrevUmbraIndex + 1);
456 }
457
458 fPrevUmbraOutside = isOutside;
459 }
460 }
461
462 // add next penumbra point and quad
463 SkPoint newPoint = nextPoint + nextNormal;
464 fPositions.push_back(newPoint);
466
467 if (!duplicate) {
468 this->appendTriangle(fPrevUmbraIndex, prevPenumbraIndex, currUmbraIndex);
469 }
470 this->appendTriangle(prevPenumbraIndex, fPositions.size() - 1, currUmbraIndex);
471
472 fPrevUmbraIndex = currUmbraIndex;
473 fPrevOutset = nextNormal;
474}
475
476bool SkBaseShadowTessellator::clipUmbraPoint(const SkPoint& umbraPoint, const SkPoint& centroid,
477 SkPoint* clipPoint) {
478 SkVector segmentVector = centroid - umbraPoint;
479
480 int startClipPoint = fCurrClipIndex;
481 do {
482 SkVector dp = umbraPoint - fClipPolygon[fCurrClipIndex];
483 SkScalar denom = fClipVectors[fCurrClipIndex].cross(segmentVector);
484 SkScalar t_num = dp.cross(segmentVector);
485 // if line segments are nearly parallel
486 if (SkScalarNearlyZero(denom)) {
487 // and collinear
488 if (SkScalarNearlyZero(t_num)) {
489 return false;
490 }
491 // otherwise are separate, will try the next poly segment
492 // else if crossing lies within poly segment
493 } else if (t_num >= 0 && t_num <= denom) {
495 // if umbra point is inside the clip polygon
496 if (s_num >= 0 && s_num <= denom) {
497 segmentVector *= s_num / denom;
498 *clipPoint = umbraPoint + segmentVector;
499 return true;
500 }
501 }
503 } while (fCurrClipIndex != startClipPoint);
504
505 return false;
506}
507
509 const SkTDArray<SkPoint>& umbraPolygon,
510 int* currUmbraIndex) {
511 SkPoint umbraPoint;
512 if (!fValidUmbra) {
513 SkVector v = fCentroid - pathPoint;
514 v *= 0.95f;
515 umbraPoint = pathPoint + v;
516 } else {
517 umbraPoint = umbraPolygon[this->getClosestUmbraIndex(pathPoint, umbraPolygon)];
518 }
519
520 fPrevPoint = pathPoint;
521
522 // merge "close" points
523 if (fPrevUmbraIndex == -1 ||
524 !duplicate_pt(umbraPoint, fPositions[fPrevUmbraIndex])) {
525 // if we've wrapped around, don't add a new point
526 if (fPrevUmbraIndex >= 0 && duplicate_pt(umbraPoint, fPositions[fFirstVertexIndex])) {
527 *currUmbraIndex = fFirstVertexIndex;
528 } else {
529 *currUmbraIndex = fPositions.size();
530 fPositions.push_back(umbraPoint);
531 fColors.push_back(umbraColor);
532 }
533 return false;
534 } else {
535 *currUmbraIndex = fPrevUmbraIndex;
536 return true;
537 }
538}
539
541 const SkTDArray<SkPoint>& umbraPolygon) {
542 SkScalar minDistance = SkPointPriv::DistanceToSqd(p, umbraPolygon[fCurrUmbraIndex]);
543 int index = fCurrUmbraIndex;
544 int dir = 1;
545 int next = (index + dir) % umbraPolygon.size();
546
547 // init travel direction
548 SkScalar distance = SkPointPriv::DistanceToSqd(p, umbraPolygon[next]);
549 if (distance < minDistance) {
550 index = next;
551 minDistance = distance;
552 } else {
553 dir = umbraPolygon.size() - 1;
554 }
555
556 // iterate until we find a point that increases the distance
557 next = (index + dir) % umbraPolygon.size();
558 distance = SkPointPriv::DistanceToSqd(p, umbraPolygon[next]);
559 while (distance < minDistance) {
560 index = next;
561 minDistance = distance;
562 next = (index + dir) % umbraPolygon.size();
563 distance = SkPointPriv::DistanceToSqd(p, umbraPolygon[next]);
564 }
565
566 fCurrUmbraIndex = index;
567 return index;
568}
569
572 return false;
573 }
574
575 // shouldn't inset more than the half bounds of the polygon
576 inset = std::min(inset, std::min(SkTAbs(SkRectPriv::HalfWidth(fPathBounds)),
578 // generate inner ring
579 SkTDArray<SkPoint> umbraPolygon;
580 SkTDArray<int> umbraIndices;
581 umbraIndices.reserve(fPathPolygon.size());
583 &umbraPolygon, &umbraIndices)) {
584 // TODO: figure out how to handle this case
585 return false;
586 }
587
588 // generate outer ring
589 SkTDArray<SkPoint> penumbraPolygon;
590 SkTDArray<int> penumbraIndices;
591 penumbraPolygon.reserve(umbraPolygon.size());
592 penumbraIndices.reserve(umbraPolygon.size());
594 &penumbraPolygon, &penumbraIndices)) {
595 // TODO: figure out how to handle this case
596 return false;
597 }
598
599 if (umbraPolygon.empty() || penumbraPolygon.empty()) {
600 return false;
601 }
602
603 // attach the rings together
604 this->stitchConcaveRings(umbraPolygon, &umbraIndices, penumbraPolygon, &penumbraIndices);
605
606 return true;
607}
608
610 SkTDArray<int>* umbraIndices,
611 const SkTDArray<SkPoint>& penumbraPolygon,
612 SkTDArray<int>* penumbraIndices) {
613 // TODO: only create and fill indexMap when fTransparent is true?
614 AutoSTMalloc<64, uint16_t> indexMap(umbraPolygon.size());
615
616 // find minimum indices
617 int minIndex = 0;
618 int min = (*penumbraIndices)[0];
619 for (int i = 1; i < (*penumbraIndices).size(); ++i) {
620 if ((*penumbraIndices)[i] < min) {
621 min = (*penumbraIndices)[i];
622 minIndex = i;
623 }
624 }
625 int currPenumbra = minIndex;
626
627 minIndex = 0;
628 min = (*umbraIndices)[0];
629 for (int i = 1; i < (*umbraIndices).size(); ++i) {
630 if ((*umbraIndices)[i] < min) {
631 min = (*umbraIndices)[i];
632 minIndex = i;
633 }
634 }
635 int currUmbra = minIndex;
636
637 // now find a case where the indices are equal (there should be at least one)
638 int maxPenumbraIndex = fPathPolygon.size() - 1;
639 int maxUmbraIndex = fPathPolygon.size() - 1;
640 while ((*penumbraIndices)[currPenumbra] != (*umbraIndices)[currUmbra]) {
641 if ((*penumbraIndices)[currPenumbra] < (*umbraIndices)[currUmbra]) {
642 (*penumbraIndices)[currPenumbra] += fPathPolygon.size();
643 maxPenumbraIndex = (*penumbraIndices)[currPenumbra];
644 currPenumbra = (currPenumbra + 1) % penumbraPolygon.size();
645 } else {
646 (*umbraIndices)[currUmbra] += fPathPolygon.size();
647 maxUmbraIndex = (*umbraIndices)[currUmbra];
648 currUmbra = (currUmbra + 1) % umbraPolygon.size();
649 }
650 }
651
652 fPositions.push_back(penumbraPolygon[currPenumbra]);
654 int prevPenumbraIndex = 0;
655 fPositions.push_back(umbraPolygon[currUmbra]);
657 fPrevUmbraIndex = 1;
658 indexMap[currUmbra] = 1;
659
660 int nextPenumbra = (currPenumbra + 1) % penumbraPolygon.size();
661 int nextUmbra = (currUmbra + 1) % umbraPolygon.size();
662 while ((*penumbraIndices)[nextPenumbra] <= maxPenumbraIndex ||
663 (*umbraIndices)[nextUmbra] <= maxUmbraIndex) {
664
665 if ((*umbraIndices)[nextUmbra] == (*penumbraIndices)[nextPenumbra]) {
666 // advance both one step
667 fPositions.push_back(penumbraPolygon[nextPenumbra]);
669 int currPenumbraIndex = fPositions.size() - 1;
670
671 fPositions.push_back(umbraPolygon[nextUmbra]);
673 int currUmbraIndex = fPositions.size() - 1;
674 indexMap[nextUmbra] = currUmbraIndex;
675
676 this->appendQuad(prevPenumbraIndex, currPenumbraIndex,
677 fPrevUmbraIndex, currUmbraIndex);
678
679 prevPenumbraIndex = currPenumbraIndex;
680 (*penumbraIndices)[currPenumbra] += fPathPolygon.size();
681 currPenumbra = nextPenumbra;
682 nextPenumbra = (currPenumbra + 1) % penumbraPolygon.size();
683
684 fPrevUmbraIndex = currUmbraIndex;
685 (*umbraIndices)[currUmbra] += fPathPolygon.size();
686 currUmbra = nextUmbra;
687 nextUmbra = (currUmbra + 1) % umbraPolygon.size();
688 }
689
690 while ((*penumbraIndices)[nextPenumbra] < (*umbraIndices)[nextUmbra] &&
691 (*penumbraIndices)[nextPenumbra] <= maxPenumbraIndex) {
692 // fill out penumbra arc
693 fPositions.push_back(penumbraPolygon[nextPenumbra]);
695 int currPenumbraIndex = fPositions.size() - 1;
696
697 this->appendTriangle(prevPenumbraIndex, currPenumbraIndex, fPrevUmbraIndex);
698
699 prevPenumbraIndex = currPenumbraIndex;
700 // this ensures the ordering when we wrap around
701 (*penumbraIndices)[currPenumbra] += fPathPolygon.size();
702 currPenumbra = nextPenumbra;
703 nextPenumbra = (currPenumbra + 1) % penumbraPolygon.size();
704 }
705
706 while ((*umbraIndices)[nextUmbra] < (*penumbraIndices)[nextPenumbra] &&
707 (*umbraIndices)[nextUmbra] <= maxUmbraIndex) {
708 // fill out umbra arc
709 fPositions.push_back(umbraPolygon[nextUmbra]);
711 int currUmbraIndex = fPositions.size() - 1;
712 indexMap[nextUmbra] = currUmbraIndex;
713
714 this->appendTriangle(fPrevUmbraIndex, prevPenumbraIndex, currUmbraIndex);
715
716 fPrevUmbraIndex = currUmbraIndex;
717 // this ensures the ordering when we wrap around
718 (*umbraIndices)[currUmbra] += fPathPolygon.size();
719 currUmbra = nextUmbra;
720 nextUmbra = (currUmbra + 1) % umbraPolygon.size();
721 }
722 }
723 // finish up by advancing both one step
724 fPositions.push_back(penumbraPolygon[nextPenumbra]);
726 int currPenumbraIndex = fPositions.size() - 1;
727
728 fPositions.push_back(umbraPolygon[nextUmbra]);
730 int currUmbraIndex = fPositions.size() - 1;
731 indexMap[nextUmbra] = currUmbraIndex;
732
733 this->appendQuad(prevPenumbraIndex, currPenumbraIndex,
734 fPrevUmbraIndex, currUmbraIndex);
735
736 if (fTransparent) {
737 SkTriangulateSimplePolygon(umbraPolygon.begin(), indexMap, umbraPolygon.size(),
738 &fIndices);
739 }
740}
741
742
743// tesselation tolerance values, in device space pixels
744#if defined(SK_GANESH)
745static constexpr SkScalar kQuadTolerance = 0.2f;
746static constexpr SkScalar kCubicTolerance = 0.2f;
749#endif
750static constexpr SkScalar kConicTolerance = 0.25f;
751
752// clamps the point to the nearest 16th of a pixel
753static void sanitize_point(const SkPoint& in, SkPoint* out) {
754 out->fX = SkScalarRoundToScalar(16.f*in.fX)*0.0625f;
755 out->fY = SkScalarRoundToScalar(16.f*in.fY)*0.0625f;
756}
757
759 SkPoint pSanitized;
760 sanitize_point(p, &pSanitized);
761
762 if (!fPathPolygon.empty()) {
763 if (!this->accumulateCentroid(fPathPolygon[fPathPolygon.size() - 1], pSanitized)) {
764 // skip coincident point
765 return;
766 }
767 }
768
769 if (fPathPolygon.size() > 1) {
772 pSanitized)) {
773 // remove collinear point
775 // it's possible that the previous point is coincident with the new one now
776 if (duplicate_pt(fPathPolygon[fPathPolygon.size() - 1], pSanitized)) {
778 }
779 }
780 }
781
782 fPathPolygon.push_back(pSanitized);
783}
784
786 m.mapPoints(p, 1);
787
788 this->handleLine(*p);
789}
790
792#if defined(SK_GANESH)
793 // check for degeneracy
794 SkVector v0 = pts[1] - pts[0];
795 SkVector v1 = pts[2] - pts[0];
796 if (SkScalarNearlyZero(v0.cross(v1))) {
797 return;
798 }
799 // TODO: Pull PathUtils out of Ganesh?
801 fPointBuffer.resize(maxCount);
803 int count = GrPathUtils::generateQuadraticPoints(pts[0], pts[1], pts[2],
804 kQuadToleranceSqd, &target, maxCount);
806 for (int i = 0; i < count; i++) {
807 this->handleLine(fPointBuffer[i]);
808 }
809#else
810 // for now, just to draw something
811 this->handleLine(pts[1]);
812 this->handleLine(pts[2]);
813#endif
814}
815
817 m.mapPoints(pts, 3);
818 this->handleQuad(pts);
819}
820
822 m.mapPoints(pts, 4);
823#if defined(SK_GANESH)
824 // TODO: Pull PathUtils out of Ganesh?
826 fPointBuffer.resize(maxCount);
828 int count = GrPathUtils::generateCubicPoints(pts[0], pts[1], pts[2], pts[3],
829 kCubicToleranceSqd, &target, maxCount);
831 for (int i = 0; i < count; i++) {
832 this->handleLine(fPointBuffer[i]);
833 }
834#else
835 // for now, just to draw something
836 this->handleLine(pts[1]);
837 this->handleLine(pts[2]);
838 this->handleLine(pts[3]);
839#endif
840}
841
843 if (m.hasPerspective()) {
844 w = SkConic::TransformW(pts, w, m);
845 }
846 m.mapPoints(pts, 3);
847 SkAutoConicToQuads quadder;
848 const SkPoint* quads = quadder.computeQuads(pts, w, kConicTolerance);
849 SkPoint lastPoint = *(quads++);
850 int count = quadder.countQuads();
851 for (int i = 0; i < count; ++i) {
852 SkPoint quadPts[3];
853 quadPts[0] = lastPoint;
854 quadPts[1] = quads[0];
855 quadPts[2] = i == count - 1 ? pts[2] : quads[1];
856 this->handleQuad(quadPts);
857 lastPoint = quadPts[2];
858 quads += 2;
859 }
860}
861
862bool SkBaseShadowTessellator::addArc(const SkVector& nextNormal, SkScalar offset, bool finishArc) {
863 // fill in fan from previous quad
864 SkScalar rotSin, rotCos;
865 int numSteps;
866 if (!SkComputeRadialSteps(fPrevOutset, nextNormal, offset, &rotSin, &rotCos, &numSteps)) {
867 // recover as best we can
868 numSteps = 0;
869 }
870 SkVector prevNormal = fPrevOutset;
871 for (int i = 0; i < numSteps-1; ++i) {
872 SkVector currNormal;
873 currNormal.fX = prevNormal.fX*rotCos - prevNormal.fY*rotSin;
874 currNormal.fY = prevNormal.fY*rotCos + prevNormal.fX*rotSin;
875 fPositions.push_back(fPrevPoint + currNormal);
878
879 prevNormal = currNormal;
880 }
881 if (finishArc && numSteps) {
882 fPositions.push_back(fPrevPoint + nextNormal);
885 }
886 fPrevOutset = nextNormal;
887
888 return (numSteps > 0);
889}
890
891void SkBaseShadowTessellator::appendTriangle(uint16_t index0, uint16_t index1, uint16_t index2) {
892 auto indices = fIndices.append(3);
893
894 indices[0] = index0;
895 indices[1] = index1;
896 indices[2] = index2;
897}
898
899void SkBaseShadowTessellator::appendQuad(uint16_t index0, uint16_t index1,
900 uint16_t index2, uint16_t index3) {
901 auto indices = fIndices.append(6);
902
903 indices[0] = index0;
904 indices[1] = index1;
905 indices[2] = index2;
906
907 indices[3] = index2;
908 indices[4] = index1;
909 indices[5] = index3;
910}
911
912//////////////////////////////////////////////////////////////////////////////////////////////////
913
915public:
916 SkAmbientShadowTessellator(const SkPath& path, const SkMatrix& ctm,
917 const SkPoint3& zPlaneParams, bool transparent);
918
919private:
920 bool computePathPolygon(const SkPath& path, const SkMatrix& ctm);
921
923};
924
926 const SkMatrix& ctm,
927 const SkPoint3& zPlaneParams,
928 bool transparent)
929 : INHERITED(zPlaneParams, path.getBounds(), transparent) {
930 // Set base colors
932 // umbraColor is the interior value, penumbraColor the exterior value.
935
936 if (!this->computePathPolygon(path, ctm)) {
937 return;
938 }
939 if (fPathPolygon.size() < 3 || !SkIsFinite(fArea)) {
940 fSucceeded = true; // We don't want to try to blur these cases, so we will
941 // return an empty SkVertices instead.
942 return;
943 }
944
945 // Outer ring: 3*numPts
946 // Middle ring: numPts
947 fPositions.reserve(4 * path.countPoints());
948 fColors.reserve(4 * path.countPoints());
949 // Outer ring: 12*numPts
950 // Middle ring: 0
951 fIndices.reserve(12 * path.countPoints());
952
953 if (fIsConvex) {
954 fSucceeded = this->computeConvexShadow(inset, outset, false);
955 } else {
956 fSucceeded = this->computeConcaveShadow(inset, outset);
957 }
958}
959
960bool SkAmbientShadowTessellator::computePathPolygon(const SkPath& path, const SkMatrix& ctm) {
961 fPathPolygon.reserve(path.countPoints());
962
963 // walk around the path, tessellate and generate outer ring
964 // if original path is transparent, will accumulate sum of points for centroid
965 SkPath::Iter iter(path, true);
966 SkPoint pts[4];
967 SkPath::Verb verb;
968 bool verbSeen = false;
969 bool closeSeen = false;
970 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
971 if (closeSeen) {
972 return false;
973 }
974 switch (verb) {
976 this->handleLine(ctm, &pts[1]);
977 break;
979 this->handleQuad(ctm, pts);
980 break;
982 this->handleCubic(ctm, pts);
983 break;
985 this->handleConic(ctm, pts, iter.conicWeight());
986 break;
988 if (verbSeen) {
989 return false;
990 }
991 break;
994 closeSeen = true;
995 break;
996 }
997 verbSeen = true;
998 }
999
1000 this->finishPathPolygon();
1001 return true;
1002}
1003
1004///////////////////////////////////////////////////////////////////////////////////////////////////
1005
1007public:
1008 SkSpotShadowTessellator(const SkPath& path, const SkMatrix& ctm,
1009 const SkPoint3& zPlaneParams, const SkPoint3& lightPos,
1010 SkScalar lightRadius, bool transparent, bool directional);
1011
1012private:
1013 bool computeClipAndPathPolygons(const SkPath& path, const SkMatrix& ctm,
1014 const SkMatrix& shadowTransform);
1015 void addToClip(const SkVector& nextPoint);
1016
1018};
1019
1021 const SkPoint3& zPlaneParams,
1022 const SkPoint3& lightPos, SkScalar lightRadius,
1023 bool transparent, bool directional)
1024 : INHERITED(zPlaneParams, path.getBounds(), transparent) {
1025
1026 // Compute the blur radius, scale and translation for the spot shadow.
1027 SkMatrix shadowTransform;
1029 if (!SkDrawShadowMetrics::GetSpotShadowTransform(lightPos, lightRadius, ctm, zPlaneParams,
1030 path.getBounds(), directional,
1031 &shadowTransform, &outset)) {
1032 return;
1033 }
1035
1036 // compute rough clip bounds for umbra, plus offset polygon, plus centroid
1037 if (!this->computeClipAndPathPolygons(path, ctm, shadowTransform)) {
1038 return;
1039 }
1040 if (fClipPolygon.size() < 3 || fPathPolygon.size() < 3 || !SkIsFinite(fArea)) {
1041 fSucceeded = true; // We don't want to try to blur these cases, so we will
1042 // return an empty SkVertices instead.
1043 return;
1044 }
1045
1046 // TODO: calculate these reserves better
1047 // Penumbra ring: 3*numPts
1048 // Umbra ring: numPts
1049 // Inner ring: numPts
1050 fPositions.reserve(5 * path.countPoints());
1051 fColors.reserve(5 * path.countPoints());
1052 // Penumbra ring: 12*numPts
1053 // Umbra ring: 3*numPts
1054 fIndices.reserve(15 * path.countPoints());
1055
1056 if (fIsConvex) {
1057 fSucceeded = this->computeConvexShadow(inset, outset, true);
1058 } else {
1059 fSucceeded = this->computeConcaveShadow(inset, outset);
1060 }
1061
1062 if (!fSucceeded) {
1063 return;
1064 }
1065
1066 fSucceeded = true;
1067}
1068
1069bool SkSpotShadowTessellator::computeClipAndPathPolygons(const SkPath& path, const SkMatrix& ctm,
1070 const SkMatrix& shadowTransform) {
1071
1072 fPathPolygon.reserve(path.countPoints());
1073 fClipPolygon.reserve(path.countPoints());
1074
1075 // Walk around the path and compute clip polygon and path polygon.
1076 // Will also accumulate sum of areas for centroid.
1077 // For Bezier curves, we compute additional interior points on curve.
1078 SkPath::Iter iter(path, true);
1079 SkPoint pts[4];
1080 SkPoint clipPts[4];
1081 SkPath::Verb verb;
1082
1083 // coefficients to compute cubic Bezier at t = 5/16
1084 static constexpr SkScalar kA = 0.32495117187f;
1085 static constexpr SkScalar kB = 0.44311523437f;
1086 static constexpr SkScalar kC = 0.20141601562f;
1087 static constexpr SkScalar kD = 0.03051757812f;
1088
1089 SkPoint curvePoint;
1090 SkScalar w;
1091 bool closeSeen = false;
1092 bool verbSeen = false;
1093 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1094 if (closeSeen) {
1095 return false;
1096 }
1097 switch (verb) {
1098 case SkPath::kLine_Verb:
1099 ctm.mapPoints(clipPts, &pts[1], 1);
1100 this->addToClip(clipPts[0]);
1101 this->handleLine(shadowTransform, &pts[1]);
1102 break;
1103 case SkPath::kQuad_Verb:
1104 ctm.mapPoints(clipPts, pts, 3);
1105 // point at t = 1/2
1106 curvePoint.fX = 0.25f*clipPts[0].fX + 0.5f*clipPts[1].fX + 0.25f*clipPts[2].fX;
1107 curvePoint.fY = 0.25f*clipPts[0].fY + 0.5f*clipPts[1].fY + 0.25f*clipPts[2].fY;
1108 this->addToClip(curvePoint);
1109 this->addToClip(clipPts[2]);
1110 this->handleQuad(shadowTransform, pts);
1111 break;
1113 ctm.mapPoints(clipPts, pts, 3);
1114 w = iter.conicWeight();
1115 // point at t = 1/2
1116 curvePoint.fX = 0.25f*clipPts[0].fX + w*0.5f*clipPts[1].fX + 0.25f*clipPts[2].fX;
1117 curvePoint.fY = 0.25f*clipPts[0].fY + w*0.5f*clipPts[1].fY + 0.25f*clipPts[2].fY;
1118 curvePoint *= SkScalarInvert(0.5f + 0.5f*w);
1119 this->addToClip(curvePoint);
1120 this->addToClip(clipPts[2]);
1121 this->handleConic(shadowTransform, pts, w);
1122 break;
1124 ctm.mapPoints(clipPts, pts, 4);
1125 // point at t = 5/16
1126 curvePoint.fX = kA*clipPts[0].fX + kB*clipPts[1].fX
1127 + kC*clipPts[2].fX + kD*clipPts[3].fX;
1128 curvePoint.fY = kA*clipPts[0].fY + kB*clipPts[1].fY
1129 + kC*clipPts[2].fY + kD*clipPts[3].fY;
1130 this->addToClip(curvePoint);
1131 // point at t = 11/16
1132 curvePoint.fX = kD*clipPts[0].fX + kC*clipPts[1].fX
1133 + kB*clipPts[2].fX + kA*clipPts[3].fX;
1134 curvePoint.fY = kD*clipPts[0].fY + kC*clipPts[1].fY
1135 + kB*clipPts[2].fY + kA*clipPts[3].fY;
1136 this->addToClip(curvePoint);
1137 this->addToClip(clipPts[3]);
1138 this->handleCubic(shadowTransform, pts);
1139 break;
1140 case SkPath::kMove_Verb:
1141 if (verbSeen) {
1142 return false;
1143 }
1144 break;
1146 case SkPath::kDone_Verb:
1147 closeSeen = true;
1148 break;
1149 default:
1150 SkDEBUGFAIL("unknown verb");
1151 }
1152 verbSeen = true;
1153 }
1154
1155 this->finishPathPolygon();
1156 return true;
1157}
1158
1159void SkSpotShadowTessellator::addToClip(const SkPoint& point) {
1160 if (fClipPolygon.empty() || !duplicate_pt(point, fClipPolygon[fClipPolygon.size() - 1])) {
1161 fClipPolygon.push_back(point);
1162 }
1163}
1164
1165///////////////////////////////////////////////////////////////////////////////////////////////////
1166
1168 const SkPoint3& zPlane, bool transparent) {
1169 if (!ctm.mapRect(path.getBounds()).isFinite() || !zPlane.isFinite()) {
1170 return nullptr;
1171 }
1172 SkAmbientShadowTessellator ambientTess(path, ctm, zPlane, transparent);
1173 return ambientTess.releaseVertices();
1174}
1175
1177 const SkPoint3& zPlane, const SkPoint3& lightPos,
1178 SkScalar lightRadius, bool transparent,
1179 bool directional) {
1180 if (!ctm.mapRect(path.getBounds()).isFinite() || !zPlane.isFinite() ||
1181 !lightPos.isFinite() || !(lightPos.fZ >= SK_ScalarNearlyZero) ||
1182 !SkIsFinite(lightRadius) || !(lightRadius >= SK_ScalarNearlyZero)) {
1183 return nullptr;
1184 }
1185 SkSpotShadowTessellator spotTess(path, ctm, zPlane, lightPos, lightRadius, transparent,
1186 directional);
1187 return spotTess.releaseVertices();
1188}
1189
1190#endif // !defined(SK_ENABLE_OPTIMIZE_SIZE)
1191
static const int outset
Definition BlurTest.cpp:58
int count
static constexpr SkScalar kClose
static constexpr SkScalar kQuadTolerance
static constexpr SkScalar kQuadToleranceSqd
static constexpr SkScalar kCubicToleranceSqd
static constexpr SkScalar kCubicTolerance
static constexpr SkScalar kCloseSqd
static constexpr float kTolerance
@ kC
static float next(float f)
#define SkDEBUGFAIL(message)
Definition SkAssert.h:118
#define SkASSERT(cond)
Definition SkAssert.h:116
static SkPMColor SkPMLerp(SkPMColor src, SkPMColor dst, unsigned scale)
uint32_t SkColor
Definition SkColor.h:37
constexpr SkColor SK_ColorTRANSPARENT
Definition SkColor.h:99
constexpr SkColor SK_ColorBLACK
Definition SkColor.h:103
static bool SkIsFinite(T x, Pack... values)
static constexpr float sk_ieee_float_divide(float numer, float denom)
bool SkComputeRadialSteps(const SkVector &v1, const SkVector &v2, SkScalar offset, SkScalar *rotSin, SkScalar *rotCos, int *n)
bool SkTriangulateSimplePolygon(const SkPoint *polygonVerts, uint16_t *indexMap, int polygonSize, SkTDArray< uint16_t > *triangleIndices)
bool SkOffsetSimplePolygon(const SkPoint *inputPolygonVerts, int inputPolygonSize, const SkRect &bounds, SkScalar offset, SkTDArray< SkPoint > *offsetPolygon, SkTDArray< int > *polygonIndices)
bool SkIsSimplePolygon(const SkPoint *polygon, int polygonSize)
bool SkInsetConvexPolygon(const SkPoint *inputPolygonVerts, int inputPolygonSize, SkScalar inset, SkTDArray< SkPoint > *insetPolygon)
#define SkScalarInvert(x)
Definition SkScalar.h:73
static bool SkScalarNearlyZero(SkScalar x, SkScalar tolerance=SK_ScalarNearlyZero)
Definition SkScalar.h:101
#define SK_Scalar1
Definition SkScalar.h:18
#define SK_ScalarNearlyZero
Definition SkScalar.h:99
#define SkScalarRoundToScalar(x)
Definition SkScalar.h:32
#define SkScalarSqrt(x)
Definition SkScalar.h:42
static bool duplicate_pt(const SkPoint &p0, const SkPoint &p1)
static SkScalar perp_dot(const SkPoint &p0, const SkPoint &p1, const SkPoint &p2)
static constexpr SkScalar kConicTolerance
static bool compute_normal(const SkPoint &p0, const SkPoint &p1, SkScalar dir, SkVector *newNormal)
static void sanitize_point(const SkPoint &in, SkPoint *out)
static T SkTAbs(T value)
Definition SkTemplates.h:43
SkAmbientShadowTessellator(const SkPath &path, const SkMatrix &ctm, const SkPoint3 &zPlaneParams, bool transparent)
const SkPoint * computeQuads(const SkConic &conic, SkScalar tol)
Definition SkGeometry.h:524
int countQuads() const
Definition SkGeometry.h:539
void appendQuad(uint16_t index0, uint16_t index1, uint16_t index2, uint16_t index3)
bool accumulateCentroid(const SkPoint &c, const SkPoint &n)
void stitchConcaveRings(const SkTDArray< SkPoint > &umbraPolygon, SkTDArray< int > *umbraIndices, const SkTDArray< SkPoint > &penumbraPolygon, SkTDArray< int > *penumbraIndices)
static constexpr auto kMinHeight
void handleLine(const SkPoint &p)
SkScalar heightFunc(SkScalar x, SkScalar y)
static constexpr auto kUmbraColor
SkTDArray< SkPoint > fPointBuffer
static constexpr auto kPenumbraColor
SkTDArray< SkPoint > fClipPolygon
SkTDArray< SkVector > fClipVectors
bool clipUmbraPoint(const SkPoint &umbraPoint, const SkPoint &centroid, SkPoint *clipPoint)
void handleCubic(const SkMatrix &m, SkPoint pts[4])
bool computeConcaveShadow(SkScalar inset, SkScalar outset)
sk_sp< SkVertices > releaseVertices()
bool computeConvexShadow(SkScalar inset, SkScalar outset, bool doClip)
void handleConic(const SkMatrix &m, SkPoint pts[3], SkScalar w)
void addEdge(const SkVector &nextPoint, const SkVector &nextNormal, SkColor umbraColor, const SkTDArray< SkPoint > &umbraPolygon, bool lastEdge, bool doClip)
SkTDArray< SkPoint > fPositions
bool addInnerPoint(const SkPoint &pathPoint, SkColor umbraColor, const SkTDArray< SkPoint > &umbraPolygon, int *currUmbraIndex)
void appendTriangle(uint16_t index0, uint16_t index1, uint16_t index2)
SkTDArray< uint16_t > fIndices
void handleQuad(const SkPoint pts[3])
int getClosestUmbraIndex(const SkPoint &point, const SkTDArray< SkPoint > &umbraPolygon)
bool checkConvexity(const SkPoint &p0, const SkPoint &p1, const SkPoint &p2)
bool addArc(const SkVector &nextNormal, SkScalar offset, bool finishArc)
SkTDArray< SkPoint > fPathPolygon
SkBaseShadowTessellator(const SkPoint3 &zPlaneParams, const SkRect &bounds, bool transparent)
void mapPoints(SkPoint dst[], const SkPoint src[], int count) const
Definition SkMatrix.cpp:770
bool mapRect(SkRect *dst, const SkRect &src, SkApplyPerspectiveClip pc=SkApplyPerspectiveClip::kYes) const
@ kClose_Verb
Definition SkPath.h:1463
@ kMove_Verb
Definition SkPath.h:1458
@ kConic_Verb
Definition SkPath.h:1461
@ kDone_Verb
Definition SkPath.h:1464
@ kCubic_Verb
Definition SkPath.h:1462
@ kQuad_Verb
Definition SkPath.h:1460
@ kLine_Verb
Definition SkPath.h:1459
static SkScalar DistanceToLineSegmentBetweenSqd(const SkPoint &pt, const SkPoint &a, const SkPoint &b)
Definition SkPoint.cpp:126
static SkScalar DistanceToSqd(const SkPoint &pt, const SkPoint &a)
Definition SkPointPriv.h:48
static constexpr float HalfWidth(const SkRect &r)
Definition SkRectPriv.h:62
static constexpr float HalfHeight(const SkRect &r)
Definition SkRectPriv.h:66
SkSpotShadowTessellator(const SkPath &path, const SkMatrix &ctm, const SkPoint3 &zPlaneParams, const SkPoint3 &lightPos, SkScalar lightRadius, bool transparent, bool directional)
int size() const
Definition SkTDArray.h:138
bool empty() const
Definition SkTDArray.h:135
void push_back(const T &v)
Definition SkTDArray.h:219
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 pop_back()
Definition SkTDArray.h:223
static sk_sp< SkVertices > MakeCopy(VertexMode mode, int vertexCount, const SkPoint positions[], const SkPoint texs[], const SkColor colors[], int indexCount, const uint16_t indices[])
@ kTriangles_VertexMode
Definition SkVertices.h:31
float SkScalar
Definition extension.cpp:12
uint32_t * target
static float min(float r, float g, float b)
Definition hsl.cpp:48
double y
double x
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)
bool GetSpotShadowTransform(const SkPoint3 &lightPos, SkScalar lightRadius, const SkMatrix &ctm, const SkPoint3 &zPlaneParams, const SkRect &pathBounds, bool directional, SkMatrix *shadowTransform, SkScalar *radius)
SkScalar AmbientRecipAlpha(SkScalar height)
SkScalar AmbientBlurRadius(SkScalar height)
sk_sp< SkVertices > MakeAmbient(const SkPath &path, const SkMatrix &ctm, const SkPoint3 &zPlane, bool transparent)
sk_sp< SkVertices > MakeSpot(const SkPath &path, const SkMatrix &ctm, const SkPoint3 &zPlane, const SkPoint3 &lightPos, SkScalar lightRadius, bool transparent, bool directional)
const auto kD
static SkRect inset(const SkRect &r)
SkScalar w
Point offset
static SkScalar TransformW(const SkPoint[3], SkScalar w, const SkMatrix &)
SkScalar fX
Definition SkPoint3.h:16
SkScalar fZ
Definition SkPoint3.h:16
bool isFinite() const
Definition SkPoint3.h:116
SkScalar fY
Definition SkPoint3.h:16
float fX
x-axis value
float cross(const SkVector &vec) const
float fY
y-axis value
constexpr float centerX() const
Definition SkRect.h:776
constexpr float centerY() const
Definition SkRect.h:785