Find cubic KLM functionals directly
- Updates GrPathUtils to computes the KLM functionals directly instead of deriving them from their explicit values at the control points. - Updates the utility to return these functionals as a matrix rather than an array of scalar values. - Adds a benchmark for chopCubicAtLoopIntersection. BUG=skia: Change-Id: I97a9b5cf610d33e15c9af96b9d9a8eb4a94b1ca7 Reviewed-on: https://skia-review.googlesource.com/9951 Commit-Queue: Chris Dalton <csmartdalton@google.com> Reviewed-by: Greg Daniel <egdaniel@google.com>
This commit is contained in:
parent
f160ad4d76
commit
cc26127920
68
bench/CubicKLMBench.cpp
Normal file
68
bench/CubicKLMBench.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "Benchmark.h"
|
||||
|
||||
#if SK_SUPPORT_GPU
|
||||
|
||||
#include "GrPathUtils.h"
|
||||
#include "SkGeometry.h"
|
||||
|
||||
class CubicKLMBench : public Benchmark {
|
||||
public:
|
||||
CubicKLMBench(SkScalar x0, SkScalar y0, SkScalar x1, SkScalar y1,
|
||||
SkScalar x2, SkScalar y2, SkScalar x3, SkScalar y3) {
|
||||
fPoints[0].set(x0, y0);
|
||||
fPoints[1].set(x1, y1);
|
||||
fPoints[2].set(x2, y2);
|
||||
fPoints[3].set(x3, y3);
|
||||
|
||||
fName = "cubic_klm_";
|
||||
SkScalar d[3];
|
||||
switch (SkClassifyCubic(fPoints, d)) {
|
||||
case kSerpentine_SkCubicType:
|
||||
fName.append("serp");
|
||||
break;
|
||||
case kLoop_SkCubicType:
|
||||
fName.append("loop");
|
||||
break;
|
||||
default:
|
||||
SkFAIL("Unexpected cubic type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool isSuitableFor(Backend backend) override {
|
||||
return backend == kNonRendering_Backend;
|
||||
}
|
||||
|
||||
const char* onGetName() override {
|
||||
return fName.c_str();
|
||||
}
|
||||
|
||||
void onDraw(int loops, SkCanvas*) override {
|
||||
SkPoint dst[10];
|
||||
SkMatrix klm;
|
||||
int loopIdx;
|
||||
for (int i = 0; i < loops * 50000; ++i) {
|
||||
GrPathUtils::chopCubicAtLoopIntersection(fPoints, dst, &klm, &loopIdx);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
SkPoint fPoints[4];
|
||||
SkString fName;
|
||||
|
||||
typedef Benchmark INHERITED;
|
||||
};
|
||||
|
||||
DEF_BENCH( return new CubicKLMBench(285.625f, 499.687f, 411.625f, 808.188f,
|
||||
1064.62f, 135.688f, 1042.63f, 585.187f); )
|
||||
DEF_BENCH( return new CubicKLMBench(635.625f, 614.687f, 171.625f, 236.188f,
|
||||
1064.62f, 135.688f, 516.625f, 570.187f); )
|
||||
|
||||
#endif
|
@ -22,10 +22,6 @@
|
||||
|
||||
#include "effects/GrBezierEffect.h"
|
||||
|
||||
static inline SkScalar eval_line(const SkPoint& p, const SkScalar lineEq[3], SkScalar sign) {
|
||||
return sign * (lineEq[0] * p.fX + lineEq[1] * p.fY + lineEq[2]);
|
||||
}
|
||||
|
||||
namespace skiagm {
|
||||
|
||||
class BezierCubicOrConicTestOp : public GrTestMeshDrawOp {
|
||||
@ -35,20 +31,21 @@ public:
|
||||
const char* name() const override { return "BezierCubicOrConicTestOp"; }
|
||||
|
||||
static std::unique_ptr<GrMeshDrawOp> Make(sk_sp<GrGeometryProcessor> gp, const SkRect& rect,
|
||||
GrColor color, const SkScalar klmEqs[9],
|
||||
SkScalar sign) {
|
||||
GrColor color, const SkMatrix& klm, SkScalar sign) {
|
||||
return std::unique_ptr<GrMeshDrawOp>(
|
||||
new BezierCubicOrConicTestOp(gp, rect, color, klmEqs, sign));
|
||||
new BezierCubicOrConicTestOp(gp, rect, color, klm, sign));
|
||||
}
|
||||
|
||||
private:
|
||||
BezierCubicOrConicTestOp(sk_sp<GrGeometryProcessor> gp, const SkRect& rect, GrColor color,
|
||||
const SkScalar klmEqs[9], SkScalar sign)
|
||||
: INHERITED(ClassID(), rect, color), fRect(rect), fGeometryProcessor(std::move(gp)) {
|
||||
for (int i = 0; i < 9; i++) {
|
||||
fKlmEqs[i] = klmEqs[i];
|
||||
const SkMatrix& klm, SkScalar sign)
|
||||
: INHERITED(ClassID(), rect, color)
|
||||
, fKLM(klm)
|
||||
, fRect(rect)
|
||||
, fGeometryProcessor(std::move(gp)) {
|
||||
if (1 != sign) {
|
||||
fKLM.postScale(sign, sign);
|
||||
}
|
||||
fSign = sign;
|
||||
}
|
||||
struct Vertex {
|
||||
SkPoint fPosition;
|
||||
@ -66,15 +63,13 @@ private:
|
||||
verts[0].fPosition.setRectFan(fRect.fLeft, fRect.fTop, fRect.fRight, fRect.fBottom,
|
||||
sizeof(Vertex));
|
||||
for (int v = 0; v < 4; ++v) {
|
||||
verts[v].fKLM[0] = eval_line(verts[v].fPosition, fKlmEqs + 0, fSign);
|
||||
verts[v].fKLM[1] = eval_line(verts[v].fPosition, fKlmEqs + 3, fSign);
|
||||
verts[v].fKLM[2] = eval_line(verts[v].fPosition, fKlmEqs + 6, 1.f);
|
||||
SkScalar pt3[3] = {verts[v].fPosition.x(), verts[v].fPosition.y(), 1.f};
|
||||
fKLM.mapHomogeneousPoints(verts[v].fKLM, pt3, 1);
|
||||
}
|
||||
helper.recordDraw(target, fGeometryProcessor.get());
|
||||
}
|
||||
|
||||
SkScalar fKlmEqs[9];
|
||||
SkScalar fSign;
|
||||
SkMatrix fKLM;
|
||||
SkRect fRect;
|
||||
sk_sp<GrGeometryProcessor> fGeometryProcessor;
|
||||
|
||||
@ -155,11 +150,11 @@ protected:
|
||||
{x + baseControlPts[3].fX, y + baseControlPts[3].fY}
|
||||
};
|
||||
SkPoint chopped[10];
|
||||
SkScalar klmEqs[9];
|
||||
SkMatrix klm;
|
||||
int loopIndex;
|
||||
int cnt = GrPathUtils::chopCubicAtLoopIntersection(controlPts,
|
||||
chopped,
|
||||
klmEqs,
|
||||
&klm,
|
||||
&loopIndex);
|
||||
|
||||
SkPaint ctrlPtPaint;
|
||||
@ -203,7 +198,7 @@ protected:
|
||||
}
|
||||
|
||||
std::unique_ptr<GrMeshDrawOp> op =
|
||||
BezierCubicOrConicTestOp::Make(gp, bounds, color, klmEqs, sign);
|
||||
BezierCubicOrConicTestOp::Make(gp, bounds, color, klm, sign);
|
||||
|
||||
renderTargetContext->priv().testingOnly_addMeshDrawOp(
|
||||
std::move(grPaint), GrAAType::kNone, std::move(op));
|
||||
@ -296,9 +291,9 @@ protected:
|
||||
{x + baseControlPts[2].fX, y + baseControlPts[2].fY}
|
||||
};
|
||||
SkConic dst[4];
|
||||
SkScalar klmEqs[9];
|
||||
SkMatrix klm;
|
||||
int cnt = chop_conic(controlPts, dst, weight);
|
||||
GrPathUtils::getConicKLM(controlPts, weight, klmEqs);
|
||||
GrPathUtils::getConicKLM(controlPts, weight, &klm);
|
||||
|
||||
SkPaint ctrlPtPaint;
|
||||
ctrlPtPaint.setColor(rand.nextU() | 0xFF000000);
|
||||
@ -336,7 +331,7 @@ protected:
|
||||
grPaint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrc));
|
||||
|
||||
std::unique_ptr<GrMeshDrawOp> op =
|
||||
BezierCubicOrConicTestOp::Make(gp, bounds, color, klmEqs, 1.f);
|
||||
BezierCubicOrConicTestOp::Make(gp, bounds, color, klm, 1.f);
|
||||
|
||||
renderTargetContext->priv().testingOnly_addMeshDrawOp(
|
||||
std::move(grPaint), GrAAType::kNone, std::move(op));
|
||||
|
@ -34,6 +34,7 @@ bench_sources = [
|
||||
"$_bench/ColorPrivBench.cpp",
|
||||
"$_bench/ControlBench.cpp",
|
||||
"$_bench/CoverageBench.cpp",
|
||||
"$_bench/CubicKLMBench.cpp",
|
||||
"$_bench/DashBench.cpp",
|
||||
"$_bench/DisplacementBench.cpp",
|
||||
"$_bench/DrawBitmapAABench.cpp",
|
||||
|
@ -323,7 +323,8 @@ void GrPathUtils::QuadUVMatrix::set(const SkPoint qPts[3]) {
|
||||
// k = (y2 - y0, x0 - x2, x2*y0 - x0*y2)
|
||||
// l = (y1 - y0, x0 - x1, x1*y0 - x0*y1) * 2*w
|
||||
// m = (y2 - y1, x1 - x2, x2*y1 - x1*y2) * 2*w
|
||||
void GrPathUtils::getConicKLM(const SkPoint p[3], const SkScalar weight, SkScalar klm[9]) {
|
||||
void GrPathUtils::getConicKLM(const SkPoint p[3], const SkScalar weight, SkMatrix* out) {
|
||||
SkMatrix& klm = *out;
|
||||
const SkScalar w2 = 2.f * weight;
|
||||
klm[0] = p[2].fY - p[0].fY;
|
||||
klm[1] = p[0].fX - p[2].fX;
|
||||
@ -575,157 +576,272 @@ void GrPathUtils::convertCubicToQuadsConstrainToTangents(const SkPoint p[4],
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Solves linear system to extract klm
|
||||
// P.K = k (similarly for l, m)
|
||||
// Where P is matrix of control points
|
||||
// K is coefficients for the line K
|
||||
// k is vector of values of K evaluated at the control points
|
||||
// Solving for K, thus K = P^(-1) . k
|
||||
static void calc_cubic_klm(const SkPoint p[4], const SkScalar controlK[4],
|
||||
const SkScalar controlL[4], const SkScalar controlM[4],
|
||||
SkScalar k[3], SkScalar l[3], SkScalar m[3]) {
|
||||
SkMatrix matrix;
|
||||
matrix.setAll(p[0].fX, p[0].fY, 1.f,
|
||||
p[1].fX, p[1].fY, 1.f,
|
||||
p[2].fX, p[2].fY, 1.f);
|
||||
SkMatrix inverse;
|
||||
if (matrix.invert(&inverse)) {
|
||||
inverse.mapHomogeneousPoints(k, controlK, 1);
|
||||
inverse.mapHomogeneousPoints(l, controlL, 1);
|
||||
inverse.mapHomogeneousPoints(m, controlM, 1);
|
||||
/**
|
||||
* 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>;
|
||||
|
||||
// First we convert the bezier coordinates 'pts' 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 |
|
||||
// C(t,s) = [t^3 t^2*s t*s^2 s^3] * | . . 0 |
|
||||
// | . . 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);
|
||||
for (int i = 2; i >= 0; --i) {
|
||||
X += M3[i] * pts[i].x();
|
||||
Y += M3[i] * pts[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 determinant. Since the
|
||||
// right column will be [0 0 1], the determinant reduces to x0*y1 - y0*x1.
|
||||
SkScalar det[4];
|
||||
SkScalar4 DETX1 = SkNx_shuffle<1,0,0,3>(X), DETY1 = SkNx_shuffle<1,0,0,3>(Y);
|
||||
SkScalar4 DETX2 = SkNx_shuffle<2,2,1,3>(X), DETY2 = SkNx_shuffle<2,2,1,3>(Y);
|
||||
(DETX1 * DETY2 - DETY1 * DETX2).store(det);
|
||||
const int skipRow = det[0] > det[2] ? (det[0] > det[1] ? 0 : 1)
|
||||
: (det[1] > det[2] ? 1 : 2);
|
||||
const SkScalar rdet = 1 / det[skipRow];
|
||||
const int row0 = (0 != skipRow) ? 0 : 1;
|
||||
const int row1 = (2 == skipRow) ? 1 : 2;
|
||||
|
||||
// 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:
|
||||
//
|
||||
// | y1 -x1 x1*y2 - y1*x2 |
|
||||
// 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);
|
||||
|
||||
return skipRow;
|
||||
}
|
||||
|
||||
static void set_serp_klm(const SkScalar d[3], SkScalar k[4], SkScalar l[4], SkScalar m[4]) {
|
||||
SkScalar tempSqrt = SkScalarSqrt(9.f * d[1] * d[1] - 12.f * d[0] * d[2]);
|
||||
SkScalar ls = 3.f * d[1] - tempSqrt;
|
||||
SkScalar lt = 6.f * d[0];
|
||||
SkScalar ms = 3.f * d[1] + tempSqrt;
|
||||
SkScalar mt = 6.f * d[0];
|
||||
static void negate_kl(SkMatrix* klm) {
|
||||
// We could use klm->postScale(-1, -1), but it ends up doing a full matrix multiply.
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
(*klm)[i] = -(*klm)[i];
|
||||
}
|
||||
}
|
||||
|
||||
k[0] = ls * ms;
|
||||
k[1] = (3.f * ls * ms - ls * mt - lt * ms) / 3.f;
|
||||
k[2] = (lt * (mt - 2.f * ms) + ls * (3.f * ms - 2.f * mt)) / 3.f;
|
||||
k[3] = (lt - ls) * (mt - ms);
|
||||
static void calc_serp_klm(const SkPoint pts[4], const SkScalar d[3], SkMatrix* klm) {
|
||||
SkMatrix CIT;
|
||||
int skipCol = calc_inverse_transpose_power_basis_matrix(pts, &CIT);
|
||||
|
||||
l[0] = ls * ls * ls;
|
||||
const SkScalar lt_ls = lt - ls;
|
||||
l[1] = ls * ls * lt_ls * -1.f;
|
||||
l[2] = lt_ls * lt_ls * ls;
|
||||
l[3] = -1.f * lt_ls * lt_ls * lt_ls;
|
||||
const SkScalar root = SkScalarSqrt(9 * d[1] * d[1] - 12 * d[0] * d[2]);
|
||||
|
||||
m[0] = ms * ms * ms;
|
||||
const SkScalar mt_ms = mt - ms;
|
||||
m[1] = ms * ms * mt_ms * -1.f;
|
||||
m[2] = mt_ms * mt_ms * ms;
|
||||
m[3] = -1.f * mt_ms * mt_ms * mt_ms;
|
||||
const SkScalar tl = 3 * d[1] + root;
|
||||
const SkScalar sl = 6 * d[0];
|
||||
const SkScalar tm = 3 * d[1] - root;
|
||||
const SkScalar sm = 6 * d[0];
|
||||
|
||||
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);
|
||||
|
||||
// If d0 > 0 we need to flip the orientation of our curve
|
||||
// This is done by negating the k and l values
|
||||
// We want negative distance values to be on the inside
|
||||
if ( d[0] > 0) {
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
k[i] = -k[i];
|
||||
l[i] = -l[i];
|
||||
}
|
||||
if (d[0] > 0) {
|
||||
negate_kl(klm);
|
||||
}
|
||||
}
|
||||
|
||||
static void set_loop_klm(const SkScalar d[3], SkScalar k[4], SkScalar l[4], SkScalar m[4]) {
|
||||
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];
|
||||
static void calc_loop_klm(const SkPoint pts[4], SkScalar d1, SkScalar td, SkScalar sd,
|
||||
SkScalar te, SkScalar se, SkMatrix* klm) {
|
||||
SkMatrix CIT;
|
||||
int skipCol = calc_inverse_transpose_power_basis_matrix(pts, &CIT);
|
||||
|
||||
k[0] = ls * ms;
|
||||
k[1] = (3.f * ls*ms - ls * mt - lt * ms) / 3.f;
|
||||
k[2] = (lt * (mt - 2.f * ms) + ls * (3.f * ms - 2.f * mt)) / 3.f;
|
||||
k[3] = (lt - ls) * (mt - ms);
|
||||
const SkScalar tesd = te * sd;
|
||||
const SkScalar tdse = td * se;
|
||||
|
||||
l[0] = ls * ls * ms;
|
||||
l[1] = (ls * (ls * (mt - 3.f * ms) + 2.f * lt * ms))/-3.f;
|
||||
l[2] = ((lt - ls) * (ls * (2.f * mt - 3.f * ms) + lt * ms))/3.f;
|
||||
l[3] = -1.f * (lt - ls) * (lt - ls) * (mt - ms);
|
||||
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;
|
||||
}
|
||||
|
||||
m[0] = ls * ms * ms;
|
||||
m[1] = (ms * (ls * (2.f * mt - 3.f * ms) + lt * ms))/-3.f;
|
||||
m[2] = ((mt - ms) * (ls * (mt - 3.f * ms) + 2.f * lt * ms))/3.f;
|
||||
m[3] = -1.f * (lt - ls) * (mt - ms) * (mt - ms);
|
||||
SkASSERT(2 == col);
|
||||
klmCoeffs[2] = td * te;
|
||||
klmCoeffs[5] = td * td * te;
|
||||
klmCoeffs[8] = te * te * td;
|
||||
|
||||
klm->setConcat(klmCoeffs, CIT);
|
||||
|
||||
// For the general loop curve, we flip the orientation in the same pattern as the serp case
|
||||
// above. Thus we only check d[0]. Technically we should check the value of the hessian as well
|
||||
// cause we care about the sign of d[0]*Hessian. However, the Hessian is always negative outside
|
||||
// above. Thus we only check d1. Technically we should check the value of the hessian as well
|
||||
// cause we care about the sign of d1*Hessian. However, the Hessian is always negative outside
|
||||
// the loop section and positive inside. We take care of the flipping for the loop sections
|
||||
// later on.
|
||||
if (d[0] > 0) {
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
k[i] = -k[i];
|
||||
l[i] = -l[i];
|
||||
}
|
||||
if (d1 > 0) {
|
||||
negate_kl(klm);
|
||||
}
|
||||
}
|
||||
|
||||
static void set_cusp_klm(const SkScalar d[3], SkScalar k[4], SkScalar l[4], SkScalar m[4]) {
|
||||
const SkScalar ls = d[2];
|
||||
const SkScalar lt = 3.f * d[1];
|
||||
// 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 d2, SkScalar d3, SkMatrix* klm) {
|
||||
SkMatrix CIT;
|
||||
int skipCol = calc_inverse_transpose_power_basis_matrix(pts, &CIT);
|
||||
|
||||
k[0] = ls;
|
||||
k[1] = ls - lt / 3.f;
|
||||
k[2] = ls - 2.f * lt / 3.f;
|
||||
k[3] = ls - lt;
|
||||
const SkScalar tn = d3;
|
||||
const SkScalar sn = 3 * d2;
|
||||
|
||||
l[0] = ls * ls * ls;
|
||||
const SkScalar ls_lt = ls - lt;
|
||||
l[1] = ls * ls * ls_lt;
|
||||
l[2] = ls_lt * ls_lt * ls;
|
||||
l[3] = ls_lt * ls_lt * ls_lt;
|
||||
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;
|
||||
}
|
||||
|
||||
m[0] = 1.f;
|
||||
m[1] = 1.f;
|
||||
m[2] = 1.f;
|
||||
m[3] = 1.f;
|
||||
SkASSERT(2 == col);
|
||||
klmCoeffs[2] = tn;
|
||||
klmCoeffs[5] = tn * tn * tn;
|
||||
|
||||
klmCoeffs[6] = 0;
|
||||
klmCoeffs[7] = 0;
|
||||
klmCoeffs[8] = 1;
|
||||
|
||||
klm->setConcat(klmCoeffs, CIT);
|
||||
}
|
||||
|
||||
// For the case when a cubic is actually a quadratic
|
||||
// M =
|
||||
// 0 0 0
|
||||
// 1/3 0 1/3
|
||||
// 2/3 1/3 2/3
|
||||
// 1 1 1
|
||||
static void set_quadratic_klm(const SkScalar d[3], SkScalar k[4], SkScalar l[4], SkScalar m[4]) {
|
||||
k[0] = 0.f;
|
||||
k[1] = 1.f/3.f;
|
||||
k[2] = 2.f/3.f;
|
||||
k[3] = 1.f;
|
||||
// For the case when a cubic bezier is actually a quadratic. We duplicate k in l so that the
|
||||
// implicit becomes:
|
||||
//
|
||||
// k^3 - l*m == k^3 - l*k == k * (k^2 - l)
|
||||
//
|
||||
// In the quadratic case we can simply assign fixed values at each control point:
|
||||
//
|
||||
// | ..K.. | | pts[0] pts[1] pts[2] pts[3] | | 0 1/3 2/3 1 |
|
||||
// | ..L.. | * | . . . . | == | 0 0 1/3 1 |
|
||||
// | ..K.. | | 1 1 1 1 | | 0 1/3 2/3 1 |
|
||||
//
|
||||
static void calc_quadratic_klm(const SkPoint pts[4], SkScalar d3, SkMatrix* klm) {
|
||||
SkMatrix klmAtPts;
|
||||
klmAtPts.setAll(0, 1.f/3, 1,
|
||||
0, 0, 1,
|
||||
0, 1.f/3, 1);
|
||||
|
||||
l[0] = 0.f;
|
||||
l[1] = 0.f;
|
||||
l[2] = 1.f/3.f;
|
||||
l[3] = 1.f;
|
||||
SkMatrix inversePts;
|
||||
inversePts.setAll(pts[0].x(), pts[1].x(), pts[3].x(),
|
||||
pts[0].y(), pts[1].y(), pts[3].y(),
|
||||
1, 1, 1);
|
||||
SkAssertResult(inversePts.invert(&inversePts));
|
||||
|
||||
m[0] = 0.f;
|
||||
m[1] = 1.f/3.f;
|
||||
m[2] = 2.f/3.f;
|
||||
m[3] = 1.f;
|
||||
klm->setConcat(klmAtPts, inversePts);
|
||||
|
||||
// If d2 < 0 we need to flip the orientation of our curve since we want negative values to be on
|
||||
// the "inside" of the curve. This is done by negating the k and l values
|
||||
if ( d[2] > 0) {
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
k[i] = -k[i];
|
||||
l[i] = -l[i];
|
||||
}
|
||||
// If d3 > 0 we need to flip the orientation of our curve
|
||||
// This is done by negating the k and l values
|
||||
if (d3 > 0) {
|
||||
negate_kl(klm);
|
||||
}
|
||||
}
|
||||
|
||||
int GrPathUtils::chopCubicAtLoopIntersection(const SkPoint src[4], SkPoint dst[10], SkScalar klm[9],
|
||||
// For the case when a cubic bezier is actually a line. We set K=0, L=1, M=-line, which results in
|
||||
// the following implicit:
|
||||
//
|
||||
// k^3 - l*m == 0^3 - 1*(-line) == -(-line) == line
|
||||
//
|
||||
static void calc_line_klm(const SkPoint pts[4], SkMatrix* klm) {
|
||||
SkScalar ny = pts[0].x() - pts[3].x();
|
||||
SkScalar nx = pts[3].y() - pts[0].y();
|
||||
SkScalar k = nx * pts[0].x() + ny * pts[0].y();
|
||||
klm->setAll( 0, 0, 0,
|
||||
0, 0, 1,
|
||||
-nx, -ny, k);
|
||||
}
|
||||
|
||||
int GrPathUtils::chopCubicAtLoopIntersection(const SkPoint src[4], SkPoint dst[10], SkMatrix* klm,
|
||||
int* loopIndex) {
|
||||
// Variable to store the two parametric values at the loop double point
|
||||
SkScalar smallS = 0.f;
|
||||
SkScalar largeS = 0.f;
|
||||
// Variables to store the two parametric values at the loop double point.
|
||||
SkScalar t1 = 0, t2 = 0;
|
||||
|
||||
// Homogeneous parametric values at the loop double point.
|
||||
SkScalar td, sd, te, se;
|
||||
|
||||
SkScalar d[3];
|
||||
SkCubicType cType = SkClassifyCubic(src, d);
|
||||
@ -733,27 +849,24 @@ int GrPathUtils::chopCubicAtLoopIntersection(const SkPoint src[4], SkPoint dst[1
|
||||
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;
|
||||
td = d[1] + tempSqrt;
|
||||
sd = 2.f * d[0];
|
||||
te = d[1] - tempSqrt;
|
||||
se = 2.f * d[0];
|
||||
|
||||
t1 = td / sd;
|
||||
t2 = te / se;
|
||||
// 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;
|
||||
if (t1 > t2) {
|
||||
SkTSwap(t1, t2);
|
||||
}
|
||||
|
||||
SkScalar chop_ts[2];
|
||||
if (smallS > 0.f && smallS < 1.f) {
|
||||
chop_ts[chop_count++] = smallS;
|
||||
if (t1 > 0.f && t1 < 1.f) {
|
||||
chop_ts[chop_count++] = t1;
|
||||
}
|
||||
if (largeS > 0.f && largeS < 1.f) {
|
||||
chop_ts[chop_count++] = largeS;
|
||||
if (t2 > 0.f && t2 < 1.f) {
|
||||
chop_ts[chop_count++] = t2;
|
||||
}
|
||||
if(dst) {
|
||||
SkChopCubicAt(src, dst, chop_ts, chop_count);
|
||||
@ -768,58 +881,45 @@ int GrPathUtils::chopCubicAtLoopIntersection(const SkPoint src[4], SkPoint dst[1
|
||||
if (2 == chop_count) {
|
||||
*loopIndex = 1;
|
||||
} else if (1 == chop_count) {
|
||||
if (smallS < 0.f) {
|
||||
if (t1 < 0.f) {
|
||||
*loopIndex = 0;
|
||||
} else {
|
||||
*loopIndex = 1;
|
||||
}
|
||||
} else {
|
||||
if (smallS < 0.f && largeS > 1.f) {
|
||||
if (t1 < 0.f && t2 > 1.f) {
|
||||
*loopIndex = 0;
|
||||
} else {
|
||||
*loopIndex = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (klm) {
|
||||
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]);
|
||||
switch (cType) {
|
||||
case kSerpentine_SkCubicType:
|
||||
calc_serp_klm(src, d, klm);
|
||||
break;
|
||||
case kLoop_SkCubicType:
|
||||
calc_loop_klm(src, d[0], td, sd, te, se, klm);
|
||||
break;
|
||||
case kCusp_SkCubicType:
|
||||
if (0 != d[0]) {
|
||||
// FIXME: SkClassifyCubic has a tolerance, but we need an exact classification
|
||||
// here to be sure we won't get a negative in the square root.
|
||||
calc_serp_klm(src, d, klm);
|
||||
} else {
|
||||
calc_inf_cusp_klm(src, d[1], d[2], klm);
|
||||
}
|
||||
break;
|
||||
case kQuadratic_SkCubicType:
|
||||
calc_quadratic_klm(src, d[2], klm);
|
||||
break;
|
||||
case kLine_SkCubicType:
|
||||
case kPoint_SkCubicType:
|
||||
calc_line_klm(src, klm);
|
||||
break;
|
||||
};
|
||||
}
|
||||
return chop_count + 1;
|
||||
}
|
||||
|
||||
void GrPathUtils::getCubicKLM(const SkPoint p[4], SkScalar klm[9]) {
|
||||
SkScalar d[3];
|
||||
SkCubicType cType = SkClassifyCubic(p, d);
|
||||
|
||||
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(p, controlK, controlL, controlM, klm, &klm[3], &klm[6]);
|
||||
}
|
||||
|
@ -98,13 +98,15 @@ namespace GrPathUtils {
|
||||
|
||||
// Input is 3 control points and a weight for a bezier conic. Calculates the
|
||||
// three linear functionals (K,L,M) that represent the implicit equation of the
|
||||
// conic, K^2 - LM.
|
||||
// conic, k^2 - lm.
|
||||
//
|
||||
// Output:
|
||||
// K = (klm[0], klm[1], klm[2])
|
||||
// L = (klm[3], klm[4], klm[5])
|
||||
// M = (klm[6], klm[7], klm[8])
|
||||
void getConicKLM(const SkPoint p[3], const SkScalar weight, SkScalar klm[9]);
|
||||
// Output: klm holds the linear functionals K,L,M as row vectors:
|
||||
//
|
||||
// | ..K.. | | x | | k |
|
||||
// | ..L.. | * | y | == | l |
|
||||
// | ..M.. | | 1 | | m |
|
||||
//
|
||||
void getConicKLM(const SkPoint p[3], const SkScalar weight, SkMatrix* klm);
|
||||
|
||||
// Converts a cubic into a sequence of quads. If working in device space
|
||||
// use tolScale = 1, otherwise set based on stretchiness of the matrix. The
|
||||
@ -127,48 +129,39 @@ namespace GrPathUtils {
|
||||
|
||||
// Chops the cubic bezier passed in by src, at the double point (intersection point)
|
||||
// if the curve is a cubic loop. If it is a loop, there will be two parametric values for
|
||||
// the double point: ls and ms. We chop the cubic at these values if they are between 0 and 1.
|
||||
// the double point: t1 and t2. We chop the cubic at these values if they are between 0 and 1.
|
||||
// Return value:
|
||||
// Value of 3: ls and ms are both between (0,1), and dst will contain the three cubics,
|
||||
// Value of 3: t1 and t2 are both between (0,1), and dst will contain the three cubics,
|
||||
// dst[0..3], dst[3..6], and dst[6..9] if dst is not nullptr
|
||||
// Value of 2: Only one of ls and ms are between (0,1), and dst will contain the two cubics,
|
||||
// Value of 2: Only one of t1 and t2 are between (0,1), and dst will contain the two cubics,
|
||||
// dst[0..3] and dst[3..6] if dst is not nullptr
|
||||
// Value of 1: Neither ls or ms are between (0,1), and dst will contain the one original cubic,
|
||||
// Value of 1: Neither t1 nor t2 are between (0,1), and dst will contain the one original cubic,
|
||||
// dst[0..3] if dst is not nullptr
|
||||
//
|
||||
// Optional KLM Calculation:
|
||||
// The function can also return the KLM linear functionals for the chopped cubic implicit form
|
||||
// of K^3 - LM.
|
||||
// It will calculate a single set of KLM values that can be shared by all sub cubics, except
|
||||
// for the subsection that is "the loop" the K and L values need to be negated.
|
||||
// Output:
|
||||
// klm: Holds the values for the linear functionals as:
|
||||
// K = (klm[0], klm[1], klm[2])
|
||||
// L = (klm[3], klm[4], klm[5])
|
||||
// M = (klm[6], klm[7], klm[8])
|
||||
// loopIndex: This value will tell the caller which of the chopped sections are the the actual
|
||||
// loop once we've chopped. A value of -1 means there is no loop section. The caller
|
||||
// can then use this value to decide how/if they want to flip the orientation of this
|
||||
// section. This flip should be done by negating the K and L values.
|
||||
// The function can also return the KLM linear functionals for the cubic implicit form of
|
||||
// k^3 - lm. This can be shared by all chopped cubics.
|
||||
//
|
||||
// Notice that the klm lines are calculated in the same space as the input control points.
|
||||
// Output:
|
||||
//
|
||||
// klm: Holds the linear functionals K,L,M as row vectors:
|
||||
//
|
||||
// | ..K.. | | x | | k |
|
||||
// | ..L.. | * | y | == | l |
|
||||
// | ..M.. | | 1 | | m |
|
||||
//
|
||||
// loopIndex: This value will tell the caller which of the chopped sections (if any) are the
|
||||
// actual loop. A value of -1 means there is no loop section. The caller can then use
|
||||
// this value to decide how/if they want to flip the orientation of this section.
|
||||
// The flip should be done by negating the k and l values as follows:
|
||||
//
|
||||
// KLM.postScale(-1, -1)
|
||||
//
|
||||
// Notice that the KLM lines are calculated in the same space as the input control points.
|
||||
// If you transform the points the lines will also need to be transformed. This can be done
|
||||
// by mapping the lines with the inverse-transpose of the matrix used to map the points.
|
||||
int chopCubicAtLoopIntersection(const SkPoint src[4], SkPoint dst[10] = nullptr,
|
||||
SkScalar klm[9] = nullptr, int* loopIndex = nullptr);
|
||||
|
||||
// Input is p which holds the 4 control points of a non-rational cubic Bezier curve.
|
||||
// Output is the coefficients of the three linear functionals K, L, & M which
|
||||
// represent the implicit form of the cubic as f(x,y,w) = K^3 - LM. The w term
|
||||
// will always be 1. The output is stored in the array klm, where the values are:
|
||||
// K = (klm[0], klm[1], klm[2])
|
||||
// L = (klm[3], klm[4], klm[5])
|
||||
// M = (klm[6], klm[7], klm[8])
|
||||
//
|
||||
// Notice that the klm lines are calculated in the same space as the input control points.
|
||||
// If you transform the points the lines will also need to be transformed. This can be done
|
||||
// by mapping the lines with the inverse-transpose of the matrix used to map the points.
|
||||
void getCubicKLM(const SkPoint p[4], SkScalar klm[9]);
|
||||
SkMatrix* klm = nullptr, int* loopIndex = nullptr);
|
||||
|
||||
// When tessellating curved paths into linear segments, this defines the maximum distance
|
||||
// in screen space which a segment may deviate from the mathmatically correct value.
|
||||
|
@ -409,9 +409,7 @@ struct BezierVertex {
|
||||
SkPoint fPos;
|
||||
union {
|
||||
struct {
|
||||
SkScalar fK;
|
||||
SkScalar fL;
|
||||
SkScalar fM;
|
||||
SkScalar fKLM[3];
|
||||
} fConic;
|
||||
SkVector fQuadCoord;
|
||||
struct {
|
||||
@ -526,15 +524,13 @@ static void bloat_quad(const SkPoint qpts[3], const SkMatrix* toDevice,
|
||||
// k, l, m are calculated in function GrPathUtils::getConicKLM
|
||||
static void set_conic_coeffs(const SkPoint p[3], BezierVertex verts[kQuadNumVertices],
|
||||
const SkScalar weight) {
|
||||
SkScalar klm[9];
|
||||
SkMatrix klm;
|
||||
|
||||
GrPathUtils::getConicKLM(p, weight, klm);
|
||||
GrPathUtils::getConicKLM(p, weight, &klm);
|
||||
|
||||
for (int i = 0; i < kQuadNumVertices; ++i) {
|
||||
const SkPoint pnt = verts[i].fPos;
|
||||
verts[i].fConic.fK = pnt.fX * klm[0] + pnt.fY * klm[1] + klm[2];
|
||||
verts[i].fConic.fL = pnt.fX * klm[3] + pnt.fY * klm[4] + klm[5];
|
||||
verts[i].fConic.fM = pnt.fX * klm[6] + pnt.fY * klm[7] + klm[8];
|
||||
const SkScalar pt3[3] = {verts[i].fPos.x(), verts[i].fPos.y(), 1.f};
|
||||
klm.mapHomogeneousPoints(verts[i].fConic.fKLM, pt3, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user