Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(165)

Unified Diff: src/utils/SkCurveMeasure.cpp

Issue 2187083002: Add initial CurveMeasure code (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: Include <algorithm> for overloaded min/max Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « src/utils/SkCurveMeasure.h ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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);
+}
« no previous file with comments | « src/utils/SkCurveMeasure.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698