Fix some shadow issues.
* Clamp path polygon points to nearest 1/16th of a pixel to help
with floating point issues.
* Added check for multiple contour paths.
* Return empty SkVertices for certain degenerate cases to avoid
unnecessary blurs.
* Check iteration count in SkOffsetPolygon to avoid infinite loops.
* Add new tests to verify these.
Bug: skia:
Change-Id: Ie6ad48d2504e065dcc822609d369f90c56ef3ad3
Reviewed-on: https://skia-review.googlesource.com/136701
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Jim Van Verth <jvanverth@google.com>
diff --git a/src/utils/SkShadowTessellator.cpp b/src/utils/SkShadowTessellator.cpp
index 79f8f22..b454e5f 100755
--- a/src/utils/SkShadowTessellator.cpp
+++ b/src/utils/SkShadowTessellator.cpp
@@ -209,12 +209,10 @@
fCentroid.fY += (curr.fY + next.fY) * quadArea;
fArea += quadArea;
// convexity check
- if (!SkScalarNearlyZero(quadArea)) {
- if (quadArea*fLastArea < 0) {
- ++fAreaSignFlips;
- }
- fLastArea = quadArea;
+ if (quadArea*fLastArea < 0) {
+ ++fAreaSignFlips;
}
+ fLastArea = quadArea;
return true;
}
@@ -400,9 +398,18 @@
#endif
static const SkScalar kConicTolerance = 0.5f;
+// clamps the point to the nearest 16th of a pixel
+static void sanitize_point(const SkPoint& in, SkPoint* out) {
+ out->fX = SkScalarRoundToScalar(16.f*in.fX)*0.0625f;
+ out->fY = SkScalarRoundToScalar(16.f*in.fY)*0.0625f;
+}
+
void SkBaseShadowTessellator::handleLine(const SkPoint& p) {
+ SkPoint pSanitized;
+ sanitize_point(p, &pSanitized);
+
if (fPathPolygon.count() > 0) {
- if (!this->accumulateCentroid(fPathPolygon[fPathPolygon.count() - 1], p)) {
+ if (!this->accumulateCentroid(fPathPolygon[fPathPolygon.count() - 1], pSanitized)) {
// skip coincident point
return;
}
@@ -411,13 +418,13 @@
if (fPathPolygon.count() > 1) {
if (!checkConvexity(fPathPolygon[fPathPolygon.count() - 2],
fPathPolygon[fPathPolygon.count() - 1],
- p)) {
+ pSanitized)) {
// remove collinear point
fPathPolygon.pop();
}
}
- *fPathPolygon.push() = p;
+ *fPathPolygon.push() = pSanitized;
}
void SkBaseShadowTessellator::handleLine(const SkMatrix& m, SkPoint* p) {
@@ -618,7 +625,7 @@
const SkPoint3& zPlaneParams, bool transparent);
private:
- void computePathPolygon(const SkPath& path, const SkMatrix& ctm);
+ bool computePathPolygon(const SkPath& path, const SkMatrix& ctm);
bool computeConvexShadow();
bool computeConcaveShadow();
@@ -667,6 +674,15 @@
return;
}
+ if (!this->computePathPolygon(path, ctm)) {
+ return;
+ }
+ if (fPathPolygon.count() < 3 || !SkScalarIsFinite(fArea)) {
+ fSucceeded = true; // We don't want to try to blur these cases, so we will
+ // return an empty SkVertices instead.
+ return;
+ }
+
// Outer ring: 3*numPts
// Middle ring: numPts
fPositions.setReserve(4 * path.countPoints());
@@ -675,7 +691,6 @@
// Middle ring: 0
fIndices.setReserve(12 * path.countPoints());
- this->computePathPolygon(path, ctm);
if (fIsConvex) {
fSucceeded = this->computeConvexShadow();
} else {
@@ -683,7 +698,7 @@
}
}
-void SkAmbientShadowTessellator::computePathPolygon(const SkPath& path, const SkMatrix& ctm) {
+bool SkAmbientShadowTessellator::computePathPolygon(const SkPath& path, const SkMatrix& ctm) {
fPathPolygon.setReserve(path.countPoints());
// walk around the path, tessellate and generate outer ring
@@ -691,28 +706,40 @@
SkPath::Iter iter(path, true);
SkPoint pts[4];
SkPath::Verb verb;
+ bool verbSeen = false;
+ bool closeSeen = false;
while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
- switch (verb) {
- case SkPath::kLine_Verb:
- this->handleLine(ctm, &pts[1]);
- break;
- case SkPath::kQuad_Verb:
- this->handleQuad(ctm, pts);
- break;
- case SkPath::kCubic_Verb:
- this->handleCubic(ctm, pts);
- break;
- case SkPath::kConic_Verb:
- this->handleConic(ctm, pts, iter.conicWeight());
- break;
- case SkPath::kMove_Verb:
- case SkPath::kClose_Verb:
- case SkPath::kDone_Verb:
- break;
+ if (closeSeen) {
+ return false;
}
+ switch (verb) {
+ case SkPath::kLine_Verb:
+ this->handleLine(ctm, &pts[1]);
+ break;
+ case SkPath::kQuad_Verb:
+ this->handleQuad(ctm, pts);
+ break;
+ case SkPath::kCubic_Verb:
+ this->handleCubic(ctm, pts);
+ break;
+ case SkPath::kConic_Verb:
+ this->handleConic(ctm, pts, iter.conicWeight());
+ break;
+ case SkPath::kMove_Verb:
+ if (verbSeen) {
+ return false;
+ }
+ break;
+ case SkPath::kClose_Verb:
+ case SkPath::kDone_Verb:
+ closeSeen = true;
+ break;
+ }
+ verbSeen = true;
}
this->finishPathPolygon();
+ return true;
}
bool SkAmbientShadowTessellator::computeConvexShadow() {
@@ -945,7 +972,7 @@
SkScalar lightRadius, bool transparent);
private:
- void computeClipAndPathPolygons(const SkPath& path, const SkMatrix& ctm,
+ bool computeClipAndPathPolygons(const SkPath& path, const SkMatrix& ctm,
const SkMatrix& shadowTransform);
void computeClipVectorsAndTestCentroid();
bool clipUmbraPoint(const SkPoint& umbraPoint, const SkPoint& centroid, SkPoint* clipPoint);
@@ -1035,20 +1062,13 @@
return;
}
- // TODO: calculate these reserves better
- // Penumbra ring: 3*numPts
- // Umbra ring: numPts
- // Inner ring: numPts
- fPositions.setReserve(5 * path.countPoints());
- fColors.setReserve(5 * path.countPoints());
- // Penumbra ring: 12*numPts
- // Umbra ring: 3*numPts
- fIndices.setReserve(15 * path.countPoints());
- fClipPolygon.setReserve(path.countPoints());
-
// compute rough clip bounds for umbra, plus offset polygon, plus centroid
- this->computeClipAndPathPolygons(path, ctm, shadowTransform);
- if (fClipPolygon.count() < 3 || fPathPolygon.count() < 3) {
+ if (!this->computeClipAndPathPolygons(path, ctm, shadowTransform)) {
+ return;
+ }
+ if (fClipPolygon.count() < 3 || fPathPolygon.count() < 3 || !SkScalarIsFinite(fArea)) {
+ fSucceeded = true; // We don't want to try to blur these cases, so we will
+ // return an empty SkVertices instead.
return;
}
@@ -1087,6 +1107,16 @@
}
}
+ // TODO: calculate these reserves better
+ // Penumbra ring: 3*numPts
+ // Umbra ring: numPts
+ // Inner ring: numPts
+ fPositions.setReserve(5 * path.countPoints());
+ fColors.setReserve(5 * path.countPoints());
+ // Penumbra ring: 12*numPts
+ // Umbra ring: 3*numPts
+ fIndices.setReserve(15 * path.countPoints());
+
if (fIsConvex) {
fSucceeded = this->computeConvexShadow(radius);
} else {
@@ -1134,10 +1164,11 @@
}
}
-void SkSpotShadowTessellator::computeClipAndPathPolygons(const SkPath& path, const SkMatrix& ctm,
+bool SkSpotShadowTessellator::computeClipAndPathPolygons(const SkPath& path, const SkMatrix& ctm,
const SkMatrix& shadowTransform) {
fPathPolygon.setReserve(path.countPoints());
+ fClipPolygon.setReserve(path.countPoints());
// Walk around the path and compute clip polygon and path polygon.
// Will also accumulate sum of areas for centroid.
@@ -1146,8 +1177,6 @@
SkPoint pts[4];
SkPath::Verb verb;
- fClipPolygon.reset();
-
// coefficients to compute cubic Bezier at t = 5/16
static constexpr SkScalar kA = 0.32495117187f;
static constexpr SkScalar kB = 0.44311523437f;
@@ -1156,7 +1185,12 @@
SkPoint curvePoint;
SkScalar w;
+ bool closeSeen = false;
+ bool verbSeen = false;
while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+ if (closeSeen) {
+ return false;
+ }
switch (verb) {
case SkPath::kLine_Verb:
ctm.mapPoints(&pts[1], 1);
@@ -1197,16 +1231,23 @@
this->handleCubic(shadowTransform, pts);
break;
case SkPath::kMove_Verb:
+ if (verbSeen) {
+ return false;
+ }
+ break;
case SkPath::kClose_Verb:
case SkPath::kDone_Verb:
+ closeSeen = true;
break;
default:
SkDEBUGFAIL("unknown verb");
}
+ verbSeen = true;
}
this->finishPathPolygon();
fCurrClipPoint = fClipPolygon.count() - 1;
+ return true;
}
void SkSpotShadowTessellator::computeClipVectorsAndTestCentroid() {