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 <jvanverth@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
This commit is contained in:
Jim Van Verth 2018-06-28 16:26:50 -04:00 committed by Skia Commit-Bot
parent a5e703043f
commit 8664a1d7d7
9 changed files with 303 additions and 82 deletions

View File

@ -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) {

View File

@ -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) {

View File

@ -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",

View File

@ -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 <Vertex, Vertex::Left> 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<SkScalar(const SkPoint&)> offsetDistanceFunc,
SkTDArray<SkPoint>* offsetPolygon, SkTDArray<int>* 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<TriangulationVertex>::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<TriangulationVertex> 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<TriangulationVertex>* 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<uint16_t>* 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<TriangulationVertex> 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<TriangulationVertex>::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<TriangulationVertex>::Iter vertexIter = convexList.begin();
vertexIter != convexList.end(); ++vertexIter) {
TriangulationVertex* vertex = *vertexIter;
*triangleIndices->push() = indexMap[vertex->fIndex];
}
return true;
}

View File

@ -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<uint16_t>* triangleIndices);
#endif

View File

@ -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<SkPoint>& umbra
SkTDArray<int>* umbraIndices,
const SkTDArray<SkPoint>& penumbraPolygon,
SkTDArray<int>* 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<SkPoint>& 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<SkPoint>& 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<SkPoint>& 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<SkPoint>& 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;
}

View File

@ -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)) {

View File

@ -5,7 +5,7 @@
* found in the LICENSE file.
*/
#include "Test.h"
#include "SkOffsetPolygon.h"
#include "SkPolyUtils.h"
static bool is_convex(const SkTDArray<SkPoint>& poly) {
if (poly.count() < 3) {

View File

@ -5,7 +5,7 @@
* found in the LICENSE file.
*/
#include "Test.h"
#include "SkOffsetPolygon.h"
#include "SkPolyUtils.h"
static bool is_convex(const SkTDArray<SkPoint>& 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);
}