// quads with extreme widths (e.g. (0,1) (1,6) (0,3) width=5e7) recurse to point of failure // largest seen for normal cubics : 5, 26 // largest seen for normal quads : 11 // 3x limits seen in practice, except for cubics (3x limit would be ~75). // For cubics, we never get close to 75 when running through dm. The limit of 24 // was chosen because it's close to the peak in a count of cubic recursion depths visited // (define DEBUG_CUBIC_RECURSION_DEPTHS) and no diffs were produced on gold when using it. staticconstint kRecursiveLimits[] = { 5*3, 24, 11*3, 11*3 };
#if DEBUG_QUAD_STROKER /* Enable to show the decisions made in subdividing the curve -- helpful when the resulting
stroke has more than the optimal number of quadratics and lines */ #define STROKER_RESULT(resultType, depth, quadPts, format, ...) \
SkDebugf("[%d] %s " format "\n", depth, __FUNCTION__, __VA_ARGS__), \
SkDebugf(" "#resultType" t=(%g,%g)\n", quadPts->fStartT, quadPts->fEndT), \
resultType #define STROKER_DEBUG_PARAMS(...) , __VA_ARGS__ #else #define STROKER_RESULT(resultType, depth, quadPts, format, ...) \
resultType #define STROKER_DEBUG_PARAMS(...) #endif
#ifndef DEBUG_CUBIC_RECURSION_DEPTHS #define DEBUG_CUBIC_RECURSION_DEPTHS 0 #endif #if DEBUG_CUBIC_RECURSION_DEPTHS /* Prints a histogram of recursion depths at process termination. */ staticstruct DepthHistogram { inlinestatic constexpr int kMaxDepth = 75; int fCubicDepths[kMaxDepth + 1];
~DepthHistogram() {
SkDebugf("# times recursion terminated per depth:\n"); for (int i = 0; i <= kMaxDepth; i++) {
SkDebugf(" depth %d: %d\n", i, fCubicDepths[i]);
}
}
struct SkQuadConstruct { // The state of the quad stroke under construction.
SkPoint fQuad[3]; // the stroked quad parallel to the original curve
SkVector fTangentStart; // tangent vector at fQuad[0]
SkVector fTangentEnd; // tangent vector at fQuad[2]
SkScalar fStartT; // a segment of the original curve
SkScalar fMidT; // "
SkScalar fEndT; // " bool fStartSet; // state to share common points across structs bool fEndSet; // " bool fOppositeTangents; // set if coincident tangents have opposite directions
// return false if start and end are too close to have a unique middle bool init(SkScalar start, SkScalar end) {
fStartT = start;
fMidT = (start + end) * SK_ScalarHalf;
fEndT = end;
fStartSet = fEndSet = false; return fStartT < fMidT && fMidT < fEndT;
}
void done(SkPathBuilder* dst, bool isLine) {
this->finishContour(false, isLine);
*dst = fOuter;
fOuter.reset(); // is this needed? we used to "swap" it with dst
}
SkPathBuilder fInner, fOuter, fCusper; // outer is our working answer, inner is temp
enum StrokeType {
kOuter_StrokeType = 1, // use sign-opposite values later to flip perpendicular axis
kInner_StrokeType = -1
} fStrokeType;
enum ResultType {
kSplit_ResultType, // the caller should split the quad stroke in two
kDegenerate_ResultType, // the caller should add a line
kQuad_ResultType, // the caller should (continue to try to) add a quad stroke
};
enum ReductionType {
kPoint_ReductionType, // all curve points are practically identical
kLine_ReductionType, // the control point is on the line between the ends
kQuad_ReductionType, // the control point is outside the line between the ends
kDegenerate_ReductionType, // the control point is on the line but outside the ends
kDegenerate2_ReductionType, // two control points are on the line but outside ends (cubic)
kDegenerate3_ReductionType, // three areas of max curvature found (for cubic)
};
int fRecursionDepth; // track stack depth to abort if numerics run amok bool fFoundTangents; // do less work until tangents meet (cubic) bool fJoinCompleted; // previous join was not degenerate
if (!set_normal_unitnormal(fPrevPt, currPt, fResScale, fRadius, normal, unitNormal)) { if (SkStrokerPriv::CapFactory(SkPaint::kButt_Cap) == fCapper) { returnfalse;
} /* Square caps and round caps draw even if the segment length is zero. Sincethezerolengthsegmenthasnodirection,settheorientation
to upright as the default orientation */
normal->set(fRadius, 0);
unitNormal->set(1, 0);
}
if (fCanIgnoreCenter) { // If we can ignore the center just make sure the larger of the two paths // is preserved and don't add the smaller one. if (fInner.computeBounds().contains(fOuter.computeBounds())) {
fOuter = fInner;
}
} else { // now add fInner as its own contour if (auto pt = fInner.getLastPt()) {
fOuter.moveTo(*pt);
fOuter.privateReversePathTo(fInner.detach()); // todo: take builder or raw
fOuter.close();
}
}
} else { // add caps to start and end // cap the end if (auto pt = fInner.getLastPt()) {
fCapper(&fOuter, fPrevPt, fPrevNormal, *pt, currIsLine);
fOuter.privateReversePathTo(fInner.detach()); // cap the start
fCapper(&fOuter, fFirstPt, -fFirstNormal, fFirstOuterPt, fPrevIsLine);
fOuter.close();
}
} if (!fCusper.isEmpty()) {
fOuter.addPath(fCusper.detach());
}
}
fInner.reset();
fSegmentCount = -1;
fFirstOuterPtIndexInContour = fOuter.countPoints();
}
// Need some estimate of how large our final result (fOuter) // and our per-contour temp (fInner) will be, so we don't spend // extra time repeatedly growing these arrays. // // 3x for result == inner + outer + join (swag) // 1x for inner == 'wag' (worst contour length would be better guess)
fOuter.incReserve(src.countPoints() * 3);
fOuter.setIsVolatile(true);
fInner.incReserve(src.countPoints());
fInner.setIsVolatile(true); // TODO : write a common error function used by stroking and filling // The '4' below matches the fill scan converter's error term
fInvResScale = SkScalarInvert(resScale * 4);
fInvResScaleSquared = fInvResScale * fInvResScale;
fRecursionDepth = 0;
}
void SkPathStroker::conicTo(const SkPoint& pt1, const SkPoint& pt2, SkScalar weight) { const SkConic conic(fPrevPt, pt1, pt2, weight);
SkPoint reduction;
ReductionType reductionType = CheckConicLinear(conic, &reduction); if (kPoint_ReductionType == reductionType) { /* If the stroke consists of a moveTo followed by a degenerate curve, treat it asifitwerefollowedbyazero-lengthline.Lineswithoutlength
can have square and round end caps. */
this->lineTo(pt2); return;
} if (kLine_ReductionType == reductionType) {
this->lineTo(pt2); return;
} if (kDegenerate_ReductionType == reductionType) {
this->lineTo(reduction);
SkStrokerPriv::JoinProc saveJoiner = fJoiner;
fJoiner = SkStrokerPriv::JoinFactory(SkPaint::kRound_Join);
this->lineTo(pt2);
fJoiner = saveJoiner; return;
}
SkASSERT(kQuad_ReductionType == reductionType);
SkVector normalAB, unitAB, normalBC, unitBC; if (!this->preJoinTo(pt1, &normalAB, &unitAB, false)) {
this->lineTo(pt2); return;
}
SkQuadConstruct quadPts;
this->init(kOuter_StrokeType, &quadPts, 0, 1);
(void) this->conicStroke(conic, &quadPts);
this->init(kInner_StrokeType, &quadPts, 0, 1);
(void) this->conicStroke(conic, &quadPts);
this->setConicEndNormal(conic, normalAB, unitAB, &normalBC, &unitBC);
this->postJoinTo(pt2, normalBC, unitBC);
}
void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) { const SkPoint quad[3] = { fPrevPt, pt1, pt2 };
SkPoint reduction;
ReductionType reductionType = CheckQuadLinear(quad, &reduction); if (kPoint_ReductionType == reductionType) { /* If the stroke consists of a moveTo followed by a degenerate curve, treat it asifitwerefollowedbyazero-lengthline.Lineswithoutlength
can have square and round end caps. */
this->lineTo(pt2); return;
} if (kLine_ReductionType == reductionType) {
this->lineTo(pt2); return;
} if (kDegenerate_ReductionType == reductionType) {
this->lineTo(reduction);
SkStrokerPriv::JoinProc saveJoiner = fJoiner;
fJoiner = SkStrokerPriv::JoinFactory(SkPaint::kRound_Join);
this->lineTo(pt2);
fJoiner = saveJoiner; return;
}
SkASSERT(kQuad_ReductionType == reductionType);
SkVector normalAB, unitAB, normalBC, unitBC; if (!this->preJoinTo(pt1, &normalAB, &unitAB, false)) {
this->lineTo(pt2); return;
}
SkQuadConstruct quadPts;
this->init(kOuter_StrokeType, &quadPts, 0, 1);
(void) this->quadStroke(quad, &quadPts);
this->init(kInner_StrokeType, &quadPts, 0, 1);
(void) this->quadStroke(quad, &quadPts);
this->setQuadEndNormal(quad, normalAB, unitAB, &normalBC, &unitBC);
this->postJoinTo(pt2, normalBC, unitBC);
}
// Given a point on the curve and its derivative, scale the derivative by the radius, and // compute the perpendicular point and its tangent. void SkPathStroker::setRayPts(const SkPoint& tPt, SkVector* dxy, SkPoint* onPt,
SkVector* tangent) const { if (!dxy->setLength(fRadius)) {
dxy->set(fRadius, 0);
}
SkScalar axisFlip = SkIntToScalar(fStrokeType); // go opposite ways for outer, inner
onPt->fX = tPt.fX + axisFlip * dxy->fY;
onPt->fY = tPt.fY - axisFlip * dxy->fX; if (tangent) {
*tangent = *dxy;
}
}
// Given a conic and t, return the point on curve, its perpendicular, and the perpendicular tangent. // Returns false if the perpendicular could not be computed (because the derivative collapsed to 0) void SkPathStroker::conicPerpRay(const SkConic& conic, SkScalar t, SkPoint* tPt, SkPoint* onPt,
SkVector* tangent) const {
SkVector dxy;
conic.evalAt(t, tPt, &dxy); if (dxy.isZero()) {
dxy = conic.fPts[2] - conic.fPts[0];
}
this->setRayPts(*tPt, &dxy, onPt, tangent);
}
// Given a conic and a t range, find the start and end if they haven't been found already. void SkPathStroker::conicQuadEnds(const SkConic& conic, SkQuadConstruct* quadPts) const { if (!quadPts->fStartSet) {
SkPoint conicStartPt;
this->conicPerpRay(conic, quadPts->fStartT, &conicStartPt, &quadPts->fQuad[0],
&quadPts->fTangentStart);
quadPts->fStartSet = true;
} if (!quadPts->fEndSet) {
SkPoint conicEndPt;
this->conicPerpRay(conic, quadPts->fEndT, &conicEndPt, &quadPts->fQuad[2],
&quadPts->fTangentEnd);
quadPts->fEndSet = true;
}
}
// Given a cubic and t, return the point on curve, its perpendicular, and the perpendicular tangent. void SkPathStroker::cubicPerpRay(const SkPoint cubic[4], SkScalar t, SkPoint* tPt, SkPoint* onPt,
SkVector* tangent) const {
SkVector dxy;
SkPoint chopped[7];
SkEvalCubicAt(cubic, t, tPt, &dxy, nullptr); if (dxy.isZero()) { const SkPoint* cPts = cubic; if (SkScalarNearlyZero(t)) {
dxy = cubic[2] - cubic[0];
} elseif (SkScalarNearlyZero(1 - t)) {
dxy = cubic[3] - cubic[1];
} else { // If the cubic inflection falls on the cusp, subdivide the cubic // to find the tangent at that point.
SkChopCubicAt(cubic, chopped, t);
dxy = chopped[3] - chopped[2]; if (dxy.isZero()) {
dxy = chopped[3] - chopped[1];
cPts = chopped;
}
} if (dxy.isZero()) {
dxy = cPts[3] - cPts[0];
}
}
setRayPts(*tPt, &dxy, onPt, tangent);
}
// Given a cubic and a t range, find the start and end if they haven't been found already. void SkPathStroker::cubicQuadEnds(const SkPoint cubic[4], SkQuadConstruct* quadPts) { if (!quadPts->fStartSet) {
SkPoint cubicStartPt;
this->cubicPerpRay(cubic, quadPts->fStartT, &cubicStartPt, &quadPts->fQuad[0],
&quadPts->fTangentStart);
quadPts->fStartSet = true;
} if (!quadPts->fEndSet) {
SkPoint cubicEndPt;
this->cubicPerpRay(cubic, quadPts->fEndT, &cubicEndPt, &quadPts->fQuad[2],
&quadPts->fTangentEnd);
quadPts->fEndSet = true;
}
}
// Given a quad and t, return the point on curve, its perpendicular, and the perpendicular tangent. void SkPathStroker::quadPerpRay(const SkPoint quad[3], SkScalar t, SkPoint* tPt, SkPoint* onPt,
SkVector* tangent) const {
SkVector dxy;
SkEvalQuadAt(quad, t, tPt, &dxy); if (dxy.isZero()) {
dxy = quad[2] - quad[0];
}
setRayPts(*tPt, &dxy, onPt, tangent);
}
// Find the intersection of the stroke tangents to construct a stroke quad. // Return whether the stroke is a degenerate (a line), a quad, or must be split. // Optionally compute the quad's control point.
SkPathStroker::ResultType SkPathStroker::intersectRay(SkQuadConstruct* quadPts,
IntersectRayType intersectRayType STROKER_DEBUG_PARAMS(int depth)) const { const SkPoint& start = quadPts->fQuad[0]; const SkPoint& end = quadPts->fQuad[2];
SkVector aLen = quadPts->fTangentStart;
SkVector bLen = quadPts->fTangentEnd; /* Slopes match when denom goes to zero: axLen/ayLen==bxLen/byLen (ayLen*byLen)*axLen/ayLen==(ayLen*byLen)*bxLen/byLen byLen*axLen==ayLen*bxLen byLen*axLen-ayLen*bxLen(==denom)
*/
SkScalar denom = aLen.cross(bLen); if (denom == 0 || !SkIsFinite(denom)) {
quadPts->fOppositeTangents = aLen.dot(bLen) < 0; return STROKER_RESULT(kDegenerate_ResultType, depth, quadPts, "denom == 0");
}
quadPts->fOppositeTangents = false;
SkVector ab0 = start - end;
SkScalar numerA = bLen.cross(ab0);
SkScalar numerB = aLen.cross(ab0); if ((numerA >= 0) == (numerB >= 0)) { // if the control point is outside the quad ends // if the perpendicular distances from the quad points to the opposite tangent line // are small, a straight line is good enough
SkScalar dist1 = pt_to_tangent_line(start, end, quadPts->fTangentEnd);
SkScalar dist2 = pt_to_tangent_line(end, start, quadPts->fTangentStart); if (std::max(dist1, dist2) <= fInvResScaleSquared) { return STROKER_RESULT(kDegenerate_ResultType, depth, quadPts, "std::max(dist1=%g, dist2=%g) <= fInvResScaleSquared", dist1, dist2);
} return STROKER_RESULT(kSplit_ResultType, depth, quadPts, "(numerA=%g >= 0) == (numerB=%g >= 0)", numerA, numerB);
} // check to see if the denominator is teeny relative to the numerator // if the offset by one will be lost, the ratio is too large
numerA /= denom; bool validDivide = numerA > numerA - 1; if (validDivide) { if (kCtrlPt_RayType == intersectRayType) {
SkPoint* ctrlPt = &quadPts->fQuad[1]; // the intersection of the tangents need not be on the tangent segment // so 0 <= numerA <= 1 is not necessarily true
*ctrlPt = start + quadPts->fTangentStart * numerA;
} return STROKER_RESULT(kQuad_ResultType, depth, quadPts, "(numerA=%g >= 0) != (numerB=%g >= 0)", numerA, numerB);
}
quadPts->fOppositeTangents = aLen.dot(bLen) < 0; // if the lines are parallel, straight line is good enough return STROKER_RESULT(kDegenerate_ResultType, depth, quadPts, "SkScalarNearlyZero(denom=%g)", denom);
}
// Given a cubic and a t-range, determine if the stroke can be described by a quadratic.
SkPathStroker::ResultType SkPathStroker::tangentsMeet(const SkPoint cubic[4],
SkQuadConstruct* quadPts) {
this->cubicQuadEnds(cubic, quadPts); return this->intersectRay(quadPts, kResultType_RayType STROKER_DEBUG_PARAMS(fRecursionDepth));
}
// Intersect the line with the quad and return the t values on the quad where the line crosses. staticint intersect_quad_ray(const SkPoint line[2], const SkPoint quad[3], SkScalar roots[2]) {
SkVector vec = line[1] - line[0];
SkScalar r[3]; for (int n = 0; n < 3; ++n) {
r[n] = vec.cross(quad[n] - line[0]);
}
SkScalar A = r[2];
SkScalar B = r[1];
SkScalar C = r[0];
A += C - 2 * B; // A = a - 2*b + c
B -= C; // B = -(b - c) return SkFindUnitQuadRoots(A, 2 * B, C, roots);
}
// Return true if the point is close to the bounds of the quad. This is used as a quick reject. bool SkPathStroker::ptInQuadBounds(const SkPoint quad[3], const SkPoint& pt) const {
SkScalar xMin = std::min({quad[0].fX, quad[1].fX, quad[2].fX}); if (pt.fX + fInvResScale < xMin) { returnfalse;
}
SkScalar xMax = std::max({quad[0].fX, quad[1].fX, quad[2].fX}); if (pt.fX - fInvResScale > xMax) { returnfalse;
}
SkScalar yMin = std::min({quad[0].fY, quad[1].fY, quad[2].fY}); if (pt.fY + fInvResScale < yMin) { returnfalse;
}
SkScalar yMax = std::max({quad[0].fY, quad[1].fY, quad[2].fY}); if (pt.fY - fInvResScale > yMax) { returnfalse;
} return true;
}
SkPathStroker::ResultType SkPathStroker::strokeCloseEnough(const SkPoint stroke[3], const SkPoint ray[2], SkQuadConstruct* quadPts STROKER_DEBUG_PARAMS(int depth)) const {
SkPoint strokeMid = SkEvalQuadAt(stroke, SK_ScalarHalf); // measure the distance from the curve to the quad-stroke midpoint, compare to radius if (points_within_dist(ray[0], strokeMid, fInvResScale)) { // if the difference is small if (sharp_angle(quadPts->fQuad)) { return STROKER_RESULT(kSplit_ResultType, depth, quadPts, "sharp_angle (1) =%g,%g, %g,%g, %g,%g",
quadPts->fQuad[0].fX, quadPts->fQuad[0].fY,
quadPts->fQuad[1].fX, quadPts->fQuad[1].fY,
quadPts->fQuad[2].fX, quadPts->fQuad[2].fY);
} return STROKER_RESULT(kQuad_ResultType, depth, quadPts, "points_within_dist(ray[0]=%g,%g, strokeMid=%g,%g, fInvResScale=%g)",
ray[0].fX, ray[0].fY, strokeMid.fX, strokeMid.fY, fInvResScale);
} // measure the distance to quad's bounds (quick reject) // an alternative : look for point in triangle if (!ptInQuadBounds(stroke, ray[0])) { // if far, subdivide return STROKER_RESULT(kSplit_ResultType, depth, quadPts, "!pt_in_quad_bounds(stroke=(%g,%g %g,%g %g,%g), ray[0]=%g,%g)",
stroke[0].fX, stroke[0].fY, stroke[1].fX, stroke[1].fY, stroke[2].fX, stroke[2].fY,
ray[0].fX, ray[0].fY);
} // measure the curve ray distance to the quad-stroke
SkScalar roots[2]; int rootCount = intersect_quad_ray(ray, stroke, roots); if (rootCount != 1) { return STROKER_RESULT(kSplit_ResultType, depth, quadPts, "rootCount=%d != 1", rootCount);
}
SkPoint quadPt = SkEvalQuadAt(stroke, roots[0]);
SkScalar error = fInvResScale * (SK_Scalar1 - SkScalarAbs(roots[0] - 0.5f) * 2); if (points_within_dist(ray[0], quadPt, error)) { // if the difference is small, we're done if (sharp_angle(quadPts->fQuad)) { return STROKER_RESULT(kSplit_ResultType, depth, quadPts, "sharp_angle (2) =%g,%g, %g,%g, %g,%g",
quadPts->fQuad[0].fX, quadPts->fQuad[0].fY,
quadPts->fQuad[1].fX, quadPts->fQuad[1].fY,
quadPts->fQuad[2].fX, quadPts->fQuad[2].fY);
} return STROKER_RESULT(kQuad_ResultType, depth, quadPts, "points_within_dist(ray[0]=%g,%g, quadPt=%g,%g, error=%g)",
ray[0].fX, ray[0].fY, quadPt.fX, quadPt.fY, error);
} // otherwise, subdivide return STROKER_RESULT(kSplit_ResultType, depth, quadPts, "%s", "fall through");
}
SkPathStroker::ResultType SkPathStroker::compareQuadCubic(const SkPoint cubic[4],
SkQuadConstruct* quadPts) { // get the quadratic approximation of the stroke
this->cubicQuadEnds(cubic, quadPts);
ResultType resultType = this->intersectRay(quadPts, kCtrlPt_RayType
STROKER_DEBUG_PARAMS(fRecursionDepth) ); if (resultType != kQuad_ResultType) { return resultType;
} // project a ray from the curve to the stroke
SkPoint ray[2]; // points near midpoint on quad, midpoint on cubic
this->cubicPerpRay(cubic, quadPts->fMidT, &ray[1], &ray[0], nullptr); return this->strokeCloseEnough(quadPts->fQuad, ray, quadPts
STROKER_DEBUG_PARAMS(fRecursionDepth));
}
SkPathStroker::ResultType SkPathStroker::compareQuadConic(const SkConic& conic,
SkQuadConstruct* quadPts) const { // get the quadratic approximation of the stroke
this->conicQuadEnds(conic, quadPts);
ResultType resultType = this->intersectRay(quadPts, kCtrlPt_RayType
STROKER_DEBUG_PARAMS(fRecursionDepth) ); if (resultType != kQuad_ResultType) { return resultType;
} // project a ray from the curve to the stroke
SkPoint ray[2]; // points near midpoint on quad, midpoint on conic
this->conicPerpRay(conic, quadPts->fMidT, &ray[1], &ray[0], nullptr); return this->strokeCloseEnough(quadPts->fQuad, ray, quadPts
STROKER_DEBUG_PARAMS(fRecursionDepth));
}
SkPathStroker::ResultType SkPathStroker::compareQuadQuad(const SkPoint quad[3],
SkQuadConstruct* quadPts) { // get the quadratic approximation of the stroke if (!quadPts->fStartSet) {
SkPoint quadStartPt;
this->quadPerpRay(quad, quadPts->fStartT, &quadStartPt, &quadPts->fQuad[0],
&quadPts->fTangentStart);
quadPts->fStartSet = true;
} if (!quadPts->fEndSet) {
SkPoint quadEndPt;
this->quadPerpRay(quad, quadPts->fEndT, &quadEndPt, &quadPts->fQuad[2],
&quadPts->fTangentEnd);
quadPts->fEndSet = true;
}
ResultType resultType = this->intersectRay(quadPts, kCtrlPt_RayType
STROKER_DEBUG_PARAMS(fRecursionDepth)); if (resultType != kQuad_ResultType) { return resultType;
} // project a ray from the curve to the stroke
SkPoint ray[2];
this->quadPerpRay(quad, quadPts->fMidT, &ray[1], &ray[0], nullptr); return this->strokeCloseEnough(quadPts->fQuad, ray, quadPts
STROKER_DEBUG_PARAMS(fRecursionDepth));
}
SkPath::Iter iter(src, false);
SkPathVerb lastSegment = SkPathVerb::kMove; while (auto rec = iter.next()) {
SkSpan<const SkPoint> pts = rec->fPoints; switch (rec->fVerb) { case SkPathVerb::kMove:
stroker.moveTo(pts[0]); break; case SkPathVerb::kLine:
stroker.lineTo(pts[1], &iter);
lastSegment = SkPathVerb::kLine; break; case SkPathVerb::kQuad:
stroker.quadTo(pts[1], pts[2]);
lastSegment = SkPathVerb::kQuad; break; case SkPathVerb::kConic: {
stroker.conicTo(pts[1], pts[2], rec->conicWeight());
lastSegment = SkPathVerb::kConic;
} break; case SkPathVerb::kCubic:
stroker.cubicTo(pts[1], pts[2], pts[3]);
lastSegment = SkPathVerb::kCubic; break; case SkPathVerb::kClose: if (SkPaint::kButt_Cap != this->getCap()) { /* If the stroke consists of a moveTo followed by a close, treat it asifitwerefollowedbyazero-lengthline.Lineswithoutlength
can have square and round end caps. */ if (stroker.hasOnlyMoveTo()) {
stroker.lineTo(stroker.moveToPt()); goto ZERO_LENGTH;
} /* If the stroke consists of a moveTo followed by one or more zero-length verbs,thenfollowedbyaclose,treatisasifitwerefollowedbya
zero-length line. Lines without length can have square & round end caps. */ if (stroker.isCurrentContourEmpty()) {
ZERO_LENGTH:
lastSegment = SkPathVerb::kLine; break;
}
}
stroker.close(lastSegment == SkPathVerb::kLine); break;
}
}
stroker.done(dst, lastSegment == SkPathVerb::kLine);
if (fDoFill && !ignoreCenter) { auto d = SkPathPriv::ComputeFirstDirection(*raw); if (d == SkPathFirstDirection::kCCW) {
dst->privateReverseAddPath(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()) #if0 // this test results in a non-convex stroke :( staticvoid 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 #if0 if (2 == src.countPoints()) {
dst->setIsConvex(true);
} #endif
}
// our answer should preserve the inverseness of the src if (src.isInverseFillType()) {
SkASSERT(!dst->isInverseFillType());
dst->toggleInverseFillType();
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.