2 * Copyright 2017 Google Inc.
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
9 #include "src/utils/SkShadowTessellator.h"
11 #include "include/core/SkColor.h"
12 #include "include/core/SkMatrix.h"
13 #include "include/core/SkPath.h"
14 #include "include/core/SkPoint.h"
15 #include "include/core/SkPoint3.h"
16 #include "include/core/SkRect.h"
17 #include "include/core/SkTypes.h"
18 #include "include/core/SkVertices.h"
19 #include "include/private/SkColorData.h"
20 #include "include/private/SkFloatingPoint.h"
21 #include "include/private/SkTDArray.h"
22 #include "include/private/SkTemplates.h"
23 #include "src/core/SkDrawShadowInfo.h"
24 #include "src/core/SkGeometry.h"
25 #include "src/core/SkPointPriv.h"
26 #include "src/core/SkRectPriv.h"
27 #include "src/utils/SkPolyUtils.h"
32 #include "src/gpu/ganesh/geometry/GrPathUtils.h"
39 class SkBaseShadowTessellator {
41 SkBaseShadowTessellator(const SkPoint3& zPlaneParams, const SkRect& bounds, bool transparent);
42 virtual ~SkBaseShadowTessellator() {}
44 sk_sp<SkVertices> releaseVertices() {
48 return SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode, this->vertexCount(),
49 fPositions.begin(), nullptr, fColors.begin(),
50 this->indexCount(), fIndices.begin());
54 inline static constexpr auto kMinHeight = 0.1f;
55 inline static constexpr auto kPenumbraColor = SK_ColorTRANSPARENT;
56 inline static constexpr auto kUmbraColor = SK_ColorBLACK;
58 int vertexCount() const { return fPositions.count(); }
59 int indexCount() const { return fIndices.count(); }
61 // initialization methods
62 bool accumulateCentroid(const SkPoint& c, const SkPoint& n);
63 bool checkConvexity(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2);
64 void finishPathPolygon();
66 // convex shadow methods
67 bool computeConvexShadow(SkScalar inset, SkScalar outset, bool doClip);
68 void computeClipVectorsAndTestCentroid();
69 bool clipUmbraPoint(const SkPoint& umbraPoint, const SkPoint& centroid, SkPoint* clipPoint);
70 void addEdge(const SkVector& nextPoint, const SkVector& nextNormal, SkColor umbraColor,
71 const SkTDArray<SkPoint>& umbraPolygon, bool lastEdge, bool doClip);
72 bool addInnerPoint(const SkPoint& pathPoint, SkColor umbraColor,
73 const SkTDArray<SkPoint>& umbraPolygon, int* currUmbraIndex);
74 int getClosestUmbraIndex(const SkPoint& point, const SkTDArray<SkPoint>& umbraPolygon);
76 // concave shadow methods
77 bool computeConcaveShadow(SkScalar inset, SkScalar outset);
78 void stitchConcaveRings(const SkTDArray<SkPoint>& umbraPolygon,
79 SkTDArray<int>* umbraIndices,
80 const SkTDArray<SkPoint>& penumbraPolygon,
81 SkTDArray<int>* penumbraIndices);
83 void handleLine(const SkPoint& p);
84 void handleLine(const SkMatrix& m, SkPoint* p);
86 void handleQuad(const SkPoint pts[3]);
87 void handleQuad(const SkMatrix& m, SkPoint pts[3]);
89 void handleCubic(const SkMatrix& m, SkPoint pts[4]);
91 void handleConic(const SkMatrix& m, SkPoint pts[3], SkScalar w);
93 bool addArc(const SkVector& nextNormal, SkScalar offset, bool finishArc);
95 void appendTriangle(uint16_t index0, uint16_t index1, uint16_t index2);
96 void appendQuad(uint16_t index0, uint16_t index1, uint16_t index2, uint16_t index3);
98 SkScalar heightFunc(SkScalar x, SkScalar y) {
99 return fZPlaneParams.fX*x + fZPlaneParams.fY*y + fZPlaneParams.fZ;
102 SkPoint3 fZPlaneParams;
105 SkTDArray<SkPoint> fPointBuffer;
107 SkTDArray<SkPoint> fPositions;
108 SkTDArray<SkColor> fColors;
109 SkTDArray<uint16_t> fIndices;
111 SkTDArray<SkPoint> fPathPolygon;
112 SkTDArray<SkPoint> fClipPolygon;
113 SkTDArray<SkVector> fClipVectors;
121 int fFirstVertexIndex;
122 SkVector fFirstOutset;
134 bool fPrevUmbraOutside;
135 bool fFirstUmbraOutside;
136 SkVector fPrevOutset;
140 static bool compute_normal(const SkPoint& p0, const SkPoint& p1, SkScalar dir,
141 SkVector* newNormal) {
143 // compute perpendicular
144 normal.fX = p0.fY - p1.fY;
145 normal.fY = p1.fX - p0.fX;
147 if (!normal.normalize()) {
154 static bool duplicate_pt(const SkPoint& p0, const SkPoint& p1) {
155 static constexpr SkScalar kClose = (SK_Scalar1 / 16);
156 static constexpr SkScalar kCloseSqd = kClose * kClose;
158 SkScalar distSq = SkPointPriv::DistanceToSqd(p0, p1);
159 return distSq < kCloseSqd;
162 static SkScalar perp_dot(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
163 SkVector v0 = p1 - p0;
164 SkVector v1 = p2 - p1;
168 SkBaseShadowTessellator::SkBaseShadowTessellator(const SkPoint3& zPlaneParams, const SkRect& bounds,
170 : fZPlaneParams(zPlaneParams)
171 , fPathBounds(bounds)
176 , fFirstVertexIndex(-1)
178 , fTransparent(transparent)
182 , fPrevUmbraIndex(-1)
185 , fPrevUmbraOutside(false)
186 , fFirstUmbraOutside(false) {
187 // child classes will set reserve for positions, colors and indices
190 bool SkBaseShadowTessellator::accumulateCentroid(const SkPoint& curr, const SkPoint& next) {
191 if (duplicate_pt(curr, next)) {
195 SkASSERT(fPathPolygon.count() > 0);
196 SkVector v0 = curr - fPathPolygon[0];
197 SkVector v1 = next - fPathPolygon[0];
198 SkScalar quadArea = v0.cross(v1);
199 fCentroid.fX += (v0.fX + v1.fX) * quadArea;
200 fCentroid.fY += (v0.fY + v1.fY) * quadArea;
203 if (quadArea*fLastArea < 0) {
207 fLastArea = quadArea;
213 bool SkBaseShadowTessellator::checkConvexity(const SkPoint& p0,
216 SkScalar cross = perp_dot(p0, p1, p2);
217 // skip collinear point
218 if (SkScalarNearlyZero(cross)) {
222 // check for convexity
223 if (fLastCross*cross < 0) {
233 void SkBaseShadowTessellator::finishPathPolygon() {
234 if (fPathPolygon.count() > 1) {
235 if (!this->accumulateCentroid(fPathPolygon[fPathPolygon.count() - 1], fPathPolygon[0])) {
236 // remove coincident point
241 if (fPathPolygon.count() > 2) {
242 // do this before the final convexity check, so we use the correct fPathPolygon[0]
243 fCentroid *= sk_ieee_float_divide(1, 3 * fArea);
244 fCentroid += fPathPolygon[0];
245 if (!checkConvexity(fPathPolygon[fPathPolygon.count() - 2],
246 fPathPolygon[fPathPolygon.count() - 1],
248 // remove collinear point
249 fPathPolygon[0] = fPathPolygon[fPathPolygon.count() - 1];
254 // if area is positive, winding is ccw
255 fDirection = fArea > 0 ? -1 : 1;
258 bool SkBaseShadowTessellator::computeConvexShadow(SkScalar inset, SkScalar outset, bool doClip) {
260 this->computeClipVectorsAndTestCentroid();
263 // adjust inset distance and umbra color if necessary
264 auto umbraColor = kUmbraColor;
265 SkScalar minDistSq = SkPointPriv::DistanceToLineSegmentBetweenSqd(fCentroid,
269 bounds.setBounds(&fPathPolygon[0], fPathPolygon.count());
270 for (int i = 1; i < fPathPolygon.count(); ++i) {
272 if (i == fPathPolygon.count() - 1) {
275 SkPoint currPoint = fPathPolygon[i];
276 SkPoint nextPoint = fPathPolygon[j];
277 SkScalar distSq = SkPointPriv::DistanceToLineSegmentBetweenSqd(fCentroid, currPoint,
279 if (distSq < minDistSq) {
284 SkTDArray<SkPoint> insetPolygon;
285 if (inset > SK_ScalarNearlyZero) {
286 static constexpr auto kTolerance = 1.0e-2f;
287 if (minDistSq < (inset + kTolerance)*(inset + kTolerance)) {
288 // if the umbra would collapse, we back off a bit on inner blur and adjust the alpha
289 auto newInset = SkScalarSqrt(minDistSq) - kTolerance;
290 auto ratio = 128 * (newInset / inset + 1);
291 SkASSERT(SkScalarIsFinite(ratio));
292 // they aren't PMColors, but the interpolation algorithm is the same
293 umbraColor = SkPMLerp(kUmbraColor, kPenumbraColor, (unsigned)ratio);
297 // generate inner ring
298 if (!SkInsetConvexPolygon(&fPathPolygon[0], fPathPolygon.count(), inset,
300 // not ideal, but in this case we'll inset using the centroid
304 const SkTDArray<SkPoint>& umbraPolygon = (inset > SK_ScalarNearlyZero) ? insetPolygon
307 // walk around the path polygon, generate outer ring and connect to inner ring
309 fPositions.push_back(fCentroid);
310 fColors.push_back(umbraColor);
316 int polyCount = fPathPolygon.count();
317 if (!compute_normal(fPathPolygon[polyCount - 1], fPathPolygon[0], fDirection, &fFirstOutset)) {
318 // polygon should be sanitized by this point, so this is unrecoverable
322 fFirstOutset *= outset;
323 fFirstPoint = fPathPolygon[polyCount - 1];
324 fFirstVertexIndex = fPositions.count();
325 fPrevOutset = fFirstOutset;
326 fPrevPoint = fFirstPoint;
327 fPrevUmbraIndex = -1;
329 this->addInnerPoint(fFirstPoint, umbraColor, umbraPolygon, &fPrevUmbraIndex);
331 if (!fTransparent && doClip) {
333 bool isOutside = this->clipUmbraPoint(fPositions[fFirstVertexIndex],
334 fCentroid, &clipPoint);
336 fPositions.push_back(clipPoint);
337 fColors.push_back(umbraColor);
339 fPrevUmbraOutside = isOutside;
340 fFirstUmbraOutside = isOutside;
343 SkPoint newPoint = fFirstPoint + fFirstOutset;
344 fPositions.push_back(newPoint);
345 fColors.push_back(kPenumbraColor);
346 this->addEdge(fPathPolygon[0], fFirstOutset, umbraColor, umbraPolygon, false, doClip);
348 for (int i = 1; i < polyCount; ++i) {
350 if (!compute_normal(fPrevPoint, fPathPolygon[i], fDirection, &normal)) {
354 this->addArc(normal, outset, true);
355 this->addEdge(fPathPolygon[i], normal, umbraColor, umbraPolygon,
356 i == polyCount - 1, doClip);
358 SkASSERT(this->indexCount());
361 SkASSERT(fPositions.count() >= 3);
362 if (this->addArc(fFirstOutset, outset, false)) {
363 if (fFirstUmbraOutside) {
364 this->appendTriangle(fFirstVertexIndex, fPositions.count() - 1,
365 fFirstVertexIndex + 2);
367 this->appendTriangle(fFirstVertexIndex, fPositions.count() - 1,
368 fFirstVertexIndex + 1);
371 // no arc added, fix up by setting first penumbra point position to last one
372 if (fFirstUmbraOutside) {
373 fPositions[fFirstVertexIndex + 2] = fPositions[fPositions.count() - 1];
375 fPositions[fFirstVertexIndex + 1] = fPositions[fPositions.count() - 1];
382 void SkBaseShadowTessellator::computeClipVectorsAndTestCentroid() {
383 SkASSERT(fClipPolygon.count() >= 3);
384 fCurrClipIndex = fClipPolygon.count() - 1;
387 SkVector v0 = fClipPolygon[1] - fClipPolygon[0];
388 SkVector v1 = fClipPolygon[2] - fClipPolygon[0];
389 fClipVectors.push_back(v0);
391 // init centroid check
392 bool hiddenCentroid = true;
393 v1 = fCentroid - fClipPolygon[0];
394 SkScalar initCross = v0.cross(v1);
396 for (int p = 1; p < fClipPolygon.count(); ++p) {
397 // add to clip vectors
398 v0 = fClipPolygon[(p + 1) % fClipPolygon.count()] - fClipPolygon[p];
399 fClipVectors.push_back(v0);
400 // Determine if transformed centroid is inside clipPolygon.
401 v1 = fCentroid - fClipPolygon[p];
402 if (initCross*v0.cross(v1) <= 0) {
403 hiddenCentroid = false;
406 SkASSERT(fClipVectors.count() == fClipPolygon.count());
408 fTransparent = fTransparent || !hiddenCentroid;
411 void SkBaseShadowTessellator::addEdge(const SkPoint& nextPoint, const SkVector& nextNormal,
412 SkColor umbraColor, const SkTDArray<SkPoint>& umbraPolygon,
413 bool lastEdge, bool doClip) {
414 // add next umbra point
419 currUmbraIndex = fFirstVertexIndex;
420 fPrevPoint = nextPoint;
422 duplicate = this->addInnerPoint(nextPoint, umbraColor, umbraPolygon, &currUmbraIndex);
424 int prevPenumbraIndex = duplicate || (currUmbraIndex == fFirstVertexIndex)
425 ? fPositions.count() - 1
426 : fPositions.count() - 2;
428 // add to center fan if transparent or centroid showing
430 this->appendTriangle(0, fPrevUmbraIndex, currUmbraIndex);
431 // otherwise add to clip ring
434 bool isOutside = lastEdge ? fFirstUmbraOutside
435 : this->clipUmbraPoint(fPositions[currUmbraIndex], fCentroid,
439 fPositions.push_back(clipPoint);
440 fColors.push_back(umbraColor);
442 this->appendTriangle(fPrevUmbraIndex, currUmbraIndex, currUmbraIndex + 1);
443 if (fPrevUmbraOutside) {
445 this->appendTriangle(fPrevUmbraIndex, currUmbraIndex + 1,
446 fPrevUmbraIndex + 1);
448 } else if (fPrevUmbraOutside) {
450 this->appendTriangle(fPrevUmbraIndex, currUmbraIndex, fPrevUmbraIndex + 1);
453 fPrevUmbraOutside = isOutside;
457 // add next penumbra point and quad
458 SkPoint newPoint = nextPoint + nextNormal;
459 fPositions.push_back(newPoint);
460 fColors.push_back(kPenumbraColor);
463 this->appendTriangle(fPrevUmbraIndex, prevPenumbraIndex, currUmbraIndex);
465 this->appendTriangle(prevPenumbraIndex, fPositions.count() - 1, currUmbraIndex);
467 fPrevUmbraIndex = currUmbraIndex;
468 fPrevOutset = nextNormal;
471 bool SkBaseShadowTessellator::clipUmbraPoint(const SkPoint& umbraPoint, const SkPoint& centroid,
472 SkPoint* clipPoint) {
473 SkVector segmentVector = centroid - umbraPoint;
475 int startClipPoint = fCurrClipIndex;
477 SkVector dp = umbraPoint - fClipPolygon[fCurrClipIndex];
478 SkScalar denom = fClipVectors[fCurrClipIndex].cross(segmentVector);
479 SkScalar t_num = dp.cross(segmentVector);
480 // if line segments are nearly parallel
481 if (SkScalarNearlyZero(denom)) {
483 if (SkScalarNearlyZero(t_num)) {
486 // otherwise are separate, will try the next poly segment
487 // else if crossing lies within poly segment
488 } else if (t_num >= 0 && t_num <= denom) {
489 SkScalar s_num = dp.cross(fClipVectors[fCurrClipIndex]);
490 // if umbra point is inside the clip polygon
491 if (s_num >= 0 && s_num <= denom) {
492 segmentVector *= s_num / denom;
493 *clipPoint = umbraPoint + segmentVector;
497 fCurrClipIndex = (fCurrClipIndex + 1) % fClipPolygon.count();
498 } while (fCurrClipIndex != startClipPoint);
503 bool SkBaseShadowTessellator::addInnerPoint(const SkPoint& pathPoint, SkColor umbraColor,
504 const SkTDArray<SkPoint>& umbraPolygon,
505 int* currUmbraIndex) {
508 SkVector v = fCentroid - pathPoint;
510 umbraPoint = pathPoint + v;
512 umbraPoint = umbraPolygon[this->getClosestUmbraIndex(pathPoint, umbraPolygon)];
515 fPrevPoint = pathPoint;
517 // merge "close" points
518 if (fPrevUmbraIndex == -1 ||
519 !duplicate_pt(umbraPoint, fPositions[fPrevUmbraIndex])) {
520 // if we've wrapped around, don't add a new point
521 if (fPrevUmbraIndex >= 0 && duplicate_pt(umbraPoint, fPositions[fFirstVertexIndex])) {
522 *currUmbraIndex = fFirstVertexIndex;
524 *currUmbraIndex = fPositions.count();
525 fPositions.push_back(umbraPoint);
526 fColors.push_back(umbraColor);
530 *currUmbraIndex = fPrevUmbraIndex;
535 int SkBaseShadowTessellator::getClosestUmbraIndex(const SkPoint& p,
536 const SkTDArray<SkPoint>& umbraPolygon) {
537 SkScalar minDistance = SkPointPriv::DistanceToSqd(p, umbraPolygon[fCurrUmbraIndex]);
538 int index = fCurrUmbraIndex;
540 int next = (index + dir) % umbraPolygon.count();
542 // init travel direction
543 SkScalar distance = SkPointPriv::DistanceToSqd(p, umbraPolygon[next]);
544 if (distance < minDistance) {
546 minDistance = distance;
548 dir = umbraPolygon.count() - 1;
551 // iterate until we find a point that increases the distance
552 next = (index + dir) % umbraPolygon.count();
553 distance = SkPointPriv::DistanceToSqd(p, umbraPolygon[next]);
554 while (distance < minDistance) {
556 minDistance = distance;
557 next = (index + dir) % umbraPolygon.count();
558 distance = SkPointPriv::DistanceToSqd(p, umbraPolygon[next]);
561 fCurrUmbraIndex = index;
565 bool SkBaseShadowTessellator::computeConcaveShadow(SkScalar inset, SkScalar outset) {
566 if (!SkIsSimplePolygon(&fPathPolygon[0], fPathPolygon.count())) {
570 // shouldn't inset more than the half bounds of the polygon
571 inset = std::min(inset, std::min(SkTAbs(SkRectPriv::HalfWidth(fPathBounds)),
572 SkTAbs(SkRectPriv::HalfHeight(fPathBounds))));
573 // generate inner ring
574 SkTDArray<SkPoint> umbraPolygon;
575 SkTDArray<int> umbraIndices;
576 umbraIndices.setReserve(fPathPolygon.count());
577 if (!SkOffsetSimplePolygon(&fPathPolygon[0], fPathPolygon.count(), fPathBounds, inset,
578 &umbraPolygon, &umbraIndices)) {
579 // TODO: figure out how to handle this case
583 // generate outer ring
584 SkTDArray<SkPoint> penumbraPolygon;
585 SkTDArray<int> penumbraIndices;
586 penumbraPolygon.setReserve(umbraPolygon.count());
587 penumbraIndices.setReserve(umbraPolygon.count());
588 if (!SkOffsetSimplePolygon(&fPathPolygon[0], fPathPolygon.count(), fPathBounds, -outset,
589 &penumbraPolygon, &penumbraIndices)) {
590 // TODO: figure out how to handle this case
594 if (!umbraPolygon.count() || !penumbraPolygon.count()) {
598 // attach the rings together
599 this->stitchConcaveRings(umbraPolygon, &umbraIndices, penumbraPolygon, &penumbraIndices);
604 void SkBaseShadowTessellator::stitchConcaveRings(const SkTDArray<SkPoint>& umbraPolygon,
605 SkTDArray<int>* umbraIndices,
606 const SkTDArray<SkPoint>& penumbraPolygon,
607 SkTDArray<int>* penumbraIndices) {
608 // TODO: only create and fill indexMap when fTransparent is true?
609 SkAutoSTMalloc<64, uint16_t> indexMap(umbraPolygon.count());
611 // find minimum indices
613 int min = (*penumbraIndices)[0];
614 for (int i = 1; i < (*penumbraIndices).count(); ++i) {
615 if ((*penumbraIndices)[i] < min) {
616 min = (*penumbraIndices)[i];
620 int currPenumbra = minIndex;
623 min = (*umbraIndices)[0];
624 for (int i = 1; i < (*umbraIndices).count(); ++i) {
625 if ((*umbraIndices)[i] < min) {
626 min = (*umbraIndices)[i];
630 int currUmbra = minIndex;
632 // now find a case where the indices are equal (there should be at least one)
633 int maxPenumbraIndex = fPathPolygon.count() - 1;
634 int maxUmbraIndex = fPathPolygon.count() - 1;
635 while ((*penumbraIndices)[currPenumbra] != (*umbraIndices)[currUmbra]) {
636 if ((*penumbraIndices)[currPenumbra] < (*umbraIndices)[currUmbra]) {
637 (*penumbraIndices)[currPenumbra] += fPathPolygon.count();
638 maxPenumbraIndex = (*penumbraIndices)[currPenumbra];
639 currPenumbra = (currPenumbra + 1) % penumbraPolygon.count();
641 (*umbraIndices)[currUmbra] += fPathPolygon.count();
642 maxUmbraIndex = (*umbraIndices)[currUmbra];
643 currUmbra = (currUmbra + 1) % umbraPolygon.count();
647 fPositions.push_back(penumbraPolygon[currPenumbra]);
648 fColors.push_back(kPenumbraColor);
649 int prevPenumbraIndex = 0;
650 fPositions.push_back(umbraPolygon[currUmbra]);
651 fColors.push_back(kUmbraColor);
653 indexMap[currUmbra] = 1;
655 int nextPenumbra = (currPenumbra + 1) % penumbraPolygon.count();
656 int nextUmbra = (currUmbra + 1) % umbraPolygon.count();
657 while ((*penumbraIndices)[nextPenumbra] <= maxPenumbraIndex ||
658 (*umbraIndices)[nextUmbra] <= maxUmbraIndex) {
660 if ((*umbraIndices)[nextUmbra] == (*penumbraIndices)[nextPenumbra]) {
661 // advance both one step
662 fPositions.push_back(penumbraPolygon[nextPenumbra]);
663 fColors.push_back(kPenumbraColor);
664 int currPenumbraIndex = fPositions.count() - 1;
666 fPositions.push_back(umbraPolygon[nextUmbra]);
667 fColors.push_back(kUmbraColor);
668 int currUmbraIndex = fPositions.count() - 1;
669 indexMap[nextUmbra] = currUmbraIndex;
671 this->appendQuad(prevPenumbraIndex, currPenumbraIndex,
672 fPrevUmbraIndex, currUmbraIndex);
674 prevPenumbraIndex = currPenumbraIndex;
675 (*penumbraIndices)[currPenumbra] += fPathPolygon.count();
676 currPenumbra = nextPenumbra;
677 nextPenumbra = (currPenumbra + 1) % penumbraPolygon.count();
679 fPrevUmbraIndex = currUmbraIndex;
680 (*umbraIndices)[currUmbra] += fPathPolygon.count();
681 currUmbra = nextUmbra;
682 nextUmbra = (currUmbra + 1) % umbraPolygon.count();
685 while ((*penumbraIndices)[nextPenumbra] < (*umbraIndices)[nextUmbra] &&
686 (*penumbraIndices)[nextPenumbra] <= maxPenumbraIndex) {
687 // fill out penumbra arc
688 fPositions.push_back(penumbraPolygon[nextPenumbra]);
689 fColors.push_back(kPenumbraColor);
690 int currPenumbraIndex = fPositions.count() - 1;
692 this->appendTriangle(prevPenumbraIndex, currPenumbraIndex, fPrevUmbraIndex);
694 prevPenumbraIndex = currPenumbraIndex;
695 // this ensures the ordering when we wrap around
696 (*penumbraIndices)[currPenumbra] += fPathPolygon.count();
697 currPenumbra = nextPenumbra;
698 nextPenumbra = (currPenumbra + 1) % penumbraPolygon.count();
701 while ((*umbraIndices)[nextUmbra] < (*penumbraIndices)[nextPenumbra] &&
702 (*umbraIndices)[nextUmbra] <= maxUmbraIndex) {
703 // fill out umbra arc
704 fPositions.push_back(umbraPolygon[nextUmbra]);
705 fColors.push_back(kUmbraColor);
706 int currUmbraIndex = fPositions.count() - 1;
707 indexMap[nextUmbra] = currUmbraIndex;
709 this->appendTriangle(fPrevUmbraIndex, prevPenumbraIndex, currUmbraIndex);
711 fPrevUmbraIndex = currUmbraIndex;
712 // this ensures the ordering when we wrap around
713 (*umbraIndices)[currUmbra] += fPathPolygon.count();
714 currUmbra = nextUmbra;
715 nextUmbra = (currUmbra + 1) % umbraPolygon.count();
718 // finish up by advancing both one step
719 fPositions.push_back(penumbraPolygon[nextPenumbra]);
720 fColors.push_back(kPenumbraColor);
721 int currPenumbraIndex = fPositions.count() - 1;
723 fPositions.push_back(umbraPolygon[nextUmbra]);
724 fColors.push_back(kUmbraColor);
725 int currUmbraIndex = fPositions.count() - 1;
726 indexMap[nextUmbra] = currUmbraIndex;
728 this->appendQuad(prevPenumbraIndex, currPenumbraIndex,
729 fPrevUmbraIndex, currUmbraIndex);
732 SkTriangulateSimplePolygon(umbraPolygon.begin(), indexMap, umbraPolygon.count(),
738 // tesselation tolerance values, in device space pixels
740 static constexpr SkScalar kQuadTolerance = 0.2f;
741 static constexpr SkScalar kCubicTolerance = 0.2f;
742 static constexpr SkScalar kQuadToleranceSqd = kQuadTolerance * kQuadTolerance;
743 static constexpr SkScalar kCubicToleranceSqd = kCubicTolerance * kCubicTolerance;
745 static constexpr SkScalar kConicTolerance = 0.25f;
747 // clamps the point to the nearest 16th of a pixel
748 static void sanitize_point(const SkPoint& in, SkPoint* out) {
749 out->fX = SkScalarRoundToScalar(16.f*in.fX)*0.0625f;
750 out->fY = SkScalarRoundToScalar(16.f*in.fY)*0.0625f;
753 void SkBaseShadowTessellator::handleLine(const SkPoint& p) {
755 sanitize_point(p, &pSanitized);
757 if (fPathPolygon.count() > 0) {
758 if (!this->accumulateCentroid(fPathPolygon[fPathPolygon.count() - 1], pSanitized)) {
759 // skip coincident point
764 if (fPathPolygon.count() > 1) {
765 if (!checkConvexity(fPathPolygon[fPathPolygon.count() - 2],
766 fPathPolygon[fPathPolygon.count() - 1],
768 // remove collinear point
770 // it's possible that the previous point is coincident with the new one now
771 if (duplicate_pt(fPathPolygon[fPathPolygon.count() - 1], pSanitized)) {
777 fPathPolygon.push_back(pSanitized);
780 void SkBaseShadowTessellator::handleLine(const SkMatrix& m, SkPoint* p) {
783 this->handleLine(*p);
786 void SkBaseShadowTessellator::handleQuad(const SkPoint pts[3]) {
788 // check for degeneracy
789 SkVector v0 = pts[1] - pts[0];
790 SkVector v1 = pts[2] - pts[0];
791 if (SkScalarNearlyZero(v0.cross(v1))) {
794 // TODO: Pull PathUtils out of Ganesh?
795 int maxCount = GrPathUtils::quadraticPointCount(pts, kQuadTolerance);
796 fPointBuffer.setCount(maxCount);
797 SkPoint* target = fPointBuffer.begin();
798 int count = GrPathUtils::generateQuadraticPoints(pts[0], pts[1], pts[2],
799 kQuadToleranceSqd, &target, maxCount);
800 fPointBuffer.setCount(count);
801 for (int i = 0; i < count; i++) {
802 this->handleLine(fPointBuffer[i]);
805 // for now, just to draw something
806 this->handleLine(pts[1]);
807 this->handleLine(pts[2]);
811 void SkBaseShadowTessellator::handleQuad(const SkMatrix& m, SkPoint pts[3]) {
813 this->handleQuad(pts);
816 void SkBaseShadowTessellator::handleCubic(const SkMatrix& m, SkPoint pts[4]) {
819 // TODO: Pull PathUtils out of Ganesh?
820 int maxCount = GrPathUtils::cubicPointCount(pts, kCubicTolerance);
821 fPointBuffer.setCount(maxCount);
822 SkPoint* target = fPointBuffer.begin();
823 int count = GrPathUtils::generateCubicPoints(pts[0], pts[1], pts[2], pts[3],
824 kCubicToleranceSqd, &target, maxCount);
825 fPointBuffer.setCount(count);
826 for (int i = 0; i < count; i++) {
827 this->handleLine(fPointBuffer[i]);
830 // for now, just to draw something
831 this->handleLine(pts[1]);
832 this->handleLine(pts[2]);
833 this->handleLine(pts[3]);
837 void SkBaseShadowTessellator::handleConic(const SkMatrix& m, SkPoint pts[3], SkScalar w) {
838 if (m.hasPerspective()) {
839 w = SkConic::TransformW(pts, w, m);
842 SkAutoConicToQuads quadder;
843 const SkPoint* quads = quadder.computeQuads(pts, w, kConicTolerance);
844 SkPoint lastPoint = *(quads++);
845 int count = quadder.countQuads();
846 for (int i = 0; i < count; ++i) {
848 quadPts[0] = lastPoint;
849 quadPts[1] = quads[0];
850 quadPts[2] = i == count - 1 ? pts[2] : quads[1];
851 this->handleQuad(quadPts);
852 lastPoint = quadPts[2];
857 bool SkBaseShadowTessellator::addArc(const SkVector& nextNormal, SkScalar offset, bool finishArc) {
858 // fill in fan from previous quad
859 SkScalar rotSin, rotCos;
861 if (!SkComputeRadialSteps(fPrevOutset, nextNormal, offset, &rotSin, &rotCos, &numSteps)) {
862 // recover as best we can
865 SkVector prevNormal = fPrevOutset;
866 for (int i = 0; i < numSteps-1; ++i) {
868 currNormal.fX = prevNormal.fX*rotCos - prevNormal.fY*rotSin;
869 currNormal.fY = prevNormal.fY*rotCos + prevNormal.fX*rotSin;
870 fPositions.push_back(fPrevPoint + currNormal);
871 fColors.push_back(kPenumbraColor);
872 this->appendTriangle(fPrevUmbraIndex, fPositions.count() - 1, fPositions.count() - 2);
874 prevNormal = currNormal;
876 if (finishArc && numSteps) {
877 fPositions.push_back(fPrevPoint + nextNormal);
878 fColors.push_back(kPenumbraColor);
879 this->appendTriangle(fPrevUmbraIndex, fPositions.count() - 1, fPositions.count() - 2);
881 fPrevOutset = nextNormal;
883 return (numSteps > 0);
886 void SkBaseShadowTessellator::appendTriangle(uint16_t index0, uint16_t index1, uint16_t index2) {
887 auto indices = fIndices.append(3);
894 void SkBaseShadowTessellator::appendQuad(uint16_t index0, uint16_t index1,
895 uint16_t index2, uint16_t index3) {
896 auto indices = fIndices.append(6);
907 //////////////////////////////////////////////////////////////////////////////////////////////////
909 class SkAmbientShadowTessellator : public SkBaseShadowTessellator {
911 SkAmbientShadowTessellator(const SkPath& path, const SkMatrix& ctm,
912 const SkPoint3& zPlaneParams, bool transparent);
915 bool computePathPolygon(const SkPath& path, const SkMatrix& ctm);
917 using INHERITED = SkBaseShadowTessellator;
920 SkAmbientShadowTessellator::SkAmbientShadowTessellator(const SkPath& path,
922 const SkPoint3& zPlaneParams,
924 : INHERITED(zPlaneParams, path.getBounds(), transparent) {
926 auto baseZ = heightFunc(fPathBounds.centerX(), fPathBounds.centerY());
927 // umbraColor is the interior value, penumbraColor the exterior value.
928 auto outset = SkDrawShadowMetrics::AmbientBlurRadius(baseZ);
929 auto inset = outset * SkDrawShadowMetrics::AmbientRecipAlpha(baseZ) - outset;
931 if (!this->computePathPolygon(path, ctm)) {
934 if (fPathPolygon.count() < 3 || !SkScalarIsFinite(fArea)) {
935 fSucceeded = true; // We don't want to try to blur these cases, so we will
936 // return an empty SkVertices instead.
940 // Outer ring: 3*numPts
941 // Middle ring: numPts
942 fPositions.setReserve(4 * path.countPoints());
943 fColors.setReserve(4 * path.countPoints());
944 // Outer ring: 12*numPts
946 fIndices.setReserve(12 * path.countPoints());
949 fSucceeded = this->computeConvexShadow(inset, outset, false);
951 fSucceeded = this->computeConcaveShadow(inset, outset);
955 bool SkAmbientShadowTessellator::computePathPolygon(const SkPath& path, const SkMatrix& ctm) {
956 fPathPolygon.setReserve(path.countPoints());
958 // walk around the path, tessellate and generate outer ring
959 // if original path is transparent, will accumulate sum of points for centroid
960 SkPath::Iter iter(path, true);
963 bool verbSeen = false;
964 bool closeSeen = false;
965 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
970 case SkPath::kLine_Verb:
971 this->handleLine(ctm, &pts[1]);
973 case SkPath::kQuad_Verb:
974 this->handleQuad(ctm, pts);
976 case SkPath::kCubic_Verb:
977 this->handleCubic(ctm, pts);
979 case SkPath::kConic_Verb:
980 this->handleConic(ctm, pts, iter.conicWeight());
982 case SkPath::kMove_Verb:
987 case SkPath::kClose_Verb:
988 case SkPath::kDone_Verb:
995 this->finishPathPolygon();
999 ///////////////////////////////////////////////////////////////////////////////////////////////////
1001 class SkSpotShadowTessellator : public SkBaseShadowTessellator {
1003 SkSpotShadowTessellator(const SkPath& path, const SkMatrix& ctm,
1004 const SkPoint3& zPlaneParams, const SkPoint3& lightPos,
1005 SkScalar lightRadius, bool transparent, bool directional);
1008 bool computeClipAndPathPolygons(const SkPath& path, const SkMatrix& ctm,
1009 const SkMatrix& shadowTransform);
1010 void addToClip(const SkVector& nextPoint);
1012 using INHERITED = SkBaseShadowTessellator;
1015 SkSpotShadowTessellator::SkSpotShadowTessellator(const SkPath& path, const SkMatrix& ctm,
1016 const SkPoint3& zPlaneParams,
1017 const SkPoint3& lightPos, SkScalar lightRadius,
1018 bool transparent, bool directional)
1019 : INHERITED(zPlaneParams, path.getBounds(), transparent) {
1021 // Compute the blur radius, scale and translation for the spot shadow.
1022 SkMatrix shadowTransform;
1024 if (!SkDrawShadowMetrics::GetSpotShadowTransform(lightPos, lightRadius, ctm, zPlaneParams,
1025 path.getBounds(), directional,
1026 &shadowTransform, &outset)) {
1029 SkScalar inset = outset;
1031 // compute rough clip bounds for umbra, plus offset polygon, plus centroid
1032 if (!this->computeClipAndPathPolygons(path, ctm, shadowTransform)) {
1035 if (fClipPolygon.count() < 3 || fPathPolygon.count() < 3 || !SkScalarIsFinite(fArea)) {
1036 fSucceeded = true; // We don't want to try to blur these cases, so we will
1037 // return an empty SkVertices instead.
1041 // TODO: calculate these reserves better
1042 // Penumbra ring: 3*numPts
1043 // Umbra ring: numPts
1044 // Inner ring: numPts
1045 fPositions.setReserve(5 * path.countPoints());
1046 fColors.setReserve(5 * path.countPoints());
1047 // Penumbra ring: 12*numPts
1048 // Umbra ring: 3*numPts
1049 fIndices.setReserve(15 * path.countPoints());
1052 fSucceeded = this->computeConvexShadow(inset, outset, true);
1054 fSucceeded = this->computeConcaveShadow(inset, outset);
1064 bool SkSpotShadowTessellator::computeClipAndPathPolygons(const SkPath& path, const SkMatrix& ctm,
1065 const SkMatrix& shadowTransform) {
1067 fPathPolygon.setReserve(path.countPoints());
1068 fClipPolygon.setReserve(path.countPoints());
1070 // Walk around the path and compute clip polygon and path polygon.
1071 // Will also accumulate sum of areas for centroid.
1072 // For Bezier curves, we compute additional interior points on curve.
1073 SkPath::Iter iter(path, true);
1078 // coefficients to compute cubic Bezier at t = 5/16
1079 static constexpr SkScalar kA = 0.32495117187f;
1080 static constexpr SkScalar kB = 0.44311523437f;
1081 static constexpr SkScalar kC = 0.20141601562f;
1082 static constexpr SkScalar kD = 0.03051757812f;
1086 bool closeSeen = false;
1087 bool verbSeen = false;
1088 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1093 case SkPath::kLine_Verb:
1094 ctm.mapPoints(clipPts, &pts[1], 1);
1095 this->addToClip(clipPts[0]);
1096 this->handleLine(shadowTransform, &pts[1]);
1098 case SkPath::kQuad_Verb:
1099 ctm.mapPoints(clipPts, pts, 3);
1101 curvePoint.fX = 0.25f*clipPts[0].fX + 0.5f*clipPts[1].fX + 0.25f*clipPts[2].fX;
1102 curvePoint.fY = 0.25f*clipPts[0].fY + 0.5f*clipPts[1].fY + 0.25f*clipPts[2].fY;
1103 this->addToClip(curvePoint);
1104 this->addToClip(clipPts[2]);
1105 this->handleQuad(shadowTransform, pts);
1107 case SkPath::kConic_Verb:
1108 ctm.mapPoints(clipPts, pts, 3);
1109 w = iter.conicWeight();
1111 curvePoint.fX = 0.25f*clipPts[0].fX + w*0.5f*clipPts[1].fX + 0.25f*clipPts[2].fX;
1112 curvePoint.fY = 0.25f*clipPts[0].fY + w*0.5f*clipPts[1].fY + 0.25f*clipPts[2].fY;
1113 curvePoint *= SkScalarInvert(0.5f + 0.5f*w);
1114 this->addToClip(curvePoint);
1115 this->addToClip(clipPts[2]);
1116 this->handleConic(shadowTransform, pts, w);
1118 case SkPath::kCubic_Verb:
1119 ctm.mapPoints(clipPts, pts, 4);
1120 // point at t = 5/16
1121 curvePoint.fX = kA*clipPts[0].fX + kB*clipPts[1].fX
1122 + kC*clipPts[2].fX + kD*clipPts[3].fX;
1123 curvePoint.fY = kA*clipPts[0].fY + kB*clipPts[1].fY
1124 + kC*clipPts[2].fY + kD*clipPts[3].fY;
1125 this->addToClip(curvePoint);
1126 // point at t = 11/16
1127 curvePoint.fX = kD*clipPts[0].fX + kC*clipPts[1].fX
1128 + kB*clipPts[2].fX + kA*clipPts[3].fX;
1129 curvePoint.fY = kD*clipPts[0].fY + kC*clipPts[1].fY
1130 + kB*clipPts[2].fY + kA*clipPts[3].fY;
1131 this->addToClip(curvePoint);
1132 this->addToClip(clipPts[3]);
1133 this->handleCubic(shadowTransform, pts);
1135 case SkPath::kMove_Verb:
1140 case SkPath::kClose_Verb:
1141 case SkPath::kDone_Verb:
1145 SkDEBUGFAIL("unknown verb");
1150 this->finishPathPolygon();
1154 void SkSpotShadowTessellator::addToClip(const SkPoint& point) {
1155 if (fClipPolygon.isEmpty() || !duplicate_pt(point, fClipPolygon[fClipPolygon.count() - 1])) {
1156 fClipPolygon.push_back(point);
1160 ///////////////////////////////////////////////////////////////////////////////////////////////////
1162 sk_sp<SkVertices> SkShadowTessellator::MakeAmbient(const SkPath& path, const SkMatrix& ctm,
1163 const SkPoint3& zPlane, bool transparent) {
1164 if (!ctm.mapRect(path.getBounds()).isFinite() || !zPlane.isFinite()) {
1167 SkAmbientShadowTessellator ambientTess(path, ctm, zPlane, transparent);
1168 return ambientTess.releaseVertices();
1171 sk_sp<SkVertices> SkShadowTessellator::MakeSpot(const SkPath& path, const SkMatrix& ctm,
1172 const SkPoint3& zPlane, const SkPoint3& lightPos,
1173 SkScalar lightRadius, bool transparent,
1175 if (!ctm.mapRect(path.getBounds()).isFinite() || !zPlane.isFinite() ||
1176 !lightPos.isFinite() || !(lightPos.fZ >= SK_ScalarNearlyZero) ||
1177 !SkScalarIsFinite(lightRadius) || !(lightRadius >= SK_ScalarNearlyZero)) {
1180 SkSpotShadowTessellator spotTess(path, ctm, zPlane, lightPos, lightRadius, transparent,
1182 return spotTess.releaseVertices();