From 8664a1d7d719153e8e854ff0112519d92916cfe2 Mon Sep 17 00:00:00 2001 From: Jim Van Verth Date: Thu, 28 Jun 2018 16:26:50 -0400 Subject: [PATCH] Add ear-clipping code to triangulate simple polygons. Use this to fill concave shadows. Bug: skia:7971 Change-Id: I63dc1ed845f9fa3fcd86f1ad13b03da23cae0313 Reviewed-on: https://skia-review.googlesource.com/135200 Commit-Queue: Jim Van Verth Reviewed-by: Robert Phillips --- gm/convex_all_line_paths.cpp | 2 +- gm/polygonoffset.cpp | 2 +- gn/utils.gni | 4 +- .../{SkOffsetPolygon.cpp => SkPolyUtils.cpp} | 283 +++++++++++++++--- .../{SkOffsetPolygon.h => SkPolyUtils.h} | 40 +++ src/utils/SkShadowTessellator.cpp | 35 +-- src/utils/SkShadowUtils.cpp | 8 +- tests/InsetConvexPolyTest.cpp | 2 +- tests/OffsetSimplePolyTest.cpp | 9 +- 9 files changed, 303 insertions(+), 82 deletions(-) rename src/utils/{SkOffsetPolygon.cpp => SkPolyUtils.cpp} (76%) rename src/utils/{SkOffsetPolygon.h => SkPolyUtils.h} (71%) diff --git a/gm/convex_all_line_paths.cpp b/gm/convex_all_line_paths.cpp index b1907bccb7..e21711b2bc 100644 --- a/gm/convex_all_line_paths.cpp +++ b/gm/convex_all_line_paths.cpp @@ -6,7 +6,7 @@ */ #include "gm.h" -#include "SkOffsetPolygon.h" +#include "SkPolyUtils.h" #include "SkPathPriv.h" static void create_ngon(int n, SkPoint* pts, SkScalar width, SkScalar height) { diff --git a/gm/polygonoffset.cpp b/gm/polygonoffset.cpp index 48b6d1a4e4..44a313b285 100644 --- a/gm/polygonoffset.cpp +++ b/gm/polygonoffset.cpp @@ -7,7 +7,7 @@ #include "gm.h" #include "sk_tool_utils.h" -#include "SkOffsetPolygon.h" +#include "SkPolyUtils.h" #include "SkPathPriv.h" static void create_ngon(int n, SkPoint* pts, SkScalar w, SkScalar h, SkPath::Direction dir) { diff --git a/gn/utils.gni b/gn/utils.gni index 63b063bf46..95c4e937c7 100644 --- a/gn/utils.gni +++ b/gn/utils.gni @@ -47,8 +47,6 @@ skia_utils_sources = [ "$_src/utils/SkMultiPictureDocument.cpp", "$_src/utils/SkNWayCanvas.cpp", "$_src/utils/SkNullCanvas.cpp", - "$_src/utils/SkOffsetPolygon.cpp", - "$_src/utils/SkOffsetPolygon.h", "$_src/utils/SkOSPath.cpp", "$_src/utils/SkOSPath.h", "$_src/utils/SkPaintFilterCanvas.cpp", @@ -57,6 +55,8 @@ skia_utils_sources = [ "$_src/utils/SkParsePath.cpp", "$_src/utils/SkPatchUtils.cpp", "$_src/utils/SkPatchUtils.h", + "$_src/utils/SkPolyUtils.cpp", + "$_src/utils/SkPolyUtils.h", "$_src/utils/SkShadowTessellator.cpp", "$_src/utils/SkShadowTessellator.h", "$_src/utils/SkShadowUtils.cpp", diff --git a/src/utils/SkOffsetPolygon.cpp b/src/utils/SkPolyUtils.cpp similarity index 76% rename from src/utils/SkOffsetPolygon.cpp rename to src/utils/SkPolyUtils.cpp index c72f7d407b..ea9d649a5c 100755 --- a/src/utils/SkOffsetPolygon.cpp +++ b/src/utils/SkPolyUtils.cpp @@ -5,12 +5,16 @@ * found in the LICENSE file. */ -#include "SkOffsetPolygon.h" +#include "SkPolyUtils.h" #include "SkPointPriv.h" #include "SkTArray.h" #include "SkTemplates.h" #include "SkTDPQueue.h" +#include "SkTInternalLList.h" + +////////////////////////////////////////////////////////////////////////////////// +// Helper data structures and functions struct OffsetSegment { SkPoint fP0; @@ -33,23 +37,17 @@ static int compute_side(const SkPoint& s0, const SkPoint& s1, const SkPoint& p) // returns 1 for ccw, -1 for cw and 0 if degenerate static int get_winding(const SkPoint* polygonVerts, int polygonSize) { - SkPoint p0 = polygonVerts[0]; - SkPoint p1 = polygonVerts[1]; - - for (int i = 2; i < polygonSize; ++i) { - SkPoint p2 = polygonVerts[i]; - - // determine if cw or ccw - int side = compute_side(p0, p1, p2); - if (0 != side) { - return ((side > 0) ? 1 : -1); - } - - // if nearly collinear, treat as straight line and continue - p1 = p2; + // compute area and use sign to determine winding + SkScalar quadArea = 0; + for (int curr = 0; curr < polygonSize; ++curr) { + int next = (curr + 1) % polygonSize; + quadArea += polygonVerts[curr].cross(polygonVerts[next]); } - - return 0; + if (SkScalarNearlyZero(quadArea)) { + return 0; + } + // 1 == ccw, -1 == cw + return (quadArea > 0) ? 1 : -1; } // Helper function to compute the individual vector for non-equal offsets @@ -261,6 +259,8 @@ struct EdgeData { } }; +////////////////////////////////////////////////////////////////////////////////// + // The objective here is to inset all of the edges by the given distance, and then // remove any invalid inset edges by detecting right-hand turns. In a ccw polygon, // we should only be making left-hand turns (for cw polygons, we use the winding @@ -280,6 +280,7 @@ bool SkInsetConvexPolygon(const SkPoint* inputPolygonVerts, int inputPolygonSize return false; } + // get winding direction int winding = get_winding(inputPolygonVerts, inputPolygonSize); if (0 == winding) { return false; @@ -397,9 +398,11 @@ bool SkInsetConvexPolygon(const SkPoint* inputPolygonVerts, int inputPolygonSize return (insetPolygon->count() >= 3 && is_convex(*insetPolygon)); } -// compute the number of points needed for a circular join when offsetting a reflex vertex -static void compute_radial_steps(const SkVector& v1, const SkVector& v2, SkScalar r, - SkScalar* rotSin, SkScalar* rotCos, int* n) { +/////////////////////////////////////////////////////////////////////////////////////////// + +// compute the number of points needed for a circular join when offsetting a reflex vertex +void SkComputeRadialSteps(const SkVector& v1, const SkVector& v2, SkScalar r, + SkScalar* rotSin, SkScalar* rotCos, int* n) { const SkScalar kRecipPixelsPerArcSegment = 0.25f; SkScalar rCos = v1.dot(v2); @@ -529,10 +532,10 @@ public: // if we intersect with the edge above or below us // then we know this polygon is not simple, so don't remove, just fail - if (removeIndex > 0 && fEdges[removeIndex].intersect(fEdges[removeIndex-1])) { + if (removeIndex > 0 && fEdges[removeIndex].intersect(fEdges[removeIndex - 1])) { return false; } - if (removeIndex < fEdges.count()-1) { + if (removeIndex < fEdges.count() - 1) { if (fEdges[removeIndex].intersect(fEdges[removeIndex + 1])) { return false; } @@ -555,7 +558,7 @@ private: // Then as we pop the vertices from the queue we generate events which indicate that an edge // should be added or removed from an edge list. If any intersections are detected in the edge // list, then we know the polygon is self-intersecting and hence not simple. -static bool is_simple_polygon(const SkPoint* polygon, int polygonSize) { +bool SkIsSimplePolygon(const SkPoint* polygon, int polygonSize) { SkTDPQueue vertexQueue; EdgeList sweepLine; @@ -613,7 +616,8 @@ static bool is_simple_polygon(const SkPoint* polygon, int polygonSize) { return (vertexQueue.count() == 0); } -// TODO: assuming a constant offset here -- do we want to support variable offset? +/////////////////////////////////////////////////////////////////////////////////////////// + bool SkOffsetSimplePolygon(const SkPoint* inputPolygonVerts, int inputPolygonSize, std::function offsetDistanceFunc, SkTDArray* offsetPolygon, SkTDArray* polygonIndices) { @@ -621,22 +625,12 @@ bool SkOffsetSimplePolygon(const SkPoint* inputPolygonVerts, int inputPolygonSiz return false; } - if (!is_simple_polygon(inputPolygonVerts, inputPolygonSize)) { + // get winding direction + int winding = get_winding(inputPolygonVerts, inputPolygonSize); + if (0 == winding) { return false; } - // compute area and use sign to determine winding - SkScalar quadArea = 0; - for (int curr = 0; curr < inputPolygonSize; ++curr) { - int next = (curr + 1) % inputPolygonSize; - quadArea += inputPolygonVerts[curr].cross(inputPolygonVerts[next]); - } - if (SkScalarNearlyZero(quadArea)) { - return false; - } - // 1 == ccw, -1 == cw - int winding = (quadArea > 0) ? 1 : -1; - // build normals SkAutoSTMalloc<64, SkVector> normal0(inputPolygonSize); SkAutoSTMalloc<64, SkVector> normal1(inputPolygonSize); @@ -667,7 +661,7 @@ bool SkOffsetSimplePolygon(const SkPoint* inputPolygonVerts, int inputPolygonSiz SkScalar rotSin, rotCos; int numSteps; SkVector prevNormal = normal1[currIndex]; - compute_radial_steps(prevNormal, normal0[currIndex], SkScalarAbs(offset), + SkComputeRadialSteps(prevNormal, normal0[currIndex], SkScalarAbs(offset), &rotSin, &rotCos, &numSteps); for (int i = 0; i < numSteps - 1; ++i) { SkVector currNormal = SkVector::Make(prevNormal.fX*rotCos - prevNormal.fY*rotSin, @@ -792,14 +786,211 @@ bool SkOffsetSimplePolygon(const SkPoint* inputPolygonVerts, int inputPolygonSiz } } - // compute signed area to check winding (it should be same as the original polygon) - quadArea = 0; - for (int curr = 0; curr < offsetPolygon->count(); ++curr) { - int next = (curr + 1) % offsetPolygon->count(); - quadArea += (*offsetPolygon)[curr].cross((*offsetPolygon)[next]); - } + // check winding of offset polygon (it should be same as the original polygon) + SkScalar offsetWinding = get_winding(offsetPolygon->begin(), offsetPolygon->count()); - return (winding*quadArea > 0 && - is_simple_polygon(offsetPolygon->begin(), offsetPolygon->count())); + return (winding*offsetWinding > 0 && + SkIsSimplePolygon(offsetPolygon->begin(), offsetPolygon->count())); } +////////////////////////////////////////////////////////////////////////////////////////// + +struct TriangulationVertex { + SK_DECLARE_INTERNAL_LLIST_INTERFACE(TriangulationVertex); + + enum class VertexType { kConvex, kReflex }; + + SkPoint fPosition; + VertexType fVertexType; + uint16_t fIndex; + uint16_t fPrevIndex; + uint16_t fNextIndex; +}; + +// test to see if point p is in triangle p0p1p2. +// for now assuming strictly inside -- if on the edge it's outside +static bool point_in_triangle(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2, + const SkPoint& p) { + SkVector v0 = p1 - p0; + SkVector v1 = p2 - p1; + SkScalar n = v0.cross(v1); + + SkVector w0 = p - p0; + if (n*v0.cross(w0) < SK_ScalarNearlyZero) { + return false; + } + + SkVector w1 = p - p1; + if (n*v1.cross(w1) < SK_ScalarNearlyZero) { + return false; + } + + SkVector v2 = p0 - p2; + SkVector w2 = p - p2; + if (n*v2.cross(w2) < SK_ScalarNearlyZero) { + return false; + } + + return true; +} + +// Data structure to track reflex vertices and check whether any are inside a given triangle +class ReflexHash { +public: + void add(TriangulationVertex* v) { + fReflexList.addToTail(v); + } + + void remove(TriangulationVertex* v) { + fReflexList.remove(v); + } + + bool checkTriangle(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) { + for (SkTInternalLList::Iter reflexIter = fReflexList.begin(); + reflexIter != fReflexList.end(); ++reflexIter) { + TriangulationVertex* reflexVertex = *reflexIter; + if (point_in_triangle(p0, p1, p2, reflexVertex->fPosition)) { + return true; + } + } + + return false; + } + +private: + // TODO: switch to an actual spatial hash + SkTInternalLList fReflexList; +}; + +// Check to see if a reflex vertex has become a convex vertex after clipping an ear +static void reclassify_vertex(TriangulationVertex* p, const SkPoint* polygonVerts, + int winding, ReflexHash* reflexHash, + SkTInternalLList* convexList) { + if (TriangulationVertex::VertexType::kReflex == p->fVertexType) { + SkVector v0 = p->fPosition - polygonVerts[p->fPrevIndex]; + SkVector v1 = polygonVerts[p->fNextIndex] - p->fPosition; + if (winding*v0.cross(v1) > SK_ScalarNearlyZero) { + p->fVertexType = TriangulationVertex::VertexType::kConvex; + reflexHash->remove(p); + p->fPrev = p->fNext = nullptr; + convexList->addToTail(p); + } + } +} + +bool SkTriangulateSimplePolygon(const SkPoint* polygonVerts, uint16_t* indexMap, int polygonSize, + SkTDArray* triangleIndices) { + if (polygonSize < 3) { + return false; + } + // need to be able to represent all the vertices in the 16-bit indices + if (polygonSize >= (1 << 16)) { + return false; + } + + // get winding direction + // TODO: we do this for all the polygon routines -- might be better to have the client + // compute it and pass it in + int winding = get_winding(polygonVerts, polygonSize); + if (0 == winding) { + return false; + } + + // Classify initial vertices into a list of convex vertices and a hash of reflex vertices + // TODO: possibly sort the convexList in some way to get better triangles + SkTInternalLList convexList; + ReflexHash reflexHash; + SkAutoSTMalloc<64, TriangulationVertex> triangulationVertices(polygonSize); + int prevIndex = polygonSize - 1; + int currIndex = 0; + int nextIndex = 1; + SkVector v0 = polygonVerts[currIndex] - polygonVerts[prevIndex]; + SkVector v1 = polygonVerts[nextIndex] - polygonVerts[currIndex]; + for (int i = 0; i < polygonSize; ++i) { + SkDEBUGCODE(memset(&triangulationVertices[currIndex], 0, sizeof(TriangulationVertex))); + triangulationVertices[currIndex].fPosition = polygonVerts[currIndex]; + triangulationVertices[currIndex].fIndex = currIndex; + triangulationVertices[currIndex].fPrevIndex = prevIndex; + triangulationVertices[currIndex].fNextIndex = nextIndex; + if (winding*v0.cross(v1) > SK_ScalarNearlyZero) { + triangulationVertices[currIndex].fVertexType = TriangulationVertex::VertexType::kConvex; + convexList.addToTail(&triangulationVertices[currIndex]); + } else { + // We treat near collinear vertices as reflex + triangulationVertices[currIndex].fVertexType = TriangulationVertex::VertexType::kReflex; + reflexHash.add(&triangulationVertices[currIndex]); + } + + prevIndex = currIndex; + currIndex = nextIndex; + nextIndex = (currIndex + 1) % polygonSize; + v0 = v1; + v1 = polygonVerts[nextIndex] - polygonVerts[currIndex]; + } + + // The general concept: We are trying to find three neighboring vertices where + // no other vertex lies inside the triangle (an "ear"). If we find one, we clip + // that ear off, and then repeat on the new polygon. Once we get down to three vertices + // we have triangulated the entire polygon. + // In the worst case this is an n^2 algorithm. We can cut down the search space somewhat by + // noting that only convex vertices can be potential ears, and we only need to check whether + // any reflex vertices lie inside the ear. + triangleIndices->setReserve(triangleIndices->count() + 3 * (polygonSize - 2)); + int vertexCount = polygonSize; + while (vertexCount > 3) { + bool success = false; + TriangulationVertex* earVertex = nullptr; + TriangulationVertex* p0 = nullptr; + TriangulationVertex* p2 = nullptr; + // find a convex vertex to clip + for (SkTInternalLList::Iter convexIter = convexList.begin(); + convexIter != convexList.end(); ++convexIter) { + earVertex = *convexIter; + SkASSERT(TriangulationVertex::VertexType::kReflex != earVertex->fVertexType); + + p0 = &triangulationVertices[earVertex->fPrevIndex]; + p2 = &triangulationVertices[earVertex->fNextIndex]; + + // see if any reflex vertices are inside the ear + bool failed = reflexHash.checkTriangle(p0->fPosition, earVertex->fPosition, + p2->fPosition); + if (failed) { + continue; + } + + // found one we can clip + success = true; + break; + } + // If we can't find any ears to clip, this probably isn't a simple polygon + if (!success) { + return false; + } + + // add indices + auto indices = triangleIndices->append(3); + indices[0] = indexMap[p0->fIndex]; + indices[1] = indexMap[earVertex->fIndex]; + indices[2] = indexMap[p2->fIndex]; + + // clip the ear + convexList.remove(earVertex); + --vertexCount; + + // reclassify reflex verts + p0->fNextIndex = earVertex->fNextIndex; + reclassify_vertex(p0, polygonVerts, winding, &reflexHash, &convexList); + + p2->fPrevIndex = earVertex->fPrevIndex; + reclassify_vertex(p2, polygonVerts, winding, &reflexHash, &convexList); + } + + // output indices + for (SkTInternalLList::Iter vertexIter = convexList.begin(); + vertexIter != convexList.end(); ++vertexIter) { + TriangulationVertex* vertex = *vertexIter; + *triangleIndices->push() = indexMap[vertex->fIndex]; + } + + return true; +} diff --git a/src/utils/SkOffsetPolygon.h b/src/utils/SkPolyUtils.h similarity index 71% rename from src/utils/SkOffsetPolygon.h rename to src/utils/SkPolyUtils.h index 4c98ac0706..ab5ebb8d40 100755 --- a/src/utils/SkOffsetPolygon.h +++ b/src/utils/SkPolyUtils.h @@ -49,6 +49,7 @@ inline bool SkInsetConvexPolygon(const SkPoint* inputPolygonVerts, int inputPoly /** * Generates a simple polygon (if possible) that is offset a variable distance (controlled by * offsetDistanceFunc) from the boundary of a given simple polygon. + * The input polygon must be simple and have no coincident vertices or collinear edges. * * @param inputPolygonVerts Array of points representing the vertices of the original polygon. * @param inputPolygonSize Number of vertices in the original polygon. @@ -66,6 +67,7 @@ bool SkOffsetSimplePolygon(const SkPoint* inputPolygonVerts, int inputPolygonSiz /** * Generates a simple polygon (if possible) that is offset a constant distance from the boundary * of a given simple polygon. + * The input polygon must be simple and have no coincident vertices or collinear edges. * * @param inputPolygonVerts Array of points representing the vertices of the original polygon. * @param inputPolygonSize Number of vertices in the original polygon. @@ -100,4 +102,42 @@ inline bool SkOffsetSimplePolygon(const SkPoint* inputPolygonVerts, int inputPol bool SkOffsetSegment(const SkPoint& p0, const SkPoint& p1, SkScalar d0, SkScalar d1, int side, SkPoint* offset0, SkPoint* offset1); +/** + * Compute the number of points needed for a circular join when offsetting a vertex. + * The lengths of offset0 and offset1 don't have to equal r -- only the direction matters. + * The segment lengths will be approximately four pixels. + * + * @param offset0 Starting offset vector direction. + * @param offset1 Ending offset vector direction. + * @param r Length of offset. + * @param rotSin Sine of rotation delta per step. + * @param rotCos Cosine of rotation delta per step. + * @param n Number of steps to fill out the arc. + */ +void SkComputeRadialSteps(const SkVector& offset0, const SkVector& offset1, SkScalar r, + SkScalar* rotSin, SkScalar* rotCos, int* n); + +/** + * Determine whether a polygon is simple (i.e., not self-intersecting) or not. + * + * @param polygonVerts Array of points representing the vertices of the polygon. + * @param polygonSize Number of vertices in the polygon. + * @return true if the polygon is simple, false otherwise. + */ + bool SkIsSimplePolygon(const SkPoint* polygonVerts, int polygonSize); + + /** + * Compute indices to triangulate the given polygon. + * The input polygon must be simple (i.e. it is not self-intersecting) + * and have no coincident vertices or collinear edges. + * + * @param polygonVerts Array of points representing the vertices of the polygon. + * @param indexMap Mapping from index in the given array to the final index in the triangulation. + * @param polygonSize Number of vertices in the polygon. + * @param triangleIndices Indices of the resulting triangulation. + * @return true if successful, false otherwise. + */ + bool SkTriangulateSimplePolygon(const SkPoint* polygonVerts, uint16_t* indexMap, int polygonSize, + SkTDArray* triangleIndices); + #endif diff --git a/src/utils/SkShadowTessellator.cpp b/src/utils/SkShadowTessellator.cpp index b454e5f103..3d84e8fdad 100755 --- a/src/utils/SkShadowTessellator.cpp +++ b/src/utils/SkShadowTessellator.cpp @@ -9,7 +9,7 @@ #include "SkColorData.h" #include "SkDrawShadowInfo.h" #include "SkGeometry.h" -#include "SkOffsetPolygon.h" +#include "SkPolyUtils.h" #include "SkPath.h" #include "SkPoint3.h" #include "SkPointPriv.h" @@ -127,21 +127,6 @@ static bool compute_normal(const SkPoint& p0, const SkPoint& p1, SkScalar dir, return true; } -static void compute_radial_steps(const SkVector& v1, const SkVector& v2, SkScalar r, - SkScalar* rotSin, SkScalar* rotCos, int* n) { - const SkScalar kRecipPixelsPerArcSegment = 0.125f; - - SkScalar rCos = v1.dot(v2); - SkScalar rSin = v1.cross(v2); - SkScalar theta = SkScalarATan2(rSin, rCos); - - int steps = SkScalarRoundToInt(SkScalarAbs(r*theta*kRecipPixelsPerArcSegment)); - - SkScalar dTheta = theta / steps; - *rotSin = SkScalarSinCos(dTheta, rotCos); - *n = steps; -} - static bool duplicate_pt(const SkPoint& p0, const SkPoint& p1) { static constexpr SkScalar kClose = (SK_Scalar1 / 16); static constexpr SkScalar kCloseSqd = kClose * kClose; @@ -269,6 +254,9 @@ void SkBaseShadowTessellator::stitchConcaveRings(const SkTDArray& umbra SkTDArray* umbraIndices, const SkTDArray& penumbraPolygon, SkTDArray* penumbraIndices) { + // TODO: only create and fill indexMap when fTransparent is true? + SkAutoSTMalloc<64, uint16_t> indexMap(umbraPolygon.count()); + // find minimum indices int minIndex = 0; int min = (*penumbraIndices)[0]; @@ -311,6 +299,7 @@ void SkBaseShadowTessellator::stitchConcaveRings(const SkTDArray& umbra *fPositions.push() = umbraPolygon[currUmbra]; *fColors.push() = fUmbraColor; fPrevUmbraIndex = 1; + indexMap[currUmbra] = 1; int nextPenumbra = (currPenumbra + 1) % penumbraPolygon.count(); int nextUmbra = (currUmbra + 1) % umbraPolygon.count(); @@ -326,6 +315,7 @@ void SkBaseShadowTessellator::stitchConcaveRings(const SkTDArray& umbra *fPositions.push() = umbraPolygon[nextUmbra]; *fColors.push() = fUmbraColor; int currUmbraIndex = fPositions.count() - 1; + indexMap[nextUmbra] = currUmbraIndex; this->appendQuad(prevPenumbraIndex, currPenumbraIndex, fPrevUmbraIndex, currUmbraIndex); @@ -363,6 +353,7 @@ void SkBaseShadowTessellator::stitchConcaveRings(const SkTDArray& umbra *fPositions.push() = umbraPolygon[nextUmbra]; *fColors.push() = fUmbraColor; int currUmbraIndex = fPositions.count() - 1; + indexMap[nextUmbra] = currUmbraIndex; this->appendTriangle(fPrevUmbraIndex, prevPenumbraIndex, currUmbraIndex); @@ -381,12 +372,14 @@ void SkBaseShadowTessellator::stitchConcaveRings(const SkTDArray& umbra *fPositions.push() = umbraPolygon[nextUmbra]; *fColors.push() = fUmbraColor; int currUmbraIndex = fPositions.count() - 1; + indexMap[nextUmbra] = currUmbraIndex; this->appendQuad(prevPenumbraIndex, currPenumbraIndex, fPrevUmbraIndex, currUmbraIndex); if (fTransparent) { - // TODO: fill penumbra + SkTriangulateSimplePolygon(umbraPolygon.begin(), indexMap, umbraPolygon.count(), + &fIndices); } } @@ -508,7 +501,7 @@ bool SkBaseShadowTessellator::addArc(const SkVector& nextNormal, bool finishArc) // fill in fan from previous quad SkScalar rotSin, rotCos; int numSteps; - compute_radial_steps(fPrevOutset, nextNormal, fRadius, &rotSin, &rotCos, &numSteps); + SkComputeRadialSteps(fPrevOutset, nextNormal, fRadius, &rotSin, &rotCos, &numSteps); SkVector prevNormal = fPrevOutset; for (int i = 0; i < numSteps-1; ++i) { SkVector currNormal; @@ -801,8 +794,7 @@ bool SkAmbientShadowTessellator::computeConvexShadow() { } bool SkAmbientShadowTessellator::computeConcaveShadow() { - // TODO: remove when we support filling the penumbra - if (fTransparent) { + if (!SkIsSimplePolygon(&fPathPolygon[0], fPathPolygon.count())) { return false; } @@ -1418,8 +1410,7 @@ bool SkSpotShadowTessellator::computeConvexShadow(SkScalar radius) { } bool SkSpotShadowTessellator::computeConcaveShadow(SkScalar radius) { - // TODO: remove when we support filling the penumbra - if (fTransparent) { + if (!SkIsSimplePolygon(&fPathPolygon[0], fPathPolygon.count())) { return false; } diff --git a/src/utils/SkShadowUtils.cpp b/src/utils/SkShadowUtils.cpp index b4fba5fcc1..6d5b7e16b4 100644 --- a/src/utils/SkShadowUtils.cpp +++ b/src/utils/SkShadowUtils.cpp @@ -542,9 +542,11 @@ static bool validate_rec(const SkDrawShadowRec& rec) { void SkBaseDevice::drawShadow(const SkPath& path, const SkDrawShadowRec& rec) { auto drawVertsProc = [this](const SkVertices* vertices, SkBlendMode mode, const SkPaint& paint, SkScalar tx, SkScalar ty) { - SkAutoDeviceCTMRestore adr(this, SkMatrix::Concat(this->ctm(), - SkMatrix::MakeTrans(tx, ty))); - this->drawVertices(vertices, mode, paint); + if (vertices->vertexCount()) { + SkAutoDeviceCTMRestore adr(this, SkMatrix::Concat(this->ctm(), + SkMatrix::MakeTrans(tx, ty))); + this->drawVertices(vertices, mode, paint); + } }; if (!validate_rec(rec)) { diff --git a/tests/InsetConvexPolyTest.cpp b/tests/InsetConvexPolyTest.cpp index 433cf239e6..022060c0f4 100644 --- a/tests/InsetConvexPolyTest.cpp +++ b/tests/InsetConvexPolyTest.cpp @@ -5,7 +5,7 @@ * found in the LICENSE file. */ #include "Test.h" -#include "SkOffsetPolygon.h" +#include "SkPolyUtils.h" static bool is_convex(const SkTDArray& poly) { if (poly.count() < 3) { diff --git a/tests/OffsetSimplePolyTest.cpp b/tests/OffsetSimplePolyTest.cpp index de720b5cf2..547dc9e529 100644 --- a/tests/OffsetSimplePolyTest.cpp +++ b/tests/OffsetSimplePolyTest.cpp @@ -5,7 +5,7 @@ * found in the LICENSE file. */ #include "Test.h" -#include "SkOffsetPolygon.h" +#include "SkPolyUtils.h" static bool is_convex(const SkTDArray& poly) { if (poly.count() < 3) { @@ -200,10 +200,7 @@ DEF_TEST(OffsetSimplePoly, reporter) { *intersectingPoly.push() = SkPoint::Make(-43.30f, -25.0f); *intersectingPoly.push() = SkPoint::Make(-14.43f, -25.0f); - result = SkOffsetSimplePolygon(&intersectingPoly[0], intersectingPoly.count(), -100, - &offsetPoly); + // SkOffsetSimplePolygon now assumes that the input is simple, so we'll just check for that + result = SkIsSimplePolygon(&intersectingPoly[0], intersectingPoly.count()); REPORTER_ASSERT(reporter, !result); - - - }