static void selfOneOff(skiatest::Reporter* reporter, int index) { const CubicPts& cubic = selfSet[index]; SkPoint c[4]; for (int i = 0; i < 4; ++i) { c[i] = cubic.fPts[i].asSkPoint(); } SkScalar loopT; SkScalar d[3]; SkCubicType cubicType = SkClassifyCubic(c, d); if (SkDCubic::ComplexBreak(c, &loopT) && cubicType == SkCubicType::kLoop_SkCubicType) { SkIntersections i; SkPoint twoCubics[7]; SkChopCubicAt(c, twoCubics, loopT); SkDCubic chopped[2]; chopped[0].set(&twoCubics[0]); chopped[1].set(&twoCubics[3]); int result = i.intersect(chopped[0], chopped[1]); REPORTER_ASSERT(reporter, result == 2); REPORTER_ASSERT(reporter, i.used() == 2); for (int index = 0; index < result; ++index) { SkDPoint pt1 = chopped[0].ptAtT(i[0][index]); SkDPoint pt2 = chopped[1].ptAtT(i[1][index]); REPORTER_ASSERT(reporter, pt1.approximatelyEqual(pt2)); reporter->bumpTestCount(); } } }
bool SkDCubic::ComplexBreak(const SkPoint pointsPtr[4], SkScalar* t) { SkScalar d[3]; SkCubicType cubicType = SkClassifyCubic(pointsPtr, d); if (cubicType == kLoop_SkCubicType) { // crib code from gpu path utils that finds t values where loop self-intersects // use it to find mid of t values which should be a friendly place to chop SkScalar tempSqrt = SkScalarSqrt(4.f * d[0] * d[2] - 3.f * d[1] * d[1]); SkScalar ls = d[1] - tempSqrt; SkScalar lt = 2.f * d[0]; SkScalar ms = d[1] + tempSqrt; SkScalar mt = 2.f * d[0]; if (roughly_between(0, ls, lt) && roughly_between(0, ms, mt)) { ls = ls / lt; ms = ms / mt; SkASSERT(roughly_between(0, ls, 1) && roughly_between(0, ms, 1)); *t = (ls + ms) / 2; SkASSERT(roughly_between(0, *t, 1)); return *t > 0 && *t < 1; } } else if (kSerpentine_SkCubicType == cubicType || kCusp_SkCubicType == cubicType) { SkDCubic cubic; cubic.set(pointsPtr); double inflectionTs[2]; int infTCount = cubic.findInflections(inflectionTs); if (infTCount == 2) { double maxCurvature[3]; int roots = cubic.findMaxCurvature(maxCurvature); #if DEBUG_CUBIC_SPLIT SkDebugf("%s\n", __FUNCTION__); cubic.dump(); for (int index = 0; index < infTCount; ++index) { SkDebugf("inflectionsTs[%d]=%1.9g ", index, inflectionTs[index]); SkDPoint pt = cubic.ptAtT(inflectionTs[index]); SkDVector dPt = cubic.dxdyAtT(inflectionTs[index]); SkDLine perp = {{pt - dPt, pt + dPt}}; perp.dump(); } for (int index = 0; index < roots; ++index) { SkDebugf("maxCurvature[%d]=%1.9g ", index, maxCurvature[index]); SkDPoint pt = cubic.ptAtT(maxCurvature[index]); SkDVector dPt = cubic.dxdyAtT(maxCurvature[index]); SkDLine perp = {{pt - dPt, pt + dPt}}; perp.dump(); } #endif for (int index = 0; index < roots; ++index) { if (between(inflectionTs[0], maxCurvature[index], inflectionTs[1])) { *t = maxCurvature[index]; return *t > 0 && *t < 1; } } } else if (infTCount == 1) { *t = inflectionTs[0]; return *t > 0 && *t < 1; } } return false; }
CubicKLMBench(SkScalar x0, SkScalar y0, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar x3, SkScalar y3) { fPoints[0].set(x0, y0); fPoints[1].set(x1, y1); fPoints[2].set(x2, y2); fPoints[3].set(x3, y3); fName = "cubic_klm_"; switch (SkClassifyCubic(fPoints)) { case SkCubicType::kSerpentine: fName.append("serp"); break; case SkCubicType::kLoop: fName.append("loop"); break; default: SK_ABORT("Unexpected cubic type"); break; } }
void GrPathUtils::getCubicKLM(const SkPoint p[4], SkScalar klm[9]) { SkScalar d[3]; SkCubicType cType = SkClassifyCubic(p, d); SkScalar controlK[4]; SkScalar controlL[4]; SkScalar controlM[4]; if (kSerpentine_SkCubicType == cType || (kCusp_SkCubicType == cType && 0.f != d[0])) { set_serp_klm(d, controlK, controlL, controlM); } else if (kLoop_SkCubicType == cType) { set_loop_klm(d, controlK, controlL, controlM); } else if (kCusp_SkCubicType == cType) { SkASSERT(0.f == d[0]); set_cusp_klm(d, controlK, controlL, controlM); } else if (kQuadratic_SkCubicType == cType) { set_quadratic_klm(d, controlK, controlL, controlM); } calc_cubic_klm(p, controlK, controlL, controlM, klm, &klm[3], &klm[6]); }
int GrPathUtils::chopCubicAtLoopIntersection(const SkPoint src[4], SkPoint dst[10], SkScalar klm[9], SkScalar klm_rev[3]) { // Variable to store the two parametric values at the loop double point SkScalar smallS = 0.f; SkScalar largeS = 0.f; SkScalar d[3]; SkCubicType cType = SkClassifyCubic(src, d); int chop_count = 0; if (kLoop_SkCubicType == cType) { SkScalar tempSqrt = SkScalarSqrt(4.f * d[0] * d[2] - 3.f * d[1] * d[1]); SkScalar ls = d[1] - tempSqrt; SkScalar lt = 2.f * d[0]; SkScalar ms = d[1] + tempSqrt; SkScalar mt = 2.f * d[0]; ls = ls / lt; ms = ms / mt; // need to have t values sorted since this is what is expected by SkChopCubicAt if (ls <= ms) { smallS = ls; largeS = ms; } else { smallS = ms; largeS = ls; } SkScalar chop_ts[2]; if (smallS > 0.f && smallS < 1.f) { chop_ts[chop_count++] = smallS; } if (largeS > 0.f && largeS < 1.f) { chop_ts[chop_count++] = largeS; } if(dst) { SkChopCubicAt(src, dst, chop_ts, chop_count); } } else { if (dst) { memcpy(dst, src, sizeof(SkPoint) * 4); } } if (klm && klm_rev) { // Set klm_rev to to match the sub_section of cubic that needs to have its orientation // flipped. This will always be the section that is the "loop" if (2 == chop_count) { klm_rev[0] = 1.f; klm_rev[1] = -1.f; klm_rev[2] = 1.f; } else if (1 == chop_count) { if (smallS < 0.f) { klm_rev[0] = -1.f; klm_rev[1] = 1.f; } else { klm_rev[0] = 1.f; klm_rev[1] = -1.f; } } else { if (smallS < 0.f && largeS > 1.f) { klm_rev[0] = -1.f; } else { klm_rev[0] = 1.f; } } SkScalar controlK[4]; SkScalar controlL[4]; SkScalar controlM[4]; if (kSerpentine_SkCubicType == cType || (kCusp_SkCubicType == cType && 0.f != d[0])) { set_serp_klm(d, controlK, controlL, controlM); } else if (kLoop_SkCubicType == cType) { set_loop_klm(d, controlK, controlL, controlM); } else if (kCusp_SkCubicType == cType) { SkASSERT(0.f == d[0]); set_cusp_klm(d, controlK, controlL, controlM); } else if (kQuadratic_SkCubicType == cType) { set_quadratic_klm(d, controlK, controlL, controlM); } calc_cubic_klm(src, controlK, controlL, controlM, klm, &klm[3], &klm[6]); } return chop_count + 1; }
int SkDCubic::ComplexBreak(const SkPoint pointsPtr[4], SkScalar* t) { SkDCubic cubic; cubic.set(pointsPtr); if (cubic.monotonicInX() && cubic.monotonicInY()) { return 0; } SkScalar d[3]; SkCubicType cubicType = SkClassifyCubic(pointsPtr, d); switch (cubicType) { case kLoop_SkCubicType: { // crib code from gpu path utils that finds t values where loop self-intersects // use it to find mid of t values which should be a friendly place to chop SkScalar tempSqrt = SkScalarSqrt(4.f * d[0] * d[2] - 3.f * d[1] * d[1]); SkScalar ls = d[1] - tempSqrt; SkScalar lt = 2.f * d[0]; SkScalar ms = d[1] + tempSqrt; SkScalar mt = 2.f * d[0]; if (roughly_between(0, ls, lt) && roughly_between(0, ms, mt)) { ls = ls / lt; ms = ms / mt; SkASSERT(roughly_between(0, ls, 1) && roughly_between(0, ms, 1)); t[0] = (ls + ms) / 2; SkASSERT(roughly_between(0, *t, 1)); return (int) (t[0] > 0 && t[0] < 1); } } // fall through if no t value found case kSerpentine_SkCubicType: case kCusp_SkCubicType: { double inflectionTs[2]; int infTCount = cubic.findInflections(inflectionTs); double maxCurvature[3]; int roots = cubic.findMaxCurvature(maxCurvature); #if DEBUG_CUBIC_SPLIT SkDebugf("%s\n", __FUNCTION__); cubic.dump(); for (int index = 0; index < infTCount; ++index) { SkDebugf("inflectionsTs[%d]=%1.9g ", index, inflectionTs[index]); SkDPoint pt = cubic.ptAtT(inflectionTs[index]); SkDVector dPt = cubic.dxdyAtT(inflectionTs[index]); SkDLine perp = {{pt - dPt, pt + dPt}}; perp.dump(); } for (int index = 0; index < roots; ++index) { SkDebugf("maxCurvature[%d]=%1.9g ", index, maxCurvature[index]); SkDPoint pt = cubic.ptAtT(maxCurvature[index]); SkDVector dPt = cubic.dxdyAtT(maxCurvature[index]); SkDLine perp = {{pt - dPt, pt + dPt}}; perp.dump(); } #endif if (infTCount == 2) { for (int index = 0; index < roots; ++index) { if (between(inflectionTs[0], maxCurvature[index], inflectionTs[1])) { t[0] = maxCurvature[index]; return (int) (t[0] > 0 && t[0] < 1); } } } else { int resultCount = 0; // FIXME: constant found through experimentation -- maybe there's a better way.... double precision = cubic.calcPrecision() * 2; for (int index = 0; index < roots; ++index) { double testT = maxCurvature[index]; if (0 >= testT || testT >= 1) { continue; } // don't call dxdyAtT since we want (0,0) results SkDVector dPt = { derivative_at_t(&cubic.fPts[0].fX, testT), derivative_at_t(&cubic.fPts[0].fY, testT) }; double dPtLen = dPt.length(); if (dPtLen < precision) { t[resultCount++] = testT; } } if (!resultCount && infTCount == 1) { t[0] = inflectionTs[0]; resultCount = (int) (t[0] > 0 && t[0] < 1); } return resultCount; } } default: ; } return 0; }
void GrCCFillGeometry::cubicTo(const SkPoint P[4], float inflectPad, float loopIntersectPad) { SkASSERT(fBuildingContour); SkASSERT(P[0] == fPoints.back()); // Don't crunch on the curve or inflate geometry if it is nearly flat (or just very small). // Flat curves can break the math below. if (are_collinear(P)) { Sk2f p0 = Sk2f::Load(P); Sk2f p3 = Sk2f::Load(P+3); this->appendLine(p0, p3); return; } Sk2f p0 = Sk2f::Load(P); Sk2f p1 = Sk2f::Load(P+1); Sk2f p2 = Sk2f::Load(P+2); Sk2f p3 = Sk2f::Load(P+3); // Also detect near-quadratics ahead of time. Sk2f tan0, tan1, c; get_cubic_tangents(p0, p1, p2, p3, &tan0, &tan1); if (is_cubic_nearly_quadratic(p0, p1, p2, p3, tan0, tan1, &c)) { this->appendQuadratics(p0, c, p3); return; } double tt[2], ss[2], D[4]; fCurrCubicType = SkClassifyCubic(P, tt, ss, D); SkASSERT(!SkCubicIsDegenerate(fCurrCubicType)); Sk2f t = Sk2f(static_cast<float>(tt[0]), static_cast<float>(tt[1])); Sk2f s = Sk2f(static_cast<float>(ss[0]), static_cast<float>(ss[1])); ExcludedTerm skipTerm = (std::abs(D[2]) > std::abs(D[1])) ? ExcludedTerm::kQuadraticTerm : ExcludedTerm::kLinearTerm; Sk2f C0 = SkNx_fma(Sk2f(3), p1 - p2, p3 - p0); Sk2f C1 = (ExcludedTerm::kLinearTerm == skipTerm ? SkNx_fma(Sk2f(-2), p1, p0 + p2) : p1 - p0) * 3; Sk2f C0x1 = C0 * SkNx_shuffle<1,0>(C1); float Cdet = C0x1[0] - C0x1[1]; SkSTArray<4, float> chops; if (SkCubicType::kLoop != fCurrCubicType) { find_chops_around_inflection_points(inflectPad, t, s, C0, C1, skipTerm, Cdet, &chops); } else { find_chops_around_loop_intersection(loopIntersectPad, t, s, C0, C1, skipTerm, Cdet, &chops); } if (4 == chops.count() && chops[1] >= chops[2]) { // This just the means the KLM roots are so close that their paddings overlap. We will // approximate the entire middle section, but still have it chopped midway. For loops this // chop guarantees the append code only sees convex segments. Otherwise, it means we are (at // least almost) a cusp and the chop makes sure we get a sharp point. Sk2f ts = t * SkNx_shuffle<1,0>(s); chops[1] = chops[2] = (ts[0] + ts[1]) / (2*s[0]*s[1]); } #ifdef SK_DEBUG for (int i = 1; i < chops.count(); ++i) { SkASSERT(chops[i] >= chops[i - 1]); } #endif this->appendCubics(AppendCubicMode::kLiteral, p0, p1, p2, p3, chops.begin(), chops.count()); }