/* * Copyright 2008 The Android Open Source Project * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file.
*/
// 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;
}
SkPath 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. Since the zero length segment has no direction, set the orientation
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.getBounds().contains(fOuter.getBounds())) {
fInner.swap(fOuter);
}
} else { // now add fInner as its own contour
fInner.getLastPt(&pt);
fOuter.moveTo(pt);
fOuter.reversePathTo(fInner);
fOuter.close();
}
} else { // add caps to start and end // cap the end
fInner.getLastPt(&pt);
fCapper(&fOuter, fPrevPt, fPrevNormal, pt,
currIsLine ? &fInner : nullptr);
fOuter.reversePathTo(fInner); // cap the start
fCapper(&fOuter, fFirstPt, -fFirstNormal, fFirstOuterPt,
fPrevIsLine ? &fInner : nullptr);
fOuter.close();
} if (!fCusper.isEmpty()) {
fOuter.addPath(fCusper);
fCusper.rewind();
}
} // since we may re-use fInner, we rewind instead of reset, to save on // reallocating its internal storage.
fInner.rewind();
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;
}
// returns the distance squared from the point to the line static SkScalar pt_to_line(const SkPoint& pt, const SkPoint& lineStart, const SkPoint& lineEnd) {
SkVector dxy = lineEnd - lineStart;
SkVector ab0 = pt - lineStart;
SkScalar numer = dxy.dot(ab0);
SkScalar denom = dxy.dot(dxy);
SkScalar t = sk_ieee_float_divide(numer, denom); if (t >= 0 && t <= 1) {
SkPoint hit = lineStart * (1 - t) + lineEnd * t; return SkPointPriv::DistanceToSqd(hit, pt);
} else { return SkPointPriv::DistanceToSqd(pt, lineStart);
}
}
// returns the distance squared from the point to the line static SkScalar pt_to_tangent_line(const SkPoint& pt, const SkPoint& lineStart, const SkVector& tangent) {
SkVector dxy = tangent;
SkVector ab0 = pt - lineStart;
SkScalar numer = dxy.dot(ab0);
SkScalar denom = dxy.dot(dxy);
SkScalar t = sk_ieee_float_divide(numer, denom); if (t >= 0 && t <= 1) {
SkPoint hit = lineStart + tangent * t; return SkPointPriv::DistanceToSqd(hit, pt);
} else { return SkPointPriv::DistanceToSqd(pt, lineStart);
}
}
/* Given a cubic, determine if all four points are in a line. Return true if the inner points is close to a line connecting the outermost points.
Find the outermost point by looking for the largest difference in X or Y. Given the indices of the outermost points, and that outer_1 is greater than outer_2, this table shows the index of the smaller of the remaining points:
/* Given quad, see if all there points are in a line. Return true if the inside point is close to a line connecting the outermost points.
Find the outermost point by looking for the largest difference in X or Y. Since the XOR of the indices is 3 (0 ^ 1 ^ 2) the missing index equals: outer_1 ^ outer_2 ^ 3
*/ staticbool quad_in_line(const SkPoint quad[3]) {
SkScalar ptMax = -1; int outer1 SK_INIT_TO_AVOID_WARNING; int outer2 SK_INIT_TO_AVOID_WARNING; for (int index = 0; index < 2; ++index) { for (int inner = index + 1; inner < 3; ++inner) {
SkVector testDiff = quad[inner] - quad[index];
SkScalar testMax = std::max(SkScalarAbs(testDiff.fX), SkScalarAbs(testDiff.fY)); if (ptMax < testMax) {
outer1 = index;
outer2 = inner;
ptMax = testMax;
}
}
}
SkASSERT(outer1 >= 0 && outer1 <= 1);
SkASSERT(outer2 >= 1 && outer2 <= 2);
SkASSERT(outer1 < outer2); int mid = outer1 ^ outer2 ^ 3; constfloat kCurvatureSlop = 0.000005f; // this multiplier is pulled out of the air
SkScalar lineSlop = ptMax * ptMax * kCurvatureSlop; return pt_to_line(quad[mid], quad[outer1], quad[outer2]) <= lineSlop;
}
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 as if it were followed by a zero-length line. Lines without length
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 as if it were followed by a zero-length line. Lines without length
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;
} returntrue;
}
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));
}
// If src==dst, then we use a tmp path to record the stroke, and then swap // its contents with src when we're done. class AutoTmpPath { public:
AutoTmpPath(const SkPath& src, SkPath** dst) : fSrc(src) { if (&src == *dst) {
*dst = &fTmpDst;
fSwapWithSrc = true;
} else {
(*dst)->reset();
fSwapWithSrc = false;
}
}
~AutoTmpPath() { if (fSwapWithSrc) {
fTmpDst.swap(*const_cast<SkPath*>(&fSrc));
}
}
for (;;) {
SkPoint pts[4]; switch (iter.next(pts)) { case SkPath::kMove_Verb:
stroker.moveTo(pts[0]); break; case SkPath::kLine_Verb:
stroker.lineTo(pts[1], &iter);
lastSegment = SkPath::kLine_Verb; break; case SkPath::kQuad_Verb:
stroker.quadTo(pts[1], pts[2]);
lastSegment = SkPath::kQuad_Verb; break; case SkPath::kConic_Verb: {
stroker.conicTo(pts[1], pts[2], iter.conicWeight());
lastSegment = SkPath::kConic_Verb;
} break; case SkPath::kCubic_Verb:
stroker.cubicTo(pts[1], pts[2], pts[3]);
lastSegment = SkPath::kCubic_Verb; break; case SkPath::kClose_Verb: if (SkPaint::kButt_Cap != this->getCap()) { /* If the stroke consists of a moveTo followed by a close, treat it as if it were followed by a zero-length line. Lines without length
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, then followed by a close, treat is as if it were followed by a
zero-length line. Lines without length can have square & round end caps. */ if (stroker.isCurrentContourEmpty()) {
ZERO_LENGTH:
lastSegment = SkPath::kLine_Verb; break;
}
}
stroker.close(lastSegment == SkPath::kLine_Verb); break; case SkPath::kDone_Verb: goto DONE;
}
}
DONE:
stroker.done(dst, lastSegment == SkPath::kLine_Verb);
if (fDoFill && !ignoreCenter) { if (SkPathPriv::ComputeFirstDirection(src) == SkPathFirstDirection::kCCW) {
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 :( 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 #if 0 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.