/* Returns 0 for 1 quad, and 1 for two quads, either way the answer is stored in dst[]. Guarantees that the 1/2 quads will be monotonic. */ int SkChopQuadAtXExtrema(const SkPoint src[3], SkPoint dst[5]) { SkASSERT(src); SkASSERT(dst); SkScalar a = src[0].fX; SkScalar b = src[1].fX; SkScalar c = src[2].fX; if (is_not_monotonic(a, b, c)) { SkScalar tValue; if (valid_unit_divide(a - b, a - b - b + c, &tValue)) { SkChopQuadAt(src, dst, tValue); flatten_double_quad_extrema(&dst[0].fX); return 1; } // if we get here, we need to force dst to be monotonic, even though // we couldn't compute a unit_divide value (probably underflow). b = SkScalarAbs(a - b) < SkScalarAbs(b - c) ? a : c; } dst[0].set(a, src[0].fY); dst[1].set(b, src[1].fY); dst[2].set(c, src[2].fY); return 0; }
// Modify pts[] in place so that it is clipped in Y to the clip rect static void chop_quad_in_Y(SkPoint pts[3], const SkRect& clip) { SkScalar t; SkPoint tmp[5]; // for SkChopQuadAt // are we partially above if (pts[0].fY < clip.fTop) { if (chopMonoQuadAtY(pts, clip.fTop, &t)) { // take the 2nd chopped quad SkChopQuadAt(pts, tmp, t); // clamp to clean up imprecise numerics in the chop tmp[2].fY = clip.fTop; clamp_ge(tmp[3].fY, clip.fTop); pts[0] = tmp[2]; pts[1] = tmp[3]; } else { // if chopMonoQuadAtY failed, then we may have hit inexact numerics // so we just clamp against the top for (int i = 0; i < 3; i++) { if (pts[i].fY < clip.fTop) { pts[i].fY = clip.fTop; } } } } // are we partially below if (pts[2].fY > clip.fBottom) { if (chopMonoQuadAtY(pts, clip.fBottom, &t)) { SkChopQuadAt(pts, tmp, t); // clamp to clean up imprecise numerics in the chop clamp_le(tmp[1].fY, clip.fBottom); tmp[2].fY = clip.fBottom; pts[1] = tmp[1]; pts[2] = tmp[2]; } else { // if chopMonoQuadAtY failed, then we may have hit inexact numerics // so we just clamp against the bottom for (int i = 0; i < 3; i++) { if (pts[i].fY > clip.fBottom) { pts[i].fY = clip.fBottom; } } } } }
/* If we somehow returned the fact that we had to flip the pts in Y, we could communicate that to setQuadratic, and then avoid having to flip it back here (only to have setQuadratic do the flip again) */ bool SkQuadClipper::clipQuad(const SkPoint srcPts[3], SkPoint dst[3]) { bool reverse; // we need the data to be monotonically descending in Y if (srcPts[0].fY > srcPts[2].fY) { dst[0] = srcPts[2]; dst[1] = srcPts[1]; dst[2] = srcPts[0]; reverse = true; } else { memcpy(dst, srcPts, 3 * sizeof(SkPoint)); reverse = false; } // are we completely above or below const SkScalar ctop = fClip.fTop; const SkScalar cbot = fClip.fBottom; if (dst[2].fY <= ctop || dst[0].fY >= cbot) { return false; } SkScalar t; SkPoint tmp[5]; // for SkChopQuadAt // are we partially above if (dst[0].fY < ctop && chopMonoQuadAtY(dst, ctop, &t)) { SkChopQuadAt(dst, tmp, t); dst[0] = tmp[2]; dst[1] = tmp[3]; } // are we partially below if (dst[2].fY > cbot && chopMonoQuadAtY(dst, cbot, &t)) { SkChopQuadAt(dst, tmp, t); dst[1] = tmp[1]; dst[2] = tmp[2]; } if (reverse) { SkTSwap<SkPoint>(dst[0], dst[2]); } return true; }
int SkChopQuadAtMaxCurvature(const SkPoint src[3], SkPoint dst[5]) { SkScalar t = SkFindQuadMaxCurvature(src); if (t == 0) { memcpy(dst, src, 3 * sizeof(SkPoint)); return 1; } else { SkChopQuadAt(src, dst, t); return 2; } }
// F(t) = a (1 - t) ^ 2 + 2 b t (1 - t) + c t ^ 2 // F'(t) = 2 (b - a) + 2 (a - 2b + c) t // F''(t) = 2 (a - 2b + c) // // A = 2 (b - a) // B = 2 (a - 2b + c) // // Maximum curvature for a quadratic means solving // Fx' Fx'' + Fy' Fy'' = 0 // // t = - (Ax Bx + Ay By) / (Bx ^ 2 + By ^ 2) // int SkChopQuadAtMaxCurvature(const SkPoint src[3], SkPoint dst[5]) { SkScalar Ax = src[1].fX - src[0].fX; SkScalar Ay = src[1].fY - src[0].fY; SkScalar Bx = src[0].fX - src[1].fX - src[1].fX + src[2].fX; SkScalar By = src[0].fY - src[1].fY - src[1].fY + src[2].fY; SkScalar t = 0; // 0 means don't chop #ifdef SK_SCALAR_IS_FLOAT (void)valid_unit_divide(-(Ax * Bx + Ay * By), Bx * Bx + By * By, &t); #else // !!! should I use SkFloat here? seems like it Sk64 numer, denom, tmp; numer.setMul(Ax, -Bx); tmp.setMul(Ay, -By); numer.add(tmp); if (numer.isPos()) // do nothing if numer <= 0 { denom.setMul(Bx, Bx); tmp.setMul(By, By); denom.add(tmp); SkASSERT(!denom.isNeg()); if (numer < denom) { t = numer.getFixedDiv(denom); SkASSERT(t >= 0 && t <= SK_Fixed1); // assert that we're numerically stable (ha!) if ((unsigned)t >= SK_Fixed1) // runtime check for numerical stability t = 0; // ignore the chop } } #endif if (t == 0) { memcpy(dst, src, 3 * sizeof(SkPoint)); return 1; } else { SkChopQuadAt(src, dst, t); return 2; } }
/* given a quad-curve and a point (x,y), chop the quad at that point and return the new quad's offCurve point. Should only return false if the computed pos is the start of the curve (i.e. root == 0) */ static bool quad_pt2OffCurve(const SkPoint quad[3], SkScalar x, SkScalar y, SkPoint* offCurve) { const SkScalar* base; SkScalar value; if (SkScalarAbs(x) < SkScalarAbs(y)) { base = &quad[0].fX; value = x; } else { base = &quad[0].fY; value = y; } // note: this returns 0 if it thinks value is out of range, meaning the // root might return something outside of [0, 1) SkScalar t = quad_solve(base[0], base[2], base[4], value); if (t > 0) { SkPoint tmp[5]; SkChopQuadAt(quad, tmp, t); *offCurve = tmp[1]; return true; } else { /* t == 0 means either the value triggered a root outside of [0, 1) For our purposes, we can ignore the <= 0 roots, but we want to catch the >= 1 roots (which given our caller, will basically mean a root of 1, give-or-take numerical instability). If we are in the >= 1 case, return the existing offCurve point. The test below checks to see if we are close to the "end" of the curve (near base[4]). Rather than specifying a tolerance, I just check to see if value is on to the right/left of the middle point (depending on the direction/sign of the end points). */ if ((base[0] < base[4] && value > base[2]) || (base[0] > base[4] && value < base[2])) // should root have been 1 { *offCurve = quad[1]; return true; } } return false; }
/* Returns 0 for 1 quad, and 1 for two quads, either way the answer is stored in dst[]. Guarantees that the 1/2 quads will be monotonic. */ int SkChopQuadAtYExtrema(const SkPoint src[3], SkPoint dst[5]) { SkASSERT(src); SkASSERT(dst); #if 0 static bool once = true; if (once) { once = false; SkPoint s[3] = { 0, 26398, 0, 26331, 0, 20621428 }; SkPoint d[6]; int n = SkChopQuadAtYExtrema(s, d); SkDebugf("chop=%d, Y=[%x %x %x %x %x %x]\n", n, d[0].fY, d[1].fY, d[2].fY, d[3].fY, d[4].fY, d[5].fY); } #endif SkScalar a = src[0].fY; SkScalar b = src[1].fY; SkScalar c = src[2].fY; if (is_not_monotonic(a, b, c)) { SkScalar tValue; if (valid_unit_divide(a - b, a - b - b + c, &tValue)) { SkChopQuadAt(src, dst, tValue); flatten_double_quad_extrema(&dst[0].fY); return 1; } // if we get here, we need to force dst to be monotonic, even though // we couldn't compute a unit_divide value (probably underflow). b = SkScalarAbs(a - b) < SkScalarAbs(b - c) ? a : c; } dst[0].set(src[0].fX, a); dst[1].set(src[1].fX, b); dst[2].set(src[2].fX, c); return 0; }
// srcPts[] must be monotonic in X and Y void SkEdgeClipper::clipMonoQuad(const SkPoint srcPts[3], const SkRect& clip) { SkPoint pts[3]; bool reverse = sort_increasing_Y(pts, srcPts, 3); // are we completely above or below if (pts[2].fY <= clip.fTop || pts[0].fY >= clip.fBottom) { return; } // Now chop so that pts is contained within clip in Y chop_quad_in_Y(pts, clip); if (pts[0].fX > pts[2].fX) { SkTSwap<SkPoint>(pts[0], pts[2]); reverse = !reverse; } SkASSERT(pts[0].fX <= pts[1].fX); SkASSERT(pts[1].fX <= pts[2].fX); // Now chop in X has needed, and record the segments if (pts[2].fX <= clip.fLeft) { // wholly to the left this->appendVLine(clip.fLeft, pts[0].fY, pts[2].fY, reverse); return; } if (pts[0].fX >= clip.fRight) { // wholly to the right this->appendVLine(clip.fRight, pts[0].fY, pts[2].fY, reverse); return; } SkScalar t; SkPoint tmp[5]; // for SkChopQuadAt // are we partially to the left if (pts[0].fX < clip.fLeft) { if (chopMonoQuadAtX(pts, clip.fLeft, &t)) { SkChopQuadAt(pts, tmp, t); this->appendVLine(clip.fLeft, tmp[0].fY, tmp[2].fY, reverse); clamp_ge(tmp[2].fX, clip.fLeft); clamp_ge(tmp[3].fX, clip.fLeft); pts[0] = tmp[2]; pts[1] = tmp[3]; } else { // if chopMonoQuadAtY failed, then we may have hit inexact numerics // so we just clamp against the left this->appendVLine(clip.fLeft, pts[0].fY, pts[2].fY, reverse); return; } } // are we partially to the right if (pts[2].fX > clip.fRight) { if (chopMonoQuadAtX(pts, clip.fRight, &t)) { SkChopQuadAt(pts, tmp, t); clamp_le(tmp[1].fX, clip.fRight); clamp_le(tmp[2].fX, clip.fRight); this->appendQuad(tmp, reverse); this->appendVLine(clip.fRight, tmp[2].fY, tmp[4].fY, reverse); } else { // if chopMonoQuadAtY failed, then we may have hit inexact numerics // so we just clamp against the right this->appendVLine(clip.fRight, pts[0].fY, pts[2].fY, reverse); } } else { // wholly inside the clip this->appendQuad(pts, reverse); } }
void SkChopQuadAtHalf(const SkPoint src[3], SkPoint dst[5]) { SkChopQuadAt(src, dst, 0.5f); return; }