int GrPathUtils::worstCasePointCount(const SkPath& path, int* subpaths, SkScalar tol) { if (tol < gMinCurveTol) { tol = gMinCurveTol; } SkASSERT(tol > 0); int pointCount = 0; *subpaths = 1; bool first = true; SkPath::Iter iter(path, false); SkPath::Verb verb; SkPoint pts[4]; while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { switch (verb) { case SkPath::kLine_Verb: pointCount += 1; break; case SkPath::kConic_Verb: { SkScalar weight = iter.conicWeight(); SkAutoConicToQuads converter; const SkPoint* quadPts = converter.computeQuads(pts, weight, 0.25f); for (int i = 0; i < converter.countQuads(); ++i) { pointCount += quadraticPointCount(quadPts + 2*i, tol); } } case SkPath::kQuad_Verb: pointCount += quadraticPointCount(pts, tol); break; case SkPath::kCubic_Verb: pointCount += cubicPointCount(pts, tol); break; case SkPath::kMove_Verb: pointCount += 1; if (!first) { ++(*subpaths); } break; default: break; } first = false; } return pointCount; }
void SkParsePath::ToSVGString(const SkPath& path, SkString* str) { SkDynamicMemoryWStream stream; SkPath::Iter iter(path, false); SkPoint pts[4]; for (;;) { switch (iter.next(pts, false)) { case SkPath::kConic_Verb: { const SkScalar tol = SK_Scalar1 / 1024; // how close to a quad SkAutoConicToQuads quadder; const SkPoint* quadPts = quadder.computeQuads(pts, iter.conicWeight(), tol); for (int i = 0; i < quadder.countQuads(); ++i) { append_scalars(&stream, 'Q', &quadPts[i*2 + 1].fX, 4); } } break; case SkPath::kMove_Verb: append_scalars(&stream, 'M', &pts[0].fX, 2); break; case SkPath::kLine_Verb: append_scalars(&stream, 'L', &pts[1].fX, 2); break; case SkPath::kQuad_Verb: append_scalars(&stream, 'Q', &pts[1].fX, 4); break; case SkPath::kCubic_Verb: append_scalars(&stream, 'C', &pts[1].fX, 6); break; case SkPath::kClose_Verb: stream.write("Z", 1); break; case SkPath::kDone_Verb: str->resize(stream.getOffset()); stream.copyTo(str->writable_str()); return; } } }
int SkOpEdgeBuilder::preFetch() { if (!fPath->isFinite()) { fUnparseable = true; return 0; } SkAutoConicToQuads quadder; const SkScalar quadderTol = SK_Scalar1 / 16; SkPath::RawIter iter(*fPath); SkPoint curveStart; SkPoint curve[4]; SkPoint pts[4]; SkPath::Verb verb; bool lastCurve = false; do { verb = iter.next(pts); switch (verb) { case SkPath::kMove_Verb: if (!fAllowOpenContours && lastCurve) { closeContour(curve[0], curveStart); } fPathVerbs.push_back(verb); force_small_to_zero(&pts[0]); fPathPts.push_back(pts[0]); curveStart = curve[0] = pts[0]; lastCurve = false; continue; case SkPath::kLine_Verb: force_small_to_zero(&pts[1]); if (SkDPoint::ApproximatelyEqual(curve[0], pts[1])) { uint8_t lastVerb = fPathVerbs.back(); if (lastVerb != SkPath::kLine_Verb && lastVerb != SkPath::kMove_Verb) { fPathPts.back() = pts[1]; } continue; // skip degenerate points } break; case SkPath::kQuad_Verb: force_small_to_zero(&pts[1]); force_small_to_zero(&pts[2]); curve[1] = pts[1]; curve[2] = pts[2]; verb = SkReduceOrder::Quad(curve, pts); if (verb == SkPath::kMove_Verb) { continue; // skip degenerate points } break; case SkPath::kConic_Verb: { const SkPoint* quadPts = quadder.computeQuads(pts, iter.conicWeight(), quadderTol); const int nQuads = quadder.countQuads(); for (int i = 0; i < nQuads; ++i) { fPathVerbs.push_back(SkPath::kQuad_Verb); } fPathPts.push_back_n(nQuads * 2, &quadPts[1]); curve[0] = pts[2]; lastCurve = true; } continue; case SkPath::kCubic_Verb: force_small_to_zero(&pts[1]); force_small_to_zero(&pts[2]); force_small_to_zero(&pts[3]); curve[1] = pts[1]; curve[2] = pts[2]; curve[3] = pts[3]; verb = SkReduceOrder::Cubic(curve, pts); if (verb == SkPath::kMove_Verb) { continue; // skip degenerate points } break; case SkPath::kClose_Verb: closeContour(curve[0], curveStart); lastCurve = false; continue; case SkPath::kDone_Verb: continue; } fPathVerbs.push_back(verb); int ptCount = SkPathOpsVerbToPoints(verb); fPathPts.push_back_n(ptCount, &pts[1]); curve[0] = pts[ptCount]; lastCurve = true; } while (verb != SkPath::kDone_Verb); if (!fAllowOpenContours && lastCurve) { closeContour(curve[0], curveStart); } fPathVerbs.push_back(SkPath::kDone_Verb); return fPathVerbs.count() - 1; }
void SkStroke::strokePath(const SkPath& src, SkPath* dst) const { SkASSERT(&src != NULL && dst != NULL); SkScalar radius = SkScalarHalf(fWidth); AutoTmpPath tmp(src, &dst); if (radius <= 0) { return; } // If src is really a rect, call our specialty strokeRect() method { bool isClosed; SkPath::Direction dir; if (src.isRect(&isClosed, &dir) && isClosed) { this->strokeRect(src.getBounds(), dst, dir); // our answer should preserve the inverseness of the src if (src.isInverseFillType()) { SkASSERT(!dst->isInverseFillType()); dst->toggleInverseFillType(); } return; } } SkAutoConicToQuads converter; const SkScalar conicTol = SK_Scalar1 / 4; SkPathStroker stroker(src, radius, fMiterLimit, this->getCap(), this->getJoin()); SkPath::Iter iter(src, false); SkPath::Verb lastSegment = SkPath::kMove_Verb; for (;;) { SkPoint pts[4]; switch (iter.next(pts, false)) { case SkPath::kMove_Verb: stroker.moveTo(pts[0]); break; case SkPath::kLine_Verb: stroker.lineTo(pts[1]); lastSegment = SkPath::kLine_Verb; break; case SkPath::kQuad_Verb: stroker.quadTo(pts[1], pts[2]); lastSegment = SkPath::kQuad_Verb; break; case SkPath::kConic_Verb: { // todo: if we had maxcurvature for conics, perhaps we should // natively extrude the conic instead of converting to quads. const SkPoint* quadPts = converter.computeQuads(pts, iter.conicWeight(), conicTol); for (int i = 0; i < converter.countQuads(); ++i) { stroker.quadTo(quadPts[1], quadPts[2]); quadPts += 2; } lastSegment = SkPath::kQuad_Verb; } break; case SkPath::kCubic_Verb: stroker.cubicTo(pts[1], pts[2], pts[3]); lastSegment = SkPath::kCubic_Verb; break; case SkPath::kClose_Verb: stroker.close(lastSegment == SkPath::kLine_Verb); break; case SkPath::kDone_Verb: goto DONE; } } DONE: stroker.done(dst, lastSegment == SkPath::kLine_Verb); if (fDoFill) { if (src.cheapIsDirection(SkPath::kCCW_Direction)) { dst->reverseAddPath(src); } else { dst->addPath(src); } } else { // Seems like we can assume that a 2-point src would always result in // a convex stroke, but testing has proved otherwise. // TODO: fix the stroker to make this assumption true (without making // it slower that the work that will be done in computeConvexity()) #if 0 // this test results in a non-convex stroke :( static void test(SkCanvas* canvas) { SkPoint pts[] = { 146.333328, 192.333328, 300.333344, 293.333344 }; SkPaint paint; paint.setStrokeWidth(7); paint.setStrokeCap(SkPaint::kRound_Cap); canvas->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint); } #endif #if 0 if (2 == src.countPoints()) { dst->setIsConvex(true); } #endif }
bool createGeom(void* vertices, size_t vertexOffset, void* indices, size_t indexOffset, int* vertexCnt, int* indexCnt, const SkPath& path, SkScalar srcSpaceTol, bool isIndexed) const { { SkScalar srcSpaceTolSqd = SkScalarMul(srcSpaceTol, srcSpaceTol); uint16_t indexOffsetU16 = (uint16_t)indexOffset; uint16_t vertexOffsetU16 = (uint16_t)vertexOffset; uint16_t* idxBase = reinterpret_cast<uint16_t*>(indices) + indexOffsetU16; uint16_t* idx = idxBase; uint16_t subpathIdxStart = vertexOffsetU16; SkPoint* base = reinterpret_cast<SkPoint*>(vertices) + vertexOffset; SkPoint* vert = base; SkPoint pts[4]; bool first = true; int subpath = 0; SkPath::Iter iter(path, false); bool done = false; while (!done) { SkPath::Verb verb = iter.next(pts); switch (verb) { case SkPath::kMove_Verb: if (!first) { uint16_t currIdx = (uint16_t) (vert - base) + vertexOffsetU16; subpathIdxStart = currIdx; ++subpath; } *vert = pts[0]; vert++; break; case SkPath::kLine_Verb: if (isIndexed) { uint16_t prevIdx = (uint16_t)(vert - base) - 1 + vertexOffsetU16; append_countour_edge_indices(this->isHairline(), subpathIdxStart, prevIdx, &idx); } *(vert++) = pts[1]; break; case SkPath::kConic_Verb: { SkScalar weight = iter.conicWeight(); SkAutoConicToQuads converter; // Converting in src-space, hance the finer tolerance (0.25) // TODO: find a way to do this in dev-space so the tolerance means something const SkPoint* quadPts = converter.computeQuads(pts, weight, 0.25f); for (int i = 0; i < converter.countQuads(); ++i) { add_quad(&vert, base, quadPts + i*2, srcSpaceTolSqd, srcSpaceTol, isIndexed, this->isHairline(), subpathIdxStart, (int)vertexOffset, &idx); } break; } case SkPath::kQuad_Verb: add_quad(&vert, base, pts, srcSpaceTolSqd, srcSpaceTol, isIndexed, this->isHairline(), subpathIdxStart, (int)vertexOffset, &idx); break; case SkPath::kCubic_Verb: { // first pt of cubic is the pt we ended on in previous step uint16_t firstCPtIdx = (uint16_t)(vert - base) - 1 + vertexOffsetU16; uint16_t numPts = (uint16_t) GrPathUtils::generateCubicPoints( pts[0], pts[1], pts[2], pts[3], srcSpaceTolSqd, &vert, GrPathUtils::cubicPointCount(pts, srcSpaceTol)); if (isIndexed) { for (uint16_t i = 0; i < numPts; ++i) { append_countour_edge_indices(this->isHairline(), subpathIdxStart, firstCPtIdx + i, &idx); } } break; } case SkPath::kClose_Verb: break; case SkPath::kDone_Verb: done = true; } first = false; } *vertexCnt = static_cast<int>(vert - base); *indexCnt = static_cast<int>(idx - idxBase); } return true; }
static bool get_geometry(const SkPath& path, const SkMatrix& m, PLSVertices& triVertices, PLSVertices& quadVertices, GrResourceProvider* resourceProvider, SkRect bounds) { SkScalar screenSpaceTol = GrPathUtils::kDefaultTolerance; SkScalar tol = GrPathUtils::scaleToleranceToSrc(screenSpaceTol, m, bounds); int contourCnt; int maxPts = GrPathUtils::worstCasePointCount(path, &contourCnt, tol); if (maxPts <= 0) { return 0; } SkPath linesOnlyPath; linesOnlyPath.setFillType(path.getFillType()); SkSTArray<15, SkPoint, true> quadPoints; SkPath::Iter iter(path, true); bool done = false; while (!done) { SkPoint pts[4]; SkPath::Verb verb = iter.next(pts); switch (verb) { case SkPath::kMove_Verb: SkASSERT(quadPoints.count() % 3 == 0); for (int i = 0; i < quadPoints.count(); i += 3) { add_quad(&quadPoints[i], quadVertices); } quadPoints.reset(); m.mapPoints(&pts[0], 1); linesOnlyPath.moveTo(pts[0]); break; case SkPath::kLine_Verb: m.mapPoints(&pts[1], 1); linesOnlyPath.lineTo(pts[1]); break; case SkPath::kQuad_Verb: m.mapPoints(pts, 3); linesOnlyPath.lineTo(pts[2]); quadPoints.push_back(pts[0]); quadPoints.push_back(pts[1]); quadPoints.push_back(pts[2]); break; case SkPath::kCubic_Verb: { m.mapPoints(pts, 4); SkSTArray<15, SkPoint, true> quads; GrPathUtils::convertCubicToQuads(pts, kCubicTolerance, &quads); int count = quads.count(); for (int q = 0; q < count; q += 3) { linesOnlyPath.lineTo(quads[q + 2]); quadPoints.push_back(quads[q]); quadPoints.push_back(quads[q + 1]); quadPoints.push_back(quads[q + 2]); } break; } case SkPath::kConic_Verb: { m.mapPoints(pts, 3); SkScalar weight = iter.conicWeight(); SkAutoConicToQuads converter; const SkPoint* quads = converter.computeQuads(pts, weight, kConicTolerance); int count = converter.countQuads(); for (int i = 0; i < count; ++i) { linesOnlyPath.lineTo(quads[2 * i + 2]); quadPoints.push_back(quads[2 * i]); quadPoints.push_back(quads[2 * i + 1]); quadPoints.push_back(quads[2 * i + 2]); } break; } case SkPath::kClose_Verb: linesOnlyPath.close(); break; case SkPath::kDone_Verb: done = true; break; default: SkASSERT(false); } } SkASSERT(quadPoints.count() % 3 == 0); for (int i = 0; i < quadPoints.count(); i += 3) { add_quad(&quadPoints[i], quadVertices); } static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); GrUniqueKey key; GrUniqueKey::Builder builder(&key, kDomain, 2); builder[0] = path.getGenerationID(); builder[1] = path.getFillType(); builder.finish(); GrTessellator::WindingVertex* windingVertices; int triVertexCount = GrTessellator::PathToVertices(linesOnlyPath, 0, bounds, &windingVertices); if (triVertexCount > 0) { for (int i = 0; i < triVertexCount; i += 3) { SkPoint p1 = windingVertices[i].fPos; SkPoint p2 = windingVertices[i + 1].fPos; SkPoint p3 = windingVertices[i + 2].fPos; int winding = windingVertices[i].fWinding; SkASSERT(windingVertices[i + 1].fWinding == winding); SkASSERT(windingVertices[i + 2].fWinding == winding); SkScalar cross = (p2 - p1).cross(p3 - p1); SkPoint bloated[3] = { p1, p2, p3 }; if (cross < 0.0f) { SkTSwap(p1, p3); } if (bloat_tri(bloated)) { triVertices.push_back({ bloated[0], p1, p2, p3, winding }); triVertices.push_back({ bloated[1], p1, p2, p3, winding }); triVertices.push_back({ bloated[2], p1, p2, p3, winding }); } else { SkScalar minX = SkTMin(p1.fX, SkTMin(p2.fX, p3.fX)) - 1.0f; SkScalar minY = SkTMin(p1.fY, SkTMin(p2.fY, p3.fY)) - 1.0f; SkScalar maxX = SkTMax(p1.fX, SkTMax(p2.fX, p3.fX)) + 1.0f; SkScalar maxY = SkTMax(p1.fY, SkTMax(p2.fY, p3.fY)) + 1.0f; triVertices.push_back({ { minX, minY }, p1, p2, p3, winding }); triVertices.push_back({ { maxX, minY }, p1, p2, p3, winding }); triVertices.push_back({ { minX, maxY }, p1, p2, p3, winding }); triVertices.push_back({ { maxX, minY }, p1, p2, p3, winding }); triVertices.push_back({ { maxX, maxY }, p1, p2, p3, winding }); triVertices.push_back({ { minX, maxY }, p1, p2, p3, winding }); } } delete[] windingVertices; } return triVertexCount > 0 || quadVertices.count() > 0; }
/** * Generates the lines and quads to be rendered. Lines are always recorded in * device space. We will do a device space bloat to account for the 1pixel * thickness. * Quads are recorded in device space unless m contains * perspective, then in they are in src space. We do this because we will * subdivide large quads to reduce over-fill. This subdivision has to be * performed before applying the perspective matrix. */ static int gather_lines_and_quads(const SkPath& path, const SkMatrix& m, const SkIRect& devClipBounds, SkScalar capLength, bool convertConicsToQuads, GrAAHairLinePathRenderer::PtArray* lines, GrAAHairLinePathRenderer::PtArray* quads, GrAAHairLinePathRenderer::PtArray* conics, GrAAHairLinePathRenderer::IntArray* quadSubdivCnts, GrAAHairLinePathRenderer::FloatArray* conicWeights) { SkPath::Iter iter(path, false); int totalQuadCount = 0; SkRect bounds; SkIRect ibounds; bool persp = m.hasPerspective(); // Whenever a degenerate, zero-length contour is encountered, this code will insert a // 'capLength' x-aligned line segment. Since this is rendering hairlines it is hoped this will // suffice for AA square & circle capping. int verbsInContour = 0; // Does not count moves bool seenZeroLengthVerb = false; SkPoint zeroVerbPt; // Adds a quad that has already been chopped to the list and checks for quads that are close to // lines. Also does a bounding box check. It takes points that are in src space and device // space. The src points are only required if the view matrix has perspective. auto addChoppedQuad = [&](const SkPoint srcPts[3], const SkPoint devPts[4], bool isContourStart) { SkRect bounds; SkIRect ibounds; bounds.setBounds(devPts, 3); bounds.outset(SK_Scalar1, SK_Scalar1); bounds.roundOut(&ibounds); // We only need the src space space pts when not in perspective. SkASSERT(srcPts || !persp); if (SkIRect::Intersects(devClipBounds, ibounds)) { int subdiv = num_quad_subdivs(devPts); SkASSERT(subdiv >= -1); if (-1 == subdiv) { SkPoint* pts = lines->push_back_n(4); pts[0] = devPts[0]; pts[1] = devPts[1]; pts[2] = devPts[1]; pts[3] = devPts[2]; if (isContourStart && pts[0] == pts[1] && pts[2] == pts[3]) { seenZeroLengthVerb = true; zeroVerbPt = pts[0]; } } else { // when in perspective keep quads in src space const SkPoint* qPts = persp ? srcPts : devPts; SkPoint* pts = quads->push_back_n(3); pts[0] = qPts[0]; pts[1] = qPts[1]; pts[2] = qPts[2]; quadSubdivCnts->push_back() = subdiv; totalQuadCount += 1 << subdiv; } } }; // Applies the view matrix to quad src points and calls the above helper. auto addSrcChoppedQuad = [&](const SkPoint srcSpaceQuadPts[3], bool isContourStart) { SkPoint devPts[3]; m.mapPoints(devPts, srcSpaceQuadPts, 3); addChoppedQuad(srcSpaceQuadPts, devPts, isContourStart); }; for (;;) { SkPoint pathPts[4]; SkPath::Verb verb = iter.next(pathPts, false); switch (verb) { case SkPath::kConic_Verb: if (convertConicsToQuads) { SkScalar weight = iter.conicWeight(); SkAutoConicToQuads converter; const SkPoint* quadPts = converter.computeQuads(pathPts, weight, 0.25f); for (int i = 0; i < converter.countQuads(); ++i) { addSrcChoppedQuad(quadPts + 2 * i, !verbsInContour && 0 == i); } } else { SkConic dst[4]; // We chop the conics to create tighter clipping to hide error // that appears near max curvature of very thin conics. Thin // hyperbolas with high weight still show error. int conicCnt = chop_conic(pathPts, dst, iter.conicWeight()); for (int i = 0; i < conicCnt; ++i) { SkPoint devPts[4]; SkPoint* chopPnts = dst[i].fPts; m.mapPoints(devPts, chopPnts, 3); bounds.setBounds(devPts, 3); bounds.outset(SK_Scalar1, SK_Scalar1); bounds.roundOut(&ibounds); if (SkIRect::Intersects(devClipBounds, ibounds)) { if (is_degen_quad_or_conic(devPts)) { SkPoint* pts = lines->push_back_n(4); pts[0] = devPts[0]; pts[1] = devPts[1]; pts[2] = devPts[1]; pts[3] = devPts[2]; if (verbsInContour == 0 && i == 0 && pts[0] == pts[1] && pts[2] == pts[3]) { seenZeroLengthVerb = true; zeroVerbPt = pts[0]; } } else { // when in perspective keep conics in src space SkPoint* cPts = persp ? chopPnts : devPts; SkPoint* pts = conics->push_back_n(3); pts[0] = cPts[0]; pts[1] = cPts[1]; pts[2] = cPts[2]; conicWeights->push_back() = dst[i].fW; } } } } verbsInContour++; break; case SkPath::kMove_Verb: // New contour (and last one was unclosed). If it was just a zero length drawing // operation, and we're supposed to draw caps, then add a tiny line. if (seenZeroLengthVerb && verbsInContour == 1 && capLength > 0) { SkPoint* pts = lines->push_back_n(2); pts[0] = SkPoint::Make(zeroVerbPt.fX - capLength, zeroVerbPt.fY); pts[1] = SkPoint::Make(zeroVerbPt.fX + capLength, zeroVerbPt.fY); } verbsInContour = 0; seenZeroLengthVerb = false; break; case SkPath::kLine_Verb: { SkPoint devPts[2]; m.mapPoints(devPts, pathPts, 2); bounds.setBounds(devPts, 2); bounds.outset(SK_Scalar1, SK_Scalar1); bounds.roundOut(&ibounds); if (SkIRect::Intersects(devClipBounds, ibounds)) { SkPoint* pts = lines->push_back_n(2); pts[0] = devPts[0]; pts[1] = devPts[1]; if (verbsInContour == 0 && pts[0] == pts[1]) { seenZeroLengthVerb = true; zeroVerbPt = pts[0]; } } verbsInContour++; break; } case SkPath::kQuad_Verb: { SkPoint choppedPts[5]; // Chopping the quad helps when the quad is either degenerate or nearly degenerate. // When it is degenerate it allows the approximation with lines to work since the // chop point (if there is one) will be at the parabola's vertex. In the nearly // degenerate the QuadUVMatrix computed for the points is almost singular which // can cause rendering artifacts. int n = SkChopQuadAtMaxCurvature(pathPts, choppedPts); for (int i = 0; i < n; ++i) { addSrcChoppedQuad(choppedPts + i * 2, !verbsInContour && 0 == i); } verbsInContour++; break; } case SkPath::kCubic_Verb: { SkPoint devPts[4]; m.mapPoints(devPts, pathPts, 4); bounds.setBounds(devPts, 4); bounds.outset(SK_Scalar1, SK_Scalar1); bounds.roundOut(&ibounds); if (SkIRect::Intersects(devClipBounds, ibounds)) { PREALLOC_PTARRAY(32) q; // We convert cubics to quadratics (for now). // In perspective have to do conversion in src space. if (persp) { SkScalar tolScale = GrPathUtils::scaleToleranceToSrc(SK_Scalar1, m, path.getBounds()); GrPathUtils::convertCubicToQuads(pathPts, tolScale, &q); } else { GrPathUtils::convertCubicToQuads(devPts, SK_Scalar1, &q); } for (int i = 0; i < q.count(); i += 3) { if (persp) { addSrcChoppedQuad(&q[i], !verbsInContour && 0 == i); } else { addChoppedQuad(nullptr, &q[i], !verbsInContour && 0 == i); } } } verbsInContour++; break; } case SkPath::kClose_Verb: // Contour is closed, so we don't need to grow the starting line, unless it's // *just* a zero length subpath. (SVG Spec 11.4, 'stroke'). if (capLength > 0) { if (seenZeroLengthVerb && verbsInContour == 1) { SkPoint* pts = lines->push_back_n(2); pts[0] = SkPoint::Make(zeroVerbPt.fX - capLength, zeroVerbPt.fY); pts[1] = SkPoint::Make(zeroVerbPt.fX + capLength, zeroVerbPt.fY); } else if (verbsInContour == 0) { // Contour was (moveTo, close). Add a line. SkPoint devPts[2]; m.mapPoints(devPts, pathPts, 1); devPts[1] = devPts[0]; bounds.setBounds(devPts, 2); bounds.outset(SK_Scalar1, SK_Scalar1); bounds.roundOut(&ibounds); if (SkIRect::Intersects(devClipBounds, ibounds)) { SkPoint* pts = lines->push_back_n(2); pts[0] = SkPoint::Make(devPts[0].fX - capLength, devPts[0].fY); pts[1] = SkPoint::Make(devPts[1].fX + capLength, devPts[1].fY); } } } break; case SkPath::kDone_Verb: if (seenZeroLengthVerb && verbsInContour == 1 && capLength > 0) { // Path ended with a dangling (moveTo, line|quad|etc). If the final verb is // degenerate, we need to draw a line. SkPoint* pts = lines->push_back_n(2); pts[0] = SkPoint::Make(zeroVerbPt.fX - capLength, zeroVerbPt.fY); pts[1] = SkPoint::Make(zeroVerbPt.fX + capLength, zeroVerbPt.fY); } return totalQuadCount; } } }
static bool get_segments(const SkPath& path, const SkMatrix& m, SegmentArray* segments, SkPoint* fanPt, int* vCount, int* iCount) { SkPath::Iter iter(path, true); // This renderer over-emphasizes very thin path regions. We use the distance // to the path from the sample to compute coverage. Every pixel intersected // by the path will be hit and the maximum distance is sqrt(2)/2. We don't // notice that the sample may be close to a very thin area of the path and // thus should be very light. This is particularly egregious for degenerate // line paths. We detect paths that are very close to a line (zero area) and // draw nothing. DegenerateTestData degenerateData; SkPath::Direction dir; // get_direction can fail for some degenerate paths. if (!get_direction(path, m, &dir)) { return false; } for (;;) { SkPoint pts[4]; SkPath::Verb verb = iter.next(pts); switch (verb) { case SkPath::kMove_Verb: m.mapPoints(pts, 1); update_degenerate_test(°enerateData, pts[0]); break; case SkPath::kLine_Verb: { m.mapPoints(&pts[1], 1); update_degenerate_test(°enerateData, pts[1]); add_line_to_segment(pts[1], segments); break; } case SkPath::kQuad_Verb: m.mapPoints(pts, 3); update_degenerate_test(°enerateData, pts[1]); update_degenerate_test(°enerateData, pts[2]); add_quad_segment(pts, segments); break; case SkPath::kConic_Verb: { m.mapPoints(pts, 3); SkScalar weight = iter.conicWeight(); SkAutoConicToQuads converter; const SkPoint* quadPts = converter.computeQuads(pts, weight, 0.5f); for (int i = 0; i < converter.countQuads(); ++i) { update_degenerate_test(°enerateData, quadPts[2*i + 1]); update_degenerate_test(°enerateData, quadPts[2*i + 2]); add_quad_segment(quadPts + 2*i, segments); } break; } case SkPath::kCubic_Verb: { m.mapPoints(pts, 4); update_degenerate_test(°enerateData, pts[1]); update_degenerate_test(°enerateData, pts[2]); update_degenerate_test(°enerateData, pts[3]); add_cubic_segments(pts, dir, segments); break; }; case SkPath::kDone_Verb: if (degenerateData.isDegenerate()) { return false; } else { compute_vectors(segments, fanPt, dir, vCount, iCount); return true; } default: break; } } }