diff options
author | Chris Dalton <csmartdalton@google.com> | 2017-08-28 11:29:49 -0600 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2017-08-28 17:48:38 +0000 |
commit | 695db408437807d049ecc02b3b852704092728f2 (patch) | |
tree | 12e194152eba10f5011c489ea6f97c033962db48 | |
parent | d6e12862f08e4fb6491f350d01e24bc907817569 (diff) |
Add GrPathUtils::calcCubicInverseTransposePowerBasisMatrix
Cleans up calc_inverse_transpose_power_basis_matrix and makes it
publicly visible.
Bug: skia:
Change-Id: I568c2b8518c74931962ece23ed07490e7e10e94b
Reviewed-on: https://skia-review.googlesource.com/39180
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Greg Daniel <egdaniel@google.com>
-rw-r--r-- | src/gpu/GrPathUtils.cpp | 269 | ||||
-rw-r--r-- | src/gpu/GrPathUtils.h | 36 |
2 files changed, 139 insertions, 166 deletions
diff --git a/src/gpu/GrPathUtils.cpp b/src/gpu/GrPathUtils.cpp index a8abf81a63..f64affb396 100644 --- a/src/gpu/GrPathUtils.cpp +++ b/src/gpu/GrPathUtils.cpp @@ -569,28 +569,13 @@ void GrPathUtils::convertCubicToQuadsConstrainToTangents(const SkPoint p[4], //////////////////////////////////////////////////////////////////////////////// -/** - * Computes an SkMatrix that can find the cubic KLM functionals as follows: - * - * | ..K.. | | ..kcoeffs.. | - * | ..L.. | = | ..lcoeffs.. | * inverse_transpose_power_basis_matrix - * | ..M.. | | ..mcoeffs.. | - * - * 'kcoeffs' are the power basis coefficients to a scalar valued cubic function that returns the - * signed distance to line K from a given point on the curve: - * - * k(t,s) = C(t,s) * K [C(t,s) is defined in the following comment] - * - * The same applies for lcoeffs and mcoeffs. These are found separately, depending on the type of - * curve. There are 4 coefficients but 3 rows in the matrix, so in order to do this calculation the - * caller must first remove a specific column of coefficients. - * - * @return which column of klm coefficients to exclude from the calculation. - */ -static int calc_inverse_transpose_power_basis_matrix(const SkPoint pts[4], SkMatrix* out) { - using SkScalar4 = SkNx<4, SkScalar>; +using ExcludedTerm = GrPathUtils::ExcludedTerm; + +ExcludedTerm GrPathUtils::calcCubicInverseTransposePowerBasisMatrix(const SkPoint p[4], + SkMatrix* out) { + GR_STATIC_ASSERT(SK_SCALAR_IS_FLOAT); - // First we convert the bezier coordinates 'pts' to power basis coefficients X,Y,W=[0 0 0 1]. + // First convert the bezier coordinates p[0..3] to power basis coefficients X,Y(,W=[0 0 0 1]). // M3 is the matrix that does this conversion. The homogeneous equation for the cubic becomes: // // | X Y 0 | @@ -598,30 +583,32 @@ static int calc_inverse_transpose_power_basis_matrix(const SkPoint pts[4], SkMat // | . . 0 | // | . . 1 | // - const SkScalar4 M3[3] = {SkScalar4(-1, 3, -3, 1), - SkScalar4(3, -6, 3, 0), - SkScalar4(-3, 3, 0, 0)}; - // 4th column of M3 = SkScalar4(1, 0, 0, 0)}; - SkScalar4 X(pts[3].x(), 0, 0, 0); - SkScalar4 Y(pts[3].y(), 0, 0, 0); + const Sk4f M3[3] = {Sk4f(-1, 3, -3, 1), + Sk4f(3, -6, 3, 0), + Sk4f(-3, 3, 0, 0)}; + // 4th col of M3 = Sk4f(1, 0, 0, 0)}; + Sk4f X(p[3].x(), 0, 0, 0); + Sk4f Y(p[3].y(), 0, 0, 0); for (int i = 2; i >= 0; --i) { - X += M3[i] * pts[i].x(); - Y += M3[i] * pts[i].y(); + X += M3[i] * p[i].x(); + Y += M3[i] * p[i].y(); } // The matrix is 3x4. In order to invert it, we first need to make it square by throwing out one - // of the top three rows. We toss the row that leaves us with the largest absolute determinant. - // Since the right column will be [0 0 1], the determinant reduces to x0*y1 - y0*x1. - SkScalar absDet[4]; - const SkScalar4 DETX1 = SkNx_shuffle<1,0,0,3>(X), DETY1 = SkNx_shuffle<1,0,0,3>(Y); - const SkScalar4 DETX2 = SkNx_shuffle<2,2,1,3>(X), DETY2 = SkNx_shuffle<2,2,1,3>(Y); - const SkScalar4 DET = DETX1 * DETY2 - DETY1 * DETX2; - DET.abs().store(absDet); - const int skipRow = absDet[0] > absDet[2] ? (absDet[0] > absDet[1] ? 0 : 1) - : (absDet[1] > absDet[2] ? 1 : 2); - const SkScalar rdet = 1 / DET[skipRow]; - const int row0 = (0 != skipRow) ? 0 : 1; - const int row1 = (2 == skipRow) ? 1 : 2; + // of the middle two rows. We toss the row that leaves us with the largest absolute determinant. + // Since the right column will be [0 0 1], the respective determinants reduce to x0*y2 - y0*x2 + // and x0*y1 - y0*x1. + SkScalar dets[4]; + Sk4f D = SkNx_shuffle<0,0,2,1>(X) * SkNx_shuffle<2,1,0,0>(Y); + D -= SkNx_shuffle<2,3,0,1>(D); + D.store(dets); + ExcludedTerm skipTerm = SkScalarAbs(dets[0]) > SkScalarAbs(dets[1]) ? + ExcludedTerm::kQuadraticTerm : ExcludedTerm::kLinearTerm; + SkScalar det = dets[ExcludedTerm::kQuadraticTerm == skipTerm ? 0 : 1]; + if (0 == det) { + return ExcludedTerm::kNonInvertible; + } + SkScalar rdet = 1 / det; // Compute the inverse-transpose of the power basis matrix with the 'skipRow'th row removed. // Since W=[0 0 0 1], it follows that our corresponding solution will be equal to: @@ -630,125 +617,52 @@ static int calc_inverse_transpose_power_basis_matrix(const SkPoint pts[4], SkMat // 1/det * | -y0 x0 -x0*y2 + y0*x2 | // | 0 0 det | // - const SkScalar4 R(rdet, rdet, rdet, 1); - X *= R; - Y *= R; - SkScalar x[4], y[4], z[4]; X.store(x); Y.store(y); (X * SkNx_shuffle<3,3,3,3>(Y) - Y * SkNx_shuffle<3,3,3,3>(X)).store(z); - out->setAll( y[row1], -x[row1], z[row1], - -y[row0], x[row0], -z[row0], - 0, 0, 1); + int middleRow = ExcludedTerm::kQuadraticTerm == skipTerm ? 2 : 1; + out->setAll( y[middleRow] * rdet, -x[middleRow] * rdet, z[middleRow] * rdet, + -y[0] * rdet, x[0] * rdet, -z[0] * rdet, + 0, 0, 1); - return skipRow; + return skipTerm; } -static void calc_serp_klm(const SkPoint pts[4], SkScalar tl, SkScalar sl, SkScalar tm, SkScalar sm, - SkMatrix* klm) { - SkMatrix CIT; - int skipCol = calc_inverse_transpose_power_basis_matrix(pts, &CIT); - - SkMatrix klmCoeffs; - int col = 0; - if (0 != skipCol) { - klmCoeffs[0] = 0; - klmCoeffs[3] = -sl * sl * sl; - klmCoeffs[6] = -sm * sm * sm; - ++col; - } - if (1 != skipCol) { - klmCoeffs[col + 0] = sl * sm; - klmCoeffs[col + 3] = 3 * sl * sl * tl; - klmCoeffs[col + 6] = 3 * sm * sm * tm; - ++col; - } - if (2 != skipCol) { - klmCoeffs[col + 0] = -tl * sm - tm * sl; - klmCoeffs[col + 3] = -3 * sl * tl * tl; - klmCoeffs[col + 6] = -3 * sm * tm * tm; - ++col; - } - - SkASSERT(2 == col); - klmCoeffs[2] = tl * tm; - klmCoeffs[5] = tl * tl * tl; - klmCoeffs[8] = tm * tm * tm; - - klm->setConcat(klmCoeffs, CIT); +inline static void calc_serp_kcoeffs(SkScalar tl, SkScalar sl, SkScalar tm, SkScalar sm, + ExcludedTerm skipTerm, SkScalar outCoeffs[3]) { + SkASSERT(ExcludedTerm::kQuadraticTerm == skipTerm || ExcludedTerm::kLinearTerm == skipTerm); + outCoeffs[0] = 0; + outCoeffs[1] = (ExcludedTerm::kLinearTerm == skipTerm) ? sl*sm : -tl*sm - tm*sl; + outCoeffs[2] = tl*tm; } -static void calc_loop_klm(const SkPoint pts[4], SkScalar td, SkScalar sd, SkScalar te, SkScalar se, - SkMatrix* klm) { - SkMatrix CIT; - int skipCol = calc_inverse_transpose_power_basis_matrix(pts, &CIT); - - const SkScalar tdse = td * se; - const SkScalar tesd = te * sd; - - SkMatrix klmCoeffs; - int col = 0; - if (0 != skipCol) { - klmCoeffs[0] = 0; - klmCoeffs[3] = -sd * sd * se; - klmCoeffs[6] = -se * se * sd; - ++col; - } - if (1 != skipCol) { - klmCoeffs[col + 0] = sd * se; - klmCoeffs[col + 3] = sd * (2 * tdse + tesd); - klmCoeffs[col + 6] = se * (2 * tesd + tdse); - ++col; - } - if (2 != skipCol) { - klmCoeffs[col + 0] = -tdse - tesd; - klmCoeffs[col + 3] = -td * (tdse + 2 * tesd); - klmCoeffs[col + 6] = -te * (tesd + 2 * tdse); - ++col; - } - - SkASSERT(2 == col); - klmCoeffs[2] = td * te; - klmCoeffs[5] = td * td * te; - klmCoeffs[8] = te * te * td; - - klm->setConcat(klmCoeffs, CIT); +inline static void calc_serp_lmcoeffs(SkScalar t, SkScalar s, ExcludedTerm skipTerm, + SkScalar outCoeffs[3]) { + SkASSERT(ExcludedTerm::kQuadraticTerm == skipTerm || ExcludedTerm::kLinearTerm == skipTerm); + outCoeffs[0] = -s*s*s; + outCoeffs[1] = (ExcludedTerm::kLinearTerm == skipTerm) ? 3*s*s*t : -3*s*t*t; + outCoeffs[2] = t*t*t; } -// For the case when we have a cusp at a parameter value of infinity (discr == 0, d1 == 0). -static void calc_inf_cusp_klm(const SkPoint pts[4], SkScalar tn, SkScalar sn, SkMatrix* klm) { - SkMatrix CIT; - int skipCol = calc_inverse_transpose_power_basis_matrix(pts, &CIT); - - SkMatrix klmCoeffs; - int col = 0; - if (0 != skipCol) { - klmCoeffs[0] = 0; - klmCoeffs[3] = -sn * sn * sn; - ++col; - } - if (1 != skipCol) { - klmCoeffs[col + 0] = 0; - klmCoeffs[col + 3] = 3 * sn * sn * tn; - ++col; - } - if (2 != skipCol) { - klmCoeffs[col + 0] = -sn; - klmCoeffs[col + 3] = -3 * sn * tn * tn; - ++col; - } - - SkASSERT(2 == col); - klmCoeffs[2] = tn; - klmCoeffs[5] = tn * tn * tn; - - klmCoeffs[6] = 0; - klmCoeffs[7] = 0; - klmCoeffs[8] = 1; +inline static void calc_loop_kcoeffs(SkScalar td, SkScalar sd, SkScalar te, SkScalar se, + SkScalar tdse, SkScalar tesd, ExcludedTerm skipTerm, + SkScalar outCoeffs[3]) { + SkASSERT(ExcludedTerm::kQuadraticTerm == skipTerm || ExcludedTerm::kLinearTerm == skipTerm); + outCoeffs[0] = 0; + outCoeffs[1] = (ExcludedTerm::kLinearTerm == skipTerm) ? sd*se : -tdse - tesd; + outCoeffs[2] = td*te; +} - klm->setConcat(klmCoeffs, CIT); +inline static void calc_loop_lmcoeffs(SkScalar t2, SkScalar s2, SkScalar t1, SkScalar s1, + SkScalar t2s1, SkScalar t1s2, ExcludedTerm skipTerm, + SkScalar outCoeffs[3]) { + SkASSERT(ExcludedTerm::kQuadraticTerm == skipTerm || ExcludedTerm::kLinearTerm == skipTerm); + outCoeffs[0] = -s2*s2*s1; + outCoeffs[1] = (ExcludedTerm::kLinearTerm == skipTerm) ? s2 * (2*t2s1 + t1s2) + : -t2 * (t2s1 + 2*t1s2); + outCoeffs[2] = t2*t2*t1; } // For the case when a cubic bezier is actually a quadratic. We duplicate k in l so that the @@ -797,35 +711,58 @@ static void calc_line_klm(const SkPoint pts[4], SkMatrix* klm) { -nx, -ny, k); } -SkCubicType GrPathUtils::getCubicKLM(const SkPoint src[4], SkMatrix* klm, double t[2], - double s[2]) { +SkCubicType GrPathUtils::getCubicKLM(const SkPoint src[4], SkMatrix* klm, double tt[2], + double ss[2]) { double d[4]; - SkCubicType type = SkClassifyCubic(src, t, s, d); + SkCubicType type = SkClassifyCubic(src, tt, ss, d); - const SkScalar tt[2] = {static_cast<SkScalar>(t[0]), static_cast<SkScalar>(t[1])}; - const SkScalar ss[2] = {static_cast<SkScalar>(s[0]), static_cast<SkScalar>(s[1])}; + if (SkCubicType::kLineOrPoint == type) { + calc_line_klm(src, klm); + return SkCubicType::kLineOrPoint; + } + if (SkCubicType::kQuadratic == type) { + calc_quadratic_klm(src, d[3], klm); + return SkCubicType::kQuadratic; + } + + SkMatrix CIT; + ExcludedTerm skipTerm = calcCubicInverseTransposePowerBasisMatrix(src, &CIT); + if (ExcludedTerm::kNonInvertible == skipTerm) { + // This could technically also happen if the curve were quadratic, but SkClassifyCubic + // should have detected that case already with tolerance. + calc_line_klm(src, klm); + return SkCubicType::kLineOrPoint; + } + + const SkScalar t0 = static_cast<SkScalar>(tt[0]), t1 = static_cast<SkScalar>(tt[1]), + s0 = static_cast<SkScalar>(ss[0]), s1 = static_cast<SkScalar>(ss[1]); + + SkMatrix klmCoeffs; switch (type) { - case SkCubicType::kSerpentine: - calc_serp_klm(src, tt[0], ss[0], tt[1], ss[1], klm); - break; - case SkCubicType::kLoop: - calc_loop_klm(src, tt[0], ss[0], tt[1], ss[1], klm); - break; - case SkCubicType::kLocalCusp: - calc_serp_klm(src, tt[0], ss[0], tt[1], ss[1], klm); - break; case SkCubicType::kCuspAtInfinity: - calc_inf_cusp_klm(src, tt[0], ss[0], klm); + SkASSERT(1 == t1 && 0 == s1); // Infinity. + // fallthru. + case SkCubicType::kLocalCusp: + case SkCubicType::kSerpentine: + calc_serp_kcoeffs(t0, s0, t1, s1, skipTerm, &klmCoeffs[0]); + calc_serp_lmcoeffs(t0, s0, skipTerm, &klmCoeffs[3]); + calc_serp_lmcoeffs(t1, s1, skipTerm, &klmCoeffs[6]); break; - case SkCubicType::kQuadratic: - calc_quadratic_klm(src, d[3], klm); + case SkCubicType::kLoop: { + const SkScalar tdse = t0 * s1; + const SkScalar tesd = t1 * s0; + calc_loop_kcoeffs(t0, s0, t1, s1, tdse, tesd, skipTerm, &klmCoeffs[0]); + calc_loop_lmcoeffs(t0, s0, t1, s1, tdse, tesd, skipTerm, &klmCoeffs[3]); + calc_loop_lmcoeffs(t1, s1, t0, s0, tesd, tdse, skipTerm, &klmCoeffs[6]); break; - case SkCubicType::kLineOrPoint: - calc_line_klm(src, klm); + } + default: + SK_ABORT("Unexpected cubic type."); break; } + klm->setConcat(klmCoeffs, CIT); return type; } diff --git a/src/gpu/GrPathUtils.h b/src/gpu/GrPathUtils.h index e9dee7316a..4d49ab4dbc 100644 --- a/src/gpu/GrPathUtils.h +++ b/src/gpu/GrPathUtils.h @@ -124,6 +124,42 @@ namespace GrPathUtils { SkPathPriv::FirstDirection dir, SkTArray<SkPoint, true>* quads); + enum class ExcludedTerm { + kNonInvertible, + kQuadraticTerm, + kLinearTerm + }; + + // Computes the inverse-transpose of the cubic's power basis matrix, after removing a specific + // row of coefficients. + // + // E.g. if the cubic is defined in power basis form as follows: + // + // | x3 y3 0 | + // C(t,s) = [t^3 t^2*s t*s^2 s^3] * | x2 y2 0 | + // | x1 y1 0 | + // | x0 y0 1 | + // + // And the excluded term is "kQuadraticTerm", then the resulting inverse-transpose will be: + // + // | x3 y3 0 | -1 T + // | x1 y1 0 | + // | x0 y0 1 | + // + // (The term to exclude is chosen based on maximizing the resulting matrix determinant.) + // + // This can be used to find the KLM linear functionals: + // + // | ..K.. | | ..kcoeffs.. | + // | ..L.. | = | ..lcoeffs.. | * inverse_transpose_power_basis_matrix + // | ..M.. | | ..mcoeffs.. | + // + // NOTE: the same term that was excluded here must also be removed from the corresponding column + // of the klmcoeffs matrix. + // + // Returns which row of coefficients was removed, or kNonInvertible if the cubic was degenerate. + ExcludedTerm calcCubicInverseTransposePowerBasisMatrix(const SkPoint p[4], SkMatrix* out); + // Computes the KLM linear functionals for the cubic implicit form. The "right" side of the // curve (when facing in the direction of increasing parameter values) will be the area that // satisfies: |