Spot shadow cleanup and optimization
authorJim Van Verth <jvanverth@google.com>
Thu, 2 Feb 2017 18:06:31 +0000 (13:06 -0500)
committerSkia Commit-Bot <skia-commit-bot@chromium.org>
Thu, 2 Feb 2017 18:50:49 +0000 (18:50 +0000)
Fixes clip polygon and centroid calculation.
Clips interior umbra region against original path.

BUG=skia:6119

Change-Id: I37a740ae004c38b75405e05158d92cf9fd954eda
Reviewed-on: https://skia-review.googlesource.com/7823
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
src/utils/SkShadowTessellator.cpp

index 530cdcd..a46eb40 100755 (executable)
@@ -6,6 +6,7 @@
  */
 
 #include "SkShadowTessellator.h"
+#include "SkColorPriv.h"
 #include "SkGeometry.h"
 #include "SkPath.h"
 
@@ -59,7 +60,7 @@ private:
     SkTDArray<SkColor>  fColors;
     SkTDArray<uint16_t> fIndices;
 
-    int                 fPrevInnerIndex;
+    int                 fPrevUmbraIndex;
     SkVector            fPrevNormal;
     int                 fFirstVertex;
     SkVector            fFirstNormal;
@@ -110,7 +111,7 @@ SkAmbientShadowTessellator::SkAmbientShadowTessellator(const SkPath& path,
     , fUmbraColor(umbraColor)
     , fPenumbraColor(penumbraColor)
     , fTransparent(transparent)
-    , fPrevInnerIndex(-1) {
+    , fPrevUmbraIndex(-1) {
 
     // Outer ring: 3*numPts
     // Middle ring: numPts
@@ -154,14 +155,14 @@ SkAmbientShadowTessellator::SkAmbientShadowTessellator(const SkPath& path,
     }
 
     SkVector normal;
-    if (compute_normal(fPositions[fPrevInnerIndex], fPositions[fFirstVertex], fRadius, fDirection,
+    if (compute_normal(fPositions[fPrevUmbraIndex], fPositions[fFirstVertex], fRadius, fDirection,
                        &normal)) {
         this->addArc(normal);
 
         // close out previous arc
-        *fPositions.push() = fPositions[fPrevInnerIndex] + normal;
+        *fPositions.push() = fPositions[fPrevUmbraIndex] + normal;
         *fColors.push() = fPenumbraColor;
-        *fIndices.push() = fPrevInnerIndex;
+        *fIndices.push() = fPrevUmbraIndex;
         *fIndices.push() = fPositions.count() - 2;
         *fIndices.push() = fPositions.count() - 1;
 
@@ -169,7 +170,7 @@ SkAmbientShadowTessellator::SkAmbientShadowTessellator(const SkPath& path,
         *fPositions.push() = fPositions[fFirstVertex] + normal;
         *fColors.push() = fPenumbraColor;
 
-        *fIndices.push() = fPrevInnerIndex;
+        *fIndices.push() = fPrevUmbraIndex;
         *fIndices.push() = fPositions.count() - 2;
         *fIndices.push() = fFirstVertex;
 
@@ -183,13 +184,13 @@ SkAmbientShadowTessellator::SkAmbientShadowTessellator(const SkPath& path,
         fPositions[0] *= SkScalarFastInvert(fCentroidCount);
 
         *fIndices.push() = 0;
-        *fIndices.push() = fPrevInnerIndex;
+        *fIndices.push() = fPrevUmbraIndex;
         *fIndices.push() = fFirstVertex;
     }
 
     // final fan
     if (fPositions.count() >= 3) {
-        fPrevInnerIndex = fFirstVertex;
+        fPrevUmbraIndex = fFirstVertex;
         fPrevNormal = normal;
         this->addArc(fFirstNormal);
 
@@ -234,7 +235,7 @@ void SkAmbientShadowTessellator::handleLine(const SkPoint& p)  {
 
         fFirstVertex = fPositions.count();
         fPrevNormal = fFirstNormal;
-        fPrevInnerIndex = fFirstVertex;
+        fPrevUmbraIndex = fFirstVertex;
 
         *fPositions.push() = fInitPoints[0];
         *fColors.push() = fUmbraColor;
@@ -251,7 +252,7 @@ void SkAmbientShadowTessellator::handleLine(const SkPoint& p)  {
     }
 
     SkVector normal;
-    if (compute_normal(fPositions[fPrevInnerIndex], p, fRadius, fDirection, &normal)) {
+    if (compute_normal(fPositions[fPrevUmbraIndex], p, fRadius, fDirection, &normal)) {
         this->addArc(normal);
         this->finishArcAndAddEdge(p, normal);
     }
@@ -313,9 +314,9 @@ void SkAmbientShadowTessellator::addArc(const SkVector& nextNormal) {
         SkVector nextNormal;
         nextNormal.fX = prevNormal.fX*rotCos - prevNormal.fY*rotSin;
         nextNormal.fY = prevNormal.fY*rotCos + prevNormal.fX*rotSin;
-        *fPositions.push() = fPositions[fPrevInnerIndex] + nextNormal;
+        *fPositions.push() = fPositions[fPrevUmbraIndex] + nextNormal;
         *fColors.push() = fPenumbraColor;
-        *fIndices.push() = fPrevInnerIndex;
+        *fIndices.push() = fPrevUmbraIndex;
         *fIndices.push() = fPositions.count() - 2;
         *fIndices.push() = fPositions.count() - 1;
 
@@ -326,9 +327,9 @@ void SkAmbientShadowTessellator::addArc(const SkVector& nextNormal) {
 void SkAmbientShadowTessellator::finishArcAndAddEdge(const SkPoint& nextPoint,
                                                      const SkVector& nextNormal) {
     // close out previous arc
-    *fPositions.push() = fPositions[fPrevInnerIndex] + nextNormal;
+    *fPositions.push() = fPositions[fPrevUmbraIndex] + nextNormal;
     *fColors.push() = fPenumbraColor;
-    *fIndices.push() = fPrevInnerIndex;
+    *fIndices.push() = fPrevUmbraIndex;
     *fIndices.push() = fPositions.count() - 2;
     *fIndices.push() = fPositions.count() - 1;
 
@@ -342,7 +343,7 @@ void SkAmbientShadowTessellator::addEdge(const SkPoint& nextPoint, const SkVecto
     *fPositions.push() = nextPoint + nextNormal;
     *fColors.push() = fPenumbraColor;
 
-    *fIndices.push() = fPrevInnerIndex;
+    *fIndices.push() = fPrevUmbraIndex;
     *fIndices.push() = fPositions.count() - 3;
     *fIndices.push() = fPositions.count() - 2;
 
@@ -356,11 +357,11 @@ void SkAmbientShadowTessellator::addEdge(const SkPoint& nextPoint, const SkVecto
         ++fCentroidCount;
 
         *fIndices.push() = 0;
-        *fIndices.push() = fPrevInnerIndex;
+        *fIndices.push() = fPrevUmbraIndex;
         *fIndices.push() = fPositions.count() - 2;
     }
 
-    fPrevInnerIndex = fPositions.count() - 2;
+    fPrevUmbraIndex = fPositions.count() - 2;
     fPrevNormal = nextNormal;
 }
 
@@ -389,6 +390,9 @@ public:
 
 private:
     void computeClipBounds(const SkPath& path);
+    void checkUmbraAndTransformCentroid(SkScalar scale, const SkVector& xlate,
+                                        bool useDistanceToPoint);
+    bool clipUmbraPoint(const SkPoint& umbraPoint, const SkPoint& centroid, SkPoint* clipPoint);
 
     void handleLine(const SkPoint& p);
     void handleLine(SkScalar scale, const SkVector& xlate, SkPoint p);
@@ -401,7 +405,7 @@ private:
     void handleConic(SkScalar scale, const SkVector& xlate, SkPoint pts[3], SkScalar w);
 
     void mapPoints(SkScalar scale, const SkVector& xlate, SkPoint* pts, int count);
-    void addInnerPoint(const SkPoint& pathPoint, SkColor umbraColor, SkScalar radiusSqd);
+    void addInnerPoint(const SkPoint& pathPoint);
     void addArc(const SkVector& nextNormal);
     void finishArcAndAddEdge(const SkVector& nextPoint, const SkVector& nextNormal);
     void addEdge(const SkVector& nextPoint, const SkVector& nextNormal);
@@ -409,12 +413,14 @@ private:
     SkScalar            fRadius;
     SkColor             fUmbraColor;
     SkColor             fPenumbraColor;
+    bool                fTransparent;
+    bool                fValidUmbra;
 
     SkTDArray<SkPoint>  fPositions;
     SkTDArray<SkColor>  fColors;
     SkTDArray<uint16_t> fIndices;
 
-    int                 fPrevInnerIndex;
+    int                 fPrevUmbraIndex;
     SkPoint             fPrevPoint;
     SkVector            fPrevNormal;
     int                 fFirstVertex;
@@ -424,6 +430,10 @@ private:
 
     SkPoint             fCentroid;
     SkTDArray<SkPoint>  fClipPolygon;
+    SkTDArray<SkVector> fClipVectors;
+    int                 fCurrPolyPoint;
+    bool                fPrevUmbraOutside;
+    bool                fFirstUmbraOutside;
 
     // first three points
     SkTDArray<SkPoint>  fInitPoints;
@@ -431,42 +441,55 @@ private:
     SkTDArray<SkPoint>  fPointBuffer;
 };
 
+
+
 SkSpotShadowTessellator::SkSpotShadowTessellator(const SkPath& path,
                                                  SkScalar scale, const SkVector& translate,
                                                  SkScalar radius,
                                                  SkColor umbraColor, SkColor penumbraColor,
-                                                 bool /* transparent */)
+                                                 bool transparent)
     : fRadius(radius)
     , fUmbraColor(umbraColor)
     , fPenumbraColor(penumbraColor)
-    , fPrevInnerIndex(-1) {
+    , fTransparent(transparent)
+    , fPrevUmbraIndex(-1) {
 
     // TODO: calculate these better
-    // Outer ring: 3*numPts
+    // Penumbra ring: 3*numPts
+    // Umbra ring: numPts
     // Inner ring: numPts
-    fPositions.setReserve(4 * path.countPoints());
-    fColors.setReserve(4 * path.countPoints());
-    // Outer ring: 12*numPts
-    // Inner ring: 0
-    fIndices.setReserve(12 * path.countPoints());
+    fPositions.setReserve(5 * path.countPoints());
+    fColors.setReserve(5 * path.countPoints());
+    // Penumbra ring: 12*numPts
+    // Umbra ring: 3*numPts
+    fIndices.setReserve(15 * path.countPoints());
 
     fInitPoints.setReserve(3);
 
     fClipPolygon.setReserve(path.countPoints());
+    // compute rough clip bounds for umbra, plus centroid
     this->computeClipBounds(path);
+    if (fClipPolygon.count() < 3) {
+        return;
+    }
     // We are going to apply 'scale' and 'xlate' (in that order) to each computed path point. We
     // want the effect to be to scale the points relative to the path centroid and then translate
     // them by the 'translate' param we were passed.
     SkVector xlate = fCentroid * (1.f - scale) + translate;
-    // Also translate the centroid by the global translate.
-    fCentroid += translate;
+
+    // check to see if we have a valid umbra at all
+    bool usePointCheck = path.isRRect(nullptr) || path.isRect(nullptr) || path.isOval(nullptr);
+    this->checkUmbraAndTransformCentroid(scale, translate, usePointCheck);
+    fPrevUmbraOutside = false;
 
     // walk around the path, tessellate and generate inner and outer rings
     SkPath::Iter iter(path, true);
     SkPoint pts[4];
     SkPath::Verb verb;
-    *fPositions.push() = fCentroid;
-    *fColors.push() = fUmbraColor;
+    if (fTransparent) {
+        *fPositions.push() = fCentroid;
+        *fColors.push() = fUmbraColor;
+    }
     while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
         switch (verb) {
             case SkPath::kLine_Verb:
@@ -496,44 +519,69 @@ SkSpotShadowTessellator::SkSpotShadowTessellator(const SkPath& path,
         // close out previous arc
         *fPositions.push() = fPrevPoint + normal;
         *fColors.push() = fPenumbraColor;
-        *fIndices.push() = fPrevInnerIndex;
+        *fIndices.push() = fPrevUmbraIndex;
         *fIndices.push() = fPositions.count() - 2;
         *fIndices.push() = fPositions.count() - 1;
 
+        // add to center fan
+        if (fTransparent) {
+            *fIndices.push() = 0;
+            *fIndices.push() = fPrevUmbraIndex;
+            *fIndices.push() = fFirstVertex;
+            // or to clip ring
+        } else {
+            if (fFirstUmbraOutside) {
+                *fIndices.push() = fPrevUmbraIndex;
+                *fIndices.push() = fFirstVertex;
+                *fIndices.push() = fFirstVertex + 1;
+                if (fPrevUmbraOutside) {
+                    // fill out quad
+                    *fIndices.push() = fPrevUmbraIndex;
+                    *fIndices.push() = fFirstVertex + 1;
+                    *fIndices.push() = fPrevUmbraIndex + 1;
+                }
+            } else if (fPrevUmbraOutside) {
+                // add tri
+                *fIndices.push() = fPrevUmbraIndex;
+                *fIndices.push() = fFirstVertex;
+                *fIndices.push() = fPrevUmbraIndex + 1;
+            }
+        }
+
         // add final edge
         *fPositions.push() = fFirstPoint + normal;
         *fColors.push() = fPenumbraColor;
 
-        *fIndices.push() = fPrevInnerIndex;
+        *fIndices.push() = fPrevUmbraIndex;
         *fIndices.push() = fPositions.count() - 2;
         *fIndices.push() = fFirstVertex;
 
         *fIndices.push() = fPositions.count() - 2;
         *fIndices.push() = fPositions.count() - 1;
         *fIndices.push() = fFirstVertex;
-
-        // add to center fan
-        *fIndices.push() = 0;
-        *fIndices.push() = fPrevInnerIndex;
-        *fIndices.push() = fFirstVertex;
     }
 
     // final fan
     if (fPositions.count() >= 3) {
-        fPrevInnerIndex = fFirstVertex;
+        fPrevUmbraIndex = fFirstVertex;
         fPrevPoint = fFirstPoint;
         fPrevNormal = normal;
         this->addArc(fFirstNormal);
 
         *fIndices.push() = fFirstVertex;
         *fIndices.push() = fPositions.count() - 1;
-        *fIndices.push() = fFirstVertex + 1;
+        if (fFirstUmbraOutside) {
+            *fIndices.push() = fFirstVertex + 2;
+        } else {
+            *fIndices.push() = fFirstVertex + 1;
+        }
     }
 }
 
 void SkSpotShadowTessellator::computeClipBounds(const SkPath& path) {
     // walk around the path and compute clip polygon
     // if original path is transparent, will accumulate sum of points for centroid
+    // for Bezier curves, we compute additional interior points on curve
     SkPath::Iter iter(path, true);
     SkPoint pts[4];
     SkPath::Verb verb;
@@ -542,6 +590,14 @@ void SkSpotShadowTessellator::computeClipBounds(const SkPath& path) {
     int centroidCount = 0;
     fClipPolygon.reset();
 
+    // coefficients to compute cubic Bezier at t = 5/16
+    const SkScalar kA = 0.32495117187f;
+    const SkScalar kB = 0.44311523437f;
+    const SkScalar kC = 0.20141601562f;
+    const SkScalar kD = 0.03051757812f;
+
+    SkPoint curvePoint;
+    SkScalar w;
     while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
         switch (verb) {
             case SkPath::kMove_Verb:
@@ -552,23 +608,41 @@ void SkSpotShadowTessellator::computeClipBounds(const SkPath& path) {
                 *fClipPolygon.push() = pts[1];
                 break;
             case SkPath::kQuad_Verb:
-                fCentroid += pts[1];
+                // point at t = 1/2
+                curvePoint.fX = 0.25f*pts[0].fX + 0.5f*pts[1].fX + 0.25f*pts[2].fX;
+                curvePoint.fY = 0.25f*pts[0].fY + 0.5f*pts[1].fY + 0.25f*pts[2].fY;
+                *fClipPolygon.push() = curvePoint;
+                fCentroid += curvePoint;
+                *fClipPolygon.push() = pts[2];
                 fCentroid += pts[2];
                 centroidCount += 2;
-                *fClipPolygon.push() = pts[2];
                 break;
             case SkPath::kConic_Verb:
-                fCentroid += pts[1];
+                // point at t = 1/2
+                w = iter.conicWeight();
+                curvePoint.fX = 0.25f*pts[0].fX + w*0.5f*pts[1].fX + 0.25f*pts[2].fX;
+                curvePoint.fY = 0.25f*pts[0].fY + w*0.5f*pts[1].fY + 0.25f*pts[2].fY;
+                curvePoint *= SkScalarInvert(0.5f + 0.5f*w);
+                *fClipPolygon.push() = curvePoint;
+                fCentroid += curvePoint;
+                *fClipPolygon.push() = pts[2];
                 fCentroid += pts[2];
                 centroidCount += 2;
-                *fClipPolygon.push() = pts[2];
                 break;
             case SkPath::kCubic_Verb:
-                fCentroid += pts[1];
-                fCentroid += pts[2];
+                // point at t = 5/16
+                curvePoint.fX = kA*pts[0].fX + kB*pts[1].fX + kC*pts[2].fX + kD*pts[3].fX;
+                curvePoint.fY = kA*pts[0].fY + kB*pts[1].fY + kC*pts[2].fY + kD*pts[3].fY;
+                *fClipPolygon.push() = curvePoint;
+                fCentroid += curvePoint;
+                // point at t = 11/16
+                curvePoint.fX = kD*pts[0].fX + kC*pts[1].fX + kB*pts[2].fX + kA*pts[3].fX;
+                curvePoint.fY = kD*pts[0].fY + kC*pts[1].fY + kB*pts[2].fY + kA*pts[3].fY;
+                *fClipPolygon.push() = curvePoint;
+                fCentroid += curvePoint;
+                *fClipPolygon.push() = pts[3];
                 fCentroid += pts[3];
                 centroidCount += 3;
-                *fClipPolygon.push() = pts[3];
                 break;
             case SkPath::kClose_Verb:
                 break;
@@ -578,6 +652,113 @@ void SkSpotShadowTessellator::computeClipBounds(const SkPath& path) {
     }
 
     fCentroid *= SkScalarInvert(centroidCount);
+    fCurrPolyPoint = fClipPolygon.count() - 1;
+}
+
+void SkSpotShadowTessellator::checkUmbraAndTransformCentroid(SkScalar scale,
+                                                             const SkVector& xlate,
+                                                             bool useDistanceToPoint) {
+    SkASSERT(fClipPolygon.count() >= 3);
+    SkPoint transformedCentroid = fCentroid;
+    transformedCentroid += xlate;
+
+    SkScalar localRadius = fRadius / scale;
+    localRadius *= localRadius;
+
+    // init umbra check
+    SkVector w = fCentroid - fClipPolygon[0];
+    SkVector v0 = fClipPolygon[1] - fClipPolygon[0];
+    *fClipVectors.push() = v0;
+    bool validUmbra;
+    SkScalar minDistance;
+    // check distance against line segment
+    if (useDistanceToPoint) {
+        minDistance = w.lengthSqd();
+    } else {
+        SkScalar vSq = v0.dot(v0);
+        SkScalar wDotV = w.dot(v0);
+        minDistance = w.dot(w) - wDotV*wDotV/vSq;
+    }
+    validUmbra = (minDistance >= localRadius);
+
+    // init centroid check
+    bool hiddenCentroid = true;
+    SkVector v1 = transformedCentroid - fClipPolygon[0];
+    SkScalar initCross = v0.cross(v1);
+
+    for (int p = 1; p < fClipPolygon.count(); ++p) {
+        // Determine whether we have a real umbra by insetting clipPolygon by radius/scale
+        // and see if it extends past centroid.
+        // TODO: adjust this later for more accurate umbra calcs
+        w = fCentroid - fClipPolygon[p];
+        v0 = fClipPolygon[(p + 1) % fClipPolygon.count()] - fClipPolygon[p];
+        *fClipVectors.push() = v0;
+        // check distance against line segment
+        SkScalar distance;
+        if (useDistanceToPoint) {
+            distance = w.lengthSqd();
+        } else {
+            SkScalar vSq = v0.dot(v0);
+            SkScalar wDotV = w.dot(v0);
+            distance = w.dot(w) - wDotV*wDotV/vSq;
+        }
+        if (distance < localRadius) {
+            validUmbra = false;
+        }
+        if (distance < minDistance) {
+            minDistance = distance;
+        }
+        // Determine if transformed centroid is inside clipPolygon.
+        v1 = transformedCentroid - fClipPolygon[p];
+        if (initCross*v0.cross(v1) <= 0) {
+            hiddenCentroid = false;
+        }
+    }
+    SkASSERT(fClipVectors.count() == fClipPolygon.count());
+
+    if (!validUmbra) {
+        SkScalar ratio = 256 * SkScalarSqrt(minDistance / localRadius);
+        // they aren't PMColors, but the interpolation algorithm is the same
+        fUmbraColor = SkPMLerp(fUmbraColor, fPenumbraColor, (unsigned)ratio);
+    }
+
+    fTransparent = fTransparent || !hiddenCentroid || !validUmbra;
+    fValidUmbra = validUmbra;
+    fCentroid = transformedCentroid;
+}
+
+bool SkSpotShadowTessellator::clipUmbraPoint(const SkPoint& umbraPoint, const SkPoint& centroid,
+                                             SkPoint* clipPoint) {
+    SkVector segmentVector = centroid - umbraPoint;
+
+    int startPolyPoint = fCurrPolyPoint;
+    do {
+        SkVector dp = umbraPoint - fClipPolygon[fCurrPolyPoint];
+        SkScalar denom = fClipVectors[fCurrPolyPoint].cross(segmentVector);
+        SkScalar t_num = dp.cross(segmentVector);
+        // if line segments are nearly parallel
+        if (SkScalarNearlyZero(denom)) {
+            // and collinear
+            if (SkScalarNearlyZero(t_num)) {
+                return false;
+            }
+            // otherwise are separate, will try the next poly segment
+        // else if crossing lies within poly segment
+        } else if (t_num >= 0 && t_num <= denom) {
+            SkScalar s_num = dp.cross(fClipVectors[fCurrPolyPoint]);
+            // if umbra point is inside the clip polygon
+            if (s_num < 0) {
+                return false;
+            } else {
+                segmentVector *= s_num/denom;
+                *clipPoint = umbraPoint + segmentVector;
+                return true;
+            }
+        }
+        fCurrPolyPoint = (fCurrPolyPoint + 1) % fClipPolygon.count();
+    } while (fCurrPolyPoint != startPolyPoint);
+
+    return false;
 }
 
 void SkSpotShadowTessellator::mapPoints(SkScalar scale, const SkVector& xlate,
@@ -621,9 +802,21 @@ void SkSpotShadowTessellator::handleLine(const SkPoint& p) {
         fFirstVertex = fPositions.count();
         fPrevNormal = fFirstNormal;
         fPrevPoint = fFirstPoint;
-        fPrevInnerIndex = fFirstVertex;
+        fPrevUmbraIndex = fFirstVertex;
+
+        this->addInnerPoint(fFirstPoint);
+
+        if (!fTransparent) {
+            SkPoint clipPoint;
+            bool isOutside = this->clipUmbraPoint(fPositions[fFirstVertex], fCentroid, &clipPoint);
+            if (isOutside) {
+                *fPositions.push() = clipPoint;
+                *fColors.push() = fUmbraColor;
+            }
+            fPrevUmbraOutside = isOutside;
+            fFirstUmbraOutside = isOutside;
+        }
 
-        this->addInnerPoint(fFirstPoint, fUmbraColor, fRadius);
         SkPoint newPoint = fFirstPoint + fFirstNormal;
         *fPositions.push() = newPoint;
         *fColors.push() = fPenumbraColor;
@@ -699,21 +892,21 @@ void SkSpotShadowTessellator::handleConic(SkScalar scale, const SkVector& xlate,
     }
 }
 
-void SkSpotShadowTessellator::addInnerPoint(const SkPoint& pathPoint, SkColor umbraColor,
-                                            SkScalar radius) {
+void SkSpotShadowTessellator::addInnerPoint(const SkPoint& pathPoint) {
     SkVector v = fCentroid - pathPoint;
     SkScalar distance = v.length();
-    if (distance < radius) {
-        *fPositions.push() = fCentroid;
-        *fColors.push() = umbraColor; // fix this
-        // TODO: deal with fanning from centroid
+    SkScalar t;
+    if (fValidUmbra) {
+        SkASSERT(distance >= fRadius);
+        t = fRadius / distance;
     } else {
-        SkScalar t = radius / distance;
-        v *= t;
-        SkPoint innerPoint = pathPoint + v;
-        *fPositions.push() = innerPoint;
-        *fColors.push() = umbraColor;
+        t = 0.95f;
     }
+    v *= t;
+    SkPoint umbraPoint = pathPoint + v;
+    *fPositions.push() = umbraPoint;
+    *fColors.push() = fUmbraColor;
+
     fPrevPoint = pathPoint;
 }
 
@@ -729,7 +922,7 @@ void SkSpotShadowTessellator::addArc(const SkVector& nextNormal) {
         nextNormal.fY = prevNormal.fY*rotCos + prevNormal.fX*rotSin;
         *fPositions.push() = fPrevPoint + nextNormal;
         *fColors.push() = fPenumbraColor;
-        *fIndices.push() = fPrevInnerIndex;
+        *fIndices.push() = fPrevUmbraIndex;
         *fIndices.push() = fPositions.count() - 2;
         *fIndices.push() = fPositions.count() - 1;
 
@@ -743,7 +936,7 @@ void SkSpotShadowTessellator::finishArcAndAddEdge(const SkPoint& nextPoint,
     SkPoint newPoint = fPrevPoint + nextNormal;
     *fPositions.push() = newPoint;
     *fColors.push() = fPenumbraColor;
-    *fIndices.push() = fPrevInnerIndex;
+    *fIndices.push() = fPrevUmbraIndex;
     *fIndices.push() = fPositions.count() - 2;
     *fIndices.push() = fPositions.count() - 1;
 
@@ -751,26 +944,58 @@ void SkSpotShadowTessellator::finishArcAndAddEdge(const SkPoint& nextPoint,
 }
 
 void SkSpotShadowTessellator::addEdge(const SkPoint& nextPoint, const SkVector& nextNormal) {
-    // add next quad
-    this->addInnerPoint(nextPoint, fUmbraColor, fRadius);
+    // add next umbra point
+    this->addInnerPoint(nextPoint);
+    int prevPenumbraIndex = fPositions.count() - 2;
+    int currUmbraIndex = fPositions.count() - 1;
+
+    // add to center fan if transparent or centroid showing
+    if (fTransparent) {
+        *fIndices.push() = 0;
+        *fIndices.push() = fPrevUmbraIndex;
+        *fIndices.push() = currUmbraIndex;
+    // otherwise add to clip ring
+    } else {
+        if (!fTransparent) {
+            SkPoint clipPoint;
+            bool isOutside = clipUmbraPoint(fPositions[currUmbraIndex], fCentroid, &clipPoint);
+            if (isOutside) {
+                *fPositions.push() = clipPoint;
+                *fColors.push() = fUmbraColor;
+
+                *fIndices.push() = fPrevUmbraIndex;
+                *fIndices.push() = currUmbraIndex;
+                *fIndices.push() = currUmbraIndex + 1;
+                if (fPrevUmbraOutside) {
+                    // fill out quad
+                    *fIndices.push() = fPrevUmbraIndex;
+                    *fIndices.push() = currUmbraIndex + 1;
+                    *fIndices.push() = fPrevUmbraIndex + 1;
+                }
+            } else if (fPrevUmbraOutside) {
+                // add tri
+                *fIndices.push() = fPrevUmbraIndex;
+                *fIndices.push() = currUmbraIndex;
+                *fIndices.push() = fPrevUmbraIndex + 1;
+            }
+            fPrevUmbraOutside = isOutside;
+        }
+    }
+
+    // add next penumbra point and quad
     SkPoint newPoint = nextPoint + nextNormal;
     *fPositions.push() = newPoint;
     *fColors.push() = fPenumbraColor;
 
-    *fIndices.push() = fPrevInnerIndex;
-    *fIndices.push() = fPositions.count() - 3;
-    *fIndices.push() = fPositions.count() - 2;
+    *fIndices.push() = fPrevUmbraIndex;
+    *fIndices.push() = prevPenumbraIndex;
+    *fIndices.push() = currUmbraIndex;
 
-    *fIndices.push() = fPositions.count() - 3;
+    *fIndices.push() = prevPenumbraIndex;
     *fIndices.push() = fPositions.count() - 1;
-    *fIndices.push() = fPositions.count() - 2;
-
-    // add to center fan
-    *fIndices.push() = 0;
-    *fIndices.push() = fPrevInnerIndex;
-    *fIndices.push() = fPositions.count() - 2;
+    *fIndices.push() = currUmbraIndex;
 
-    fPrevInnerIndex = fPositions.count() - 2;
+    fPrevUmbraIndex = currUmbraIndex;
     fPrevNormal = nextNormal;
 }