// Modify pts[] in place so that it is clipped in Y to the clip rect static void chop_cubic_in_Y(SkPoint pts[4], const SkRect& clip) { // are we partially above if (pts[0].fY < clip.fTop) { SkScalar t; if (chopMonoCubicAtY(pts, clip.fTop, &t)) { SkPoint tmp[7]; SkChopCubicAt(pts, tmp, t); // tmp[3, 4, 5].fY should all be to the below clip.fTop. // Since we can't trust the numerics of // the chopper, we force those conditions now tmp[3].fY = clip.fTop; clamp_ge(tmp[4].fY, clip.fTop); clamp_ge(tmp[5].fY, clip.fTop); pts[0] = tmp[3]; pts[1] = tmp[4]; pts[2] = tmp[5]; } else { // if chopMonoCubicAtY failed, then we may have hit inexact numerics // so we just clamp against the top for (int i = 0; i < 4; i++) { clamp_ge(pts[i].fY, clip.fTop); } } } // are we partially below if (pts[3].fY > clip.fBottom) { SkScalar t; if (chopMonoCubicAtY(pts, clip.fBottom, &t)) { SkPoint tmp[7]; SkChopCubicAt(pts, tmp, t); tmp[3].fY = clip.fBottom; clamp_le(tmp[2].fY, clip.fBottom); pts[1] = tmp[1]; pts[2] = tmp[2]; pts[3] = tmp[3]; } else { // if chopMonoCubicAtY failed, then we may have hit inexact numerics // so we just clamp against the bottom for (int i = 0; i < 4; i++) { clamp_le(pts[i].fY, clip.fBottom); } } } }
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 SkCubicClipper::clipCubic(const SkPoint srcPts[4], SkPoint dst[4]) { bool reverse; // we need the data to be monotonically descending in Y if (srcPts[0].fY > srcPts[3].fY) { dst[0] = srcPts[3]; dst[1] = srcPts[2]; dst[2] = srcPts[1]; dst[3] = srcPts[0]; reverse = true; } else { memcpy(dst, srcPts, 4 * sizeof(SkPoint)); reverse = false; } // are we completely above or below const SkScalar ctop = fClip.fTop; const SkScalar cbot = fClip.fBottom; if (dst[3].fY <= ctop || dst[0].fY >= cbot) { return false; } SkScalar t; SkPoint tmp[7]; // for SkChopCubicAt // are we partially above if (dst[0].fY < ctop && chopMonoCubicAtY(dst, ctop, &t)) { SkChopCubicAt(dst, tmp, t); dst[0] = tmp[3]; dst[1] = tmp[4]; dst[2] = tmp[5]; } // are we partially below if (dst[3].fY > cbot && chopMonoCubicAtY(dst, cbot, &t)) { SkChopCubicAt(dst, tmp, t); dst[1] = tmp[1]; dst[2] = tmp[2]; dst[3] = tmp[3]; } if (reverse) { SkTSwap<SkPoint>(dst[0], dst[3]); SkTSwap<SkPoint>(dst[1], dst[2]); } return true; }
// Modify pts[] in place so that it is clipped in Y to the clip rect static void chop_cubic_in_Y(SkPoint pts[4], const SkRect& clip) { SkScalar t; SkPoint tmp[7]; // for SkChopCubicAt // are we partially above if (pts[0].fY < clip.fTop) { if (chopMonoCubicAtY(pts, clip.fTop, &t)) { SkChopCubicAt(pts, tmp, t); // given the imprecision of computing t, we just slam our Y coord // to the top of the clip. This also saves us in the bad case where // the t was soooo bad that the entire segment could have been // below fBottom tmp[3].fY = clip.fTop; clamp_ge(tmp[4].fY, clip.fTop); clamp_ge(tmp[5].fY, clip.fTop); pts[0] = tmp[3]; pts[1] = tmp[4]; pts[2] = tmp[5]; } else { // if chopMonoCubicAtY failed, then we may have hit inexact numerics // so we just clamp against the top for (int i = 0; i < 4; i++) { clamp_ge(pts[i].fY, clip.fTop); } } } // are we partially below if (pts[3].fY > clip.fBottom) { if (chopMonoCubicAtY(pts, clip.fBottom, &t)) { SkChopCubicAt(pts, tmp, t); clamp_le(tmp[1].fY, clip.fBottom); clamp_le(tmp[2].fY, clip.fBottom); clamp_le(tmp[3].fY, clip.fBottom); pts[1] = tmp[1]; pts[2] = tmp[2]; pts[3] = tmp[3]; } else { // if chopMonoCubicAtY failed, then we may have hit inexact numerics // so we just clamp against the bottom for (int i = 0; i < 4; i++) { clamp_le(pts[i].fY, clip.fBottom); } } } }
int SkChopCubicAtInflections(const SkPoint src[], SkPoint dst[10]) { SkScalar tValues[2]; int count = SkFindCubicInflections(src, tValues); if (dst) { if (count == 0) { memcpy(dst, src, 4 * sizeof(SkPoint)); } else { SkChopCubicAt(src, dst, tValues, count); } } return count + 1; }
int SkChopCubicAtXExtrema(const SkPoint src[4], SkPoint dst[10]) { SkScalar tValues[2]; int roots = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, tValues); SkChopCubicAt(src, dst, tValues, roots); if (dst && roots > 0) { // we do some cleanup to ensure our Y extrema are flat flatten_double_cubic_extrema(&dst[0].fX); if (roots == 2) { flatten_double_cubic_extrema(&dst[3].fX); } } return roots; }
int SkChopCubicAtMaxCurvature(const SkPoint src[4], SkPoint dst[13], SkScalar tValues[3]) { SkScalar t_storage[3]; if (tValues == NULL) tValues = t_storage; int count = SkFindCubicMaxCurvature(src, tValues); if (dst) { if (count == 0) memcpy(dst, src, 4 * sizeof(SkPoint)); else SkChopCubicAt(src, dst, tValues, count); } return count + 1; }
void SkChopCubicAt(const SkPoint src[4], SkPoint dst[], const SkScalar tValues[], int roots) { #ifdef SK_DEBUG { for (int i = 0; i < roots - 1; i++) { SkASSERT(is_unit_interval(tValues[i])); SkASSERT(is_unit_interval(tValues[i+1])); SkASSERT(tValues[i] < tValues[i+1]); } } #endif if (dst) { if (roots == 0) // nothing to chop memcpy(dst, src, 4*sizeof(SkPoint)); else { SkScalar t = tValues[0]; SkPoint tmp[4]; for (int i = 0; i < roots; i++) { SkChopCubicAt(src, dst, t); if (i == roots - 1) break; dst += 3; // have src point to the remaining cubic (after the chop) memcpy(tmp, dst, 4 * sizeof(SkPoint)); src = tmp; // watch out in case the renormalized t isn't in range if (!valid_unit_divide(tValues[i+1] - tValues[i], SK_Scalar1 - tValues[i], &t)) { // if we can't, just create a degenerate cubic dst[4] = dst[5] = dst[6] = src[3]; break; } } } } }
// http://code.google.com/p/skia/issues/detail?id=32 static void test_cubic() { SkPoint src[4] = { { 556.25000f, 523.03003f }, { 556.23999f, 522.96002f }, { 556.21997f, 522.89001f }, { 556.21997f, 522.82001f } }; SkPoint dst[11]; dst[10].set(42, -42); // one past the end, that we don't clobber these SkScalar tval[] = { 0.33333334f, 0.99999994f }; SkChopCubicAt(src, dst, tval, 2); #if 0 for (int i = 0; i < 11; i++) { SkDebugf("--- %d [%g %g]\n", i, dst[i].fX, dst[i].fY); } #endif }
// srcPts[] must be monotonic in X and Y void SkEdgeClipper::clipMonoCubic(const SkPoint src[4], const SkRect& clip) { SkPoint pts[4]; bool reverse = sort_increasing_Y(pts, src, 4); // are we completely above or below if (pts[3].fY <= clip.fTop || pts[0].fY >= clip.fBottom) { return; } // Now chop so that pts is contained within clip in Y chop_cubic_in_Y(pts, clip); if (pts[0].fX > pts[3].fX) { SkTSwap<SkPoint>(pts[0], pts[3]); SkTSwap<SkPoint>(pts[1], pts[2]); reverse = !reverse; } // Now chop in X has needed, and record the segments if (pts[3].fX <= clip.fLeft) { // wholly to the left this->appendVLine(clip.fLeft, pts[0].fY, pts[3].fY, reverse); return; } if (pts[0].fX >= clip.fRight) { // wholly to the right this->appendVLine(clip.fRight, pts[0].fY, pts[3].fY, reverse); return; } SkScalar t; SkPoint tmp[7]; // are we partially to the left if (pts[0].fX < clip.fLeft) { if (chopMonoCubicAtX(pts, clip.fLeft, &t)) { SkChopCubicAt(pts, tmp, t); this->appendVLine(clip.fLeft, tmp[0].fY, tmp[3].fY, reverse); clamp_ge(tmp[3].fX, clip.fLeft); clamp_ge(tmp[4].fX, clip.fLeft); clamp_ge(tmp[5].fX, clip.fLeft); pts[0] = tmp[3]; pts[1] = tmp[4]; pts[2] = tmp[5]; } else { // if chopMonocubicAtY failed, then we may have hit inexact numerics // so we just clamp against the left this->appendVLine(clip.fLeft, pts[0].fY, pts[3].fY, reverse); return; } } // are we partially to the right if (pts[3].fX > clip.fRight) { if (chopMonoCubicAtX(pts, clip.fRight, &t)) { SkChopCubicAt(pts, tmp, t); clamp_le(tmp[1].fX, clip.fRight); clamp_le(tmp[2].fX, clip.fRight); clamp_le(tmp[3].fX, clip.fRight); this->appendCubic(tmp, reverse); this->appendVLine(clip.fRight, tmp[3].fY, tmp[6].fY, reverse); } else { // if chopMonoCubicAtX failed, then we may have hit inexact numerics // so we just clamp against the right this->appendVLine(clip.fRight, pts[0].fY, pts[3].fY, reverse); } } else { // wholly inside the clip this->appendCubic(pts, reverse); } }
bool SkOpEdgeBuilder::walk() { uint8_t* verbPtr = fPathVerbs.begin(); uint8_t* endOfFirstHalf = &verbPtr[fSecondHalf]; SkPoint* pointsPtr = fPathPts.begin() - 1; SkScalar* weightPtr = fWeights.begin(); SkPath::Verb verb; while ((verb = (SkPath::Verb) *verbPtr) != SkPath::kDone_Verb) { if (verbPtr == endOfFirstHalf) { fOperand = true; } verbPtr++; switch (verb) { case SkPath::kMove_Verb: if (fCurrentContour && fCurrentContour->count()) { if (fAllowOpenContours) { complete(); } else if (!close()) { return false; } } if (!fCurrentContour) { fCurrentContour = fContoursHead->appendContour(); } fCurrentContour->init(fGlobalState, fOperand, fXorMask[fOperand] == kEvenOdd_PathOpsMask); pointsPtr += 1; continue; case SkPath::kLine_Verb: fCurrentContour->addLine(pointsPtr); break; case SkPath::kQuad_Verb: { SkVector v1 = pointsPtr[1] - pointsPtr[0]; SkVector v2 = pointsPtr[2] - pointsPtr[1]; if (v1.dot(v2) < 0) { SkPoint pair[5]; if (SkChopQuadAtMaxCurvature(pointsPtr, pair) == 1) { goto addOneQuad; } if (!SkScalarsAreFinite(&pair[0].fX, SK_ARRAY_COUNT(pair) * 2)) { return false; } SkPoint cStorage[2][2]; SkPath::Verb v1 = SkReduceOrder::Quad(&pair[0], cStorage[0]); SkPath::Verb v2 = SkReduceOrder::Quad(&pair[2], cStorage[1]); SkPoint* curve1 = v1 != SkPath::kLine_Verb ? &pair[0] : cStorage[0]; SkPoint* curve2 = v2 != SkPath::kLine_Verb ? &pair[2] : cStorage[1]; if (can_add_curve(v1, curve1) && can_add_curve(v2, curve2)) { fCurrentContour->addCurve(v1, curve1); fCurrentContour->addCurve(v2, curve2); break; } } } addOneQuad: fCurrentContour->addQuad(pointsPtr); break; case SkPath::kConic_Verb: { SkVector v1 = pointsPtr[1] - pointsPtr[0]; SkVector v2 = pointsPtr[2] - pointsPtr[1]; SkScalar weight = *weightPtr++; if (v1.dot(v2) < 0) { // FIXME: max curvature for conics hasn't been implemented; use placeholder SkScalar maxCurvature = SkFindQuadMaxCurvature(pointsPtr); if (maxCurvature > 0) { SkConic conic(pointsPtr, weight); SkConic pair[2]; if (!conic.chopAt(maxCurvature, pair)) { // if result can't be computed, use original fCurrentContour->addConic(pointsPtr, weight); break; } SkPoint cStorage[2][3]; SkPath::Verb v1 = SkReduceOrder::Conic(pair[0], cStorage[0]); SkPath::Verb v2 = SkReduceOrder::Conic(pair[1], cStorage[1]); SkPoint* curve1 = v1 != SkPath::kLine_Verb ? pair[0].fPts : cStorage[0]; SkPoint* curve2 = v2 != SkPath::kLine_Verb ? pair[1].fPts : cStorage[1]; if (can_add_curve(v1, curve1) && can_add_curve(v2, curve2)) { fCurrentContour->addCurve(v1, curve1, pair[0].fW); fCurrentContour->addCurve(v2, curve2, pair[1].fW); break; } } } fCurrentContour->addConic(pointsPtr, weight); } break; case SkPath::kCubic_Verb: { // Split complex cubics (such as self-intersecting curves or // ones with difficult curvature) in two before proceeding. // This can be required for intersection to succeed. SkScalar splitT; if (SkDCubic::ComplexBreak(pointsPtr, &splitT)) { SkPoint pair[7]; SkChopCubicAt(pointsPtr, pair, splitT); if (!SkScalarsAreFinite(&pair[0].fX, SK_ARRAY_COUNT(pair) * 2)) { return false; } SkPoint cStorage[2][4]; SkPath::Verb v1 = SkReduceOrder::Cubic(&pair[0], cStorage[0]); SkPath::Verb v2 = SkReduceOrder::Cubic(&pair[3], cStorage[1]); SkPoint* curve1 = v1 == SkPath::kCubic_Verb ? &pair[0] : cStorage[0]; SkPoint* curve2 = v2 == SkPath::kCubic_Verb ? &pair[3] : cStorage[1]; if (can_add_curve(v1, curve1) && can_add_curve(v2, curve2)) { fCurrentContour->addCurve(v1, curve1); fCurrentContour->addCurve(v2, curve2); break; } } } fCurrentContour->addCubic(pointsPtr); break; case SkPath::kClose_Verb: SkASSERT(fCurrentContour); if (!close()) { return false; } continue; default: SkDEBUGFAIL("bad verb"); return false; } SkASSERT(fCurrentContour); fCurrentContour->debugValidate(); pointsPtr += SkPathOpsVerbToPoints(verb); } if (fCurrentContour && fCurrentContour->count() &&!fAllowOpenContours && !close()) { return false; } return true; }
void SkChopCubicAtHalf(const SkPoint src[4], SkPoint dst[7]) { SkChopCubicAt(src, dst, 0.5f); }
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; }
static void chop_mono_cubic_at_x(SkPoint src[4], SkScalar x, SkPoint dst[7]) { if (SkChopMonoCubicAtX(src, x, dst)) { return; } SkChopCubicAt(src, dst, mono_cubic_closestT(&src->fX, x)); }
static void chop_mono_cubic_at_y(SkPoint src[4], SkScalar y, SkPoint dst[7]) { if (SkChopMonoCubicAtY(src, y, dst)) { return; } SkChopCubicAt(src, dst, mono_cubic_closestT(&src->fY, y)); }