| Index: src/utils/SkCurveMeasure.cpp
|
| diff --git a/src/utils/SkCurveMeasure.cpp b/src/utils/SkCurveMeasure.cpp
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..785bca7cfd15f5c118348249be2f1e0f879c3ea6
|
| --- /dev/null
|
| +++ b/src/utils/SkCurveMeasure.cpp
|
| @@ -0,0 +1,283 @@
|
| +/*
|
| + * Copyright 2016 Google Inc.
|
| + *
|
| + * Use of this source code is governed by a BSD-style license that can be
|
| + * found in the LICENSE file.
|
| + */
|
| +
|
| +#include "SkCurveMeasure.h"
|
| +
|
| +// for abs
|
| +#include <cmath>
|
| +
|
| +static inline Sk8f evaluateDerivativeLength(const Sk8f& ts,
|
| + const Sk8f (&xCoeff)[3],
|
| + const Sk8f (&yCoeff)[3],
|
| + const SkSegType segType) {
|
| + Sk8f x;
|
| + Sk8f y;
|
| + switch (segType) {
|
| + case kQuad_SegType:
|
| + x = xCoeff[0]*ts + xCoeff[1];
|
| + y = yCoeff[0]*ts + yCoeff[1];
|
| + break;
|
| + case kLine_SegType:
|
| + SkDebugf("Unimplemented");
|
| + break;
|
| + case kCubic_SegType:
|
| + x = (xCoeff[0]*ts + xCoeff[1])*ts + xCoeff[2];
|
| + y = (yCoeff[0]*ts + yCoeff[1])*ts + yCoeff[2];
|
| + break;
|
| + case kConic_SegType:
|
| + SkDebugf("Unimplemented");
|
| + break;
|
| + default:
|
| + SkDebugf("Unimplemented");
|
| + }
|
| +
|
| + x = x * x;
|
| + y = y * y;
|
| +
|
| + return (x + y).sqrt();
|
| +}
|
| +ArcLengthIntegrator::ArcLengthIntegrator(const SkPoint* pts, SkSegType segType)
|
| + : fSegType(segType) {
|
| + switch (fSegType) {
|
| + case kQuad_SegType: {
|
| + float Ax = pts[0].x();
|
| + float Bx = pts[1].x();
|
| + float Cx = pts[2].x();
|
| + float Ay = pts[0].y();
|
| + float By = pts[1].y();
|
| + float Cy = pts[2].y();
|
| +
|
| + // precompute coefficients for derivative
|
| + xCoeff[0] = Sk8f(2.0f*(Ax - 2*Bx + Cx));
|
| + xCoeff[1] = Sk8f(2.0f*(Bx - Ax));
|
| +
|
| + yCoeff[0] = Sk8f(2.0f*(Ay - 2*By + Cy));
|
| + yCoeff[1] = Sk8f(2.0f*(By - Ay));
|
| + }
|
| + break;
|
| + case kLine_SegType:
|
| + SkDEBUGF(("Unimplemented"));
|
| + break;
|
| + case kCubic_SegType:
|
| + {
|
| + float Ax = pts[0].x();
|
| + float Bx = pts[1].x();
|
| + float Cx = pts[2].x();
|
| + float Dx = pts[3].x();
|
| + float Ay = pts[0].y();
|
| + float By = pts[1].y();
|
| + float Cy = pts[2].y();
|
| + float Dy = pts[3].y();
|
| +
|
| + xCoeff[0] = Sk8f(3.0f*(-Ax + 3.0f*(Bx - Cx) + Dx));
|
| + xCoeff[1] = Sk8f(3.0f*(2.0f*(Ax - 2.0f*Bx + Cx)));
|
| + xCoeff[2] = Sk8f(3.0f*(-Ax + Bx));
|
| +
|
| + yCoeff[0] = Sk8f(3.0f*(-Ay + 3.0f*(By - Cy) + Dy));
|
| + yCoeff[1] = Sk8f(3.0f * -Ay + By + 2.0f*(Ay - 2.0f*By + Cy));
|
| + yCoeff[2] = Sk8f(3.0f*(-Ay + By));
|
| + }
|
| + break;
|
| + case kConic_SegType:
|
| + SkDEBUGF(("Unimplemented"));
|
| + break;
|
| + default:
|
| + SkDEBUGF(("Unimplemented"));
|
| + }
|
| +}
|
| +
|
| +// We use Gaussian quadrature
|
| +// (https://en.wikipedia.org/wiki/Gaussian_quadrature)
|
| +// to approximate the arc length integral here, because it is amenable to SIMD.
|
| +SkScalar ArcLengthIntegrator::computeLength(SkScalar t) {
|
| + SkScalar length = 0.0f;
|
| +
|
| + Sk8f lengths = evaluateDerivativeLength(absc*t, xCoeff, yCoeff, fSegType);
|
| + lengths = weights*lengths;
|
| + // is it faster or more accurate to sum and then multiply or vice versa?
|
| + lengths = lengths*(t*0.5f);
|
| +
|
| + // Why does SkNx index with ints? does negative index mean something?
|
| + for (int i = 0; i < 8; i++) {
|
| + length += lengths[i];
|
| + }
|
| + return length;
|
| +}
|
| +
|
| +SkCurveMeasure::SkCurveMeasure(const SkPoint* pts, SkSegType segType)
|
| + : fSegType(segType) {
|
| + switch (fSegType) {
|
| + case SkSegType::kQuad_SegType:
|
| + for (size_t i = 0; i < 3; i++) {
|
| + fPts[i] = pts[i];
|
| + }
|
| + break;
|
| + case SkSegType::kLine_SegType:
|
| + SkDebugf("Unimplemented");
|
| + break;
|
| + case SkSegType::kCubic_SegType:
|
| + for (size_t i = 0; i < 4; i++) {
|
| + fPts[i] = pts[i];
|
| + }
|
| + break;
|
| + case SkSegType::kConic_SegType:
|
| + SkDebugf("Unimplemented");
|
| + break;
|
| + default:
|
| + SkDEBUGF(("Unimplemented"));
|
| + break;
|
| + }
|
| + fIntegrator = ArcLengthIntegrator(fPts, fSegType);
|
| +}
|
| +
|
| +SkScalar SkCurveMeasure::getLength() {
|
| + if (-1.0f == fLength) {
|
| + fLength = fIntegrator.computeLength(1.0f);
|
| + }
|
| + return fLength;
|
| +}
|
| +
|
| +// Given an arc length targetLength, we want to determine what t
|
| +// gives us the corresponding arc length along the curve.
|
| +// We do this by letting the arc length integral := f(t) and
|
| +// solving for the root of the equation f(t) - targetLength = 0
|
| +// using Newton's method and lerp-bisection.
|
| +// The computationally expensive parts are the integral approximation
|
| +// at each step, and computing the derivative of the arc length integral,
|
| +// which is equal to the length of the tangent (so we have to do a sqrt).
|
| +
|
| +SkScalar SkCurveMeasure::getTime(SkScalar targetLength) {
|
| + if (targetLength == 0.0f) {
|
| + return 0.0f;
|
| + }
|
| +
|
| + SkScalar currentLength = getLength();
|
| +
|
| + if (SkScalarNearlyEqual(targetLength, currentLength)) {
|
| + return 1.0f;
|
| + }
|
| +
|
| + // initial estimate of t is percentage of total length
|
| + SkScalar currentT = targetLength / currentLength;
|
| + SkScalar prevT = -1.0f;
|
| + SkScalar newT;
|
| +
|
| + SkScalar minT = 0.0f;
|
| + SkScalar maxT = 1.0f;
|
| +
|
| + int iterations = 0;
|
| + while (iterations < kNewtonIters + kBisectIters) {
|
| + currentLength = fIntegrator.computeLength(currentT);
|
| + SkScalar lengthDiff = currentLength - targetLength;
|
| +
|
| + // Update root bounds.
|
| + // If lengthDiff is positive, we have overshot the target, so
|
| + // we know the current t is an upper bound, and similarly
|
| + // for the lower bound.
|
| + if (lengthDiff > 0.0f) {
|
| + if (currentT < maxT) {
|
| + maxT = currentT;
|
| + }
|
| + } else {
|
| + if (currentT > minT) {
|
| + minT = currentT;
|
| + }
|
| + }
|
| +
|
| + // We have a tolerance on both the absolute value of the difference and
|
| + // on the t value
|
| + // because we may not have enough precision in the t to get close enough
|
| + // in the length.
|
| + if ((std::abs(lengthDiff) < kTolerance) ||
|
| + (std::abs(prevT - currentT) < kTolerance)) {
|
| + break;
|
| + }
|
| +
|
| + prevT = currentT;
|
| + if (iterations < kNewtonIters) {
|
| + // TODO(hstern) switch here on curve type.
|
| + // This is just newton's formula.
|
| + SkScalar dt = evaluateQuadDerivative(currentT).length();
|
| + newT = currentT - (lengthDiff / dt);
|
| +
|
| + // If newT is out of bounds, bisect inside newton.
|
| + if ((newT < 0.0f) || (newT > 1.0f)) {
|
| + newT = (minT + maxT) * 0.5f;
|
| + }
|
| + } else if (iterations < kNewtonIters + kBisectIters) {
|
| + if (lengthDiff > 0.0f) {
|
| + maxT = currentT;
|
| + } else {
|
| + minT = currentT;
|
| + }
|
| + // TODO(hstern) do a lerp here instead of a bisection
|
| + newT = (minT + maxT) * 0.5f;
|
| + } else {
|
| + SkDEBUGF(("%.7f %.7f didn't get close enough after bisection.\n",
|
| + currentT, currentLength));
|
| + break;
|
| + }
|
| + currentT = newT;
|
| +
|
| + SkASSERT(minT <= maxT);
|
| +
|
| + iterations++;
|
| + }
|
| +
|
| + // debug. is there an SKDEBUG or something for ifdefs?
|
| + fIters = iterations;
|
| +
|
| + return currentT;
|
| +}
|
| +
|
| +void SkCurveMeasure::getPosTan(SkScalar targetLength, SkPoint* pos,
|
| + SkVector* tan) {
|
| + SkScalar t = getTime(targetLength);
|
| +
|
| + if (pos) {
|
| + // TODO(hstern) switch here on curve type.
|
| + *pos = evaluateQuad(t);
|
| + }
|
| + if (tan) {
|
| + // TODO(hstern) switch here on curve type.
|
| + *tan = evaluateQuadDerivative(t);
|
| + }
|
| +}
|
| +
|
| +// this is why I feel that the ArcLengthIntegrator should be combined
|
| +// with some sort of evaluator that caches the constants computed from the
|
| +// control points. this is basically the same code in ArcLengthIntegrator
|
| +SkPoint SkCurveMeasure::evaluateQuad(SkScalar t) {
|
| + SkScalar ti = 1.0f - t;
|
| +
|
| + SkScalar Ax = fPts[0].x();
|
| + SkScalar Bx = fPts[1].x();
|
| + SkScalar Cx = fPts[2].x();
|
| + SkScalar Ay = fPts[0].y();
|
| + SkScalar By = fPts[1].y();
|
| + SkScalar Cy = fPts[2].y();
|
| +
|
| + SkScalar x = Ax*ti*ti + 2.0f*Bx*t*ti + Cx*t*t;
|
| + SkScalar y = Ay*ti*ti + 2.0f*By*t*ti + Cy*t*t;
|
| + return SkPoint::Make(x, y);
|
| +}
|
| +
|
| +SkVector SkCurveMeasure::evaluateQuadDerivative(SkScalar t) {
|
| + SkScalar Ax = fPts[0].x();
|
| + SkScalar Bx = fPts[1].x();
|
| + SkScalar Cx = fPts[2].x();
|
| + SkScalar Ay = fPts[0].y();
|
| + SkScalar By = fPts[1].y();
|
| + SkScalar Cy = fPts[2].y();
|
| +
|
| + SkScalar A2BCx = 2.0f*(Ax - 2*Bx + Cx);
|
| + SkScalar A2BCy = 2.0f*(Ay - 2*By + Cy);
|
| + SkScalar ABx = 2.0f*(Bx - Ax);
|
| + SkScalar ABy = 2.0f*(By - Ay);
|
| +
|
| + return SkPoint::Make(A2BCx*t + ABx, A2BCy*t + ABy);
|
| +}
|
|
|