Clean up GrPathUtils

Removes unused code, including utilities for dealing with KLM
functionals for the implicit cubic function. The implicit has proven
to not be a very good tool for rendering cubics.

Change-Id: I577b50a9eb296c52dc0101a20394480a4a008654
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/329440
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
This commit is contained in:
Chris Dalton 2020-10-23 16:42:27 -06:00 committed by Skia Commit-Bot
parent 81b270a659
commit 0e13db707f
5 changed files with 88 additions and 546 deletions

View File

@ -1,63 +0,0 @@
/*
* 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 "bench/Benchmark.h"
#include "src/core/SkGeometry.h"
#include "src/gpu/geometry/GrPathUtils.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_";
switch (SkClassifyCubic(fPoints)) {
case SkCubicType::kSerpentine:
fName.append("serp");
break;
case SkCubicType::kLoop:
fName.append("loop");
break;
default:
SK_ABORT("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;
using INHERITED = Benchmark;
};
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); )

View File

@ -38,7 +38,6 @@ bench_sources = [
"$_bench/ControlBench.cpp",
"$_bench/CoverageBench.cpp",
"$_bench/CreateBackendTextureBench.cpp",
"$_bench/CubicKLMBench.cpp",
"$_bench/CubicMapBench.cpp",
"$_bench/DDLRecorderBench.cpp",
"$_bench/DashBench.cpp",

View File

@ -67,7 +67,6 @@ class CCPRGeometryView : public Sample {
PrimitiveType fPrimitiveType = PrimitiveType::kCubics;
SkCubicType fCubicType;
SkMatrix fCubicKLM;
SkPoint fPoints[4] = {
{100.05f, 100.05f}, {400.75f, 100.05f}, {400.75f, 300.95f}, {100.05f, 300.95f}};
@ -154,27 +153,6 @@ private:
GrGLSLFragmentProcessor* onCreateGLSLInstance() const override { return new Impl; }
};
static void draw_klm_line(int w, int h, SkCanvas* canvas, const SkScalar line[3], SkColor color) {
SkPoint p1, p2;
if (SkScalarAbs(line[1]) > SkScalarAbs(line[0])) {
// Draw from vertical edge to vertical edge.
p1 = {0, -line[2] / line[1]};
p2 = {(SkScalar)w, (-line[2] - w * line[0]) / line[1]};
} else {
// Draw from horizontal edge to horizontal edge.
p1 = {-line[2] / line[0], 0};
p2 = {(-line[2] - h * line[1]) / line[0], (SkScalar)h};
}
SkPaint linePaint;
linePaint.setColor(color);
linePaint.setAlpha(128);
linePaint.setStyle(SkPaint::kStroke_Style);
linePaint.setStrokeWidth(0);
linePaint.setAntiAlias(true);
canvas->drawLine(p1, p2, linePaint);
}
void CCPRGeometryView::onDrawContent(SkCanvas* canvas) {
canvas->clear(SK_ColorBLACK);
@ -248,12 +226,6 @@ void CCPRGeometryView::onDrawContent(SkCanvas* canvas) {
if (PrimitiveType::kCubics == fPrimitiveType) {
canvas->drawPoints(SkCanvas::kPoints_PointMode, 4, fPoints, pointsPaint);
if (!fDoStroke) {
int w = this->width(), h = this->height();
draw_klm_line(w, h, canvas, &fCubicKLM[0], SK_ColorYELLOW);
draw_klm_line(w, h, canvas, &fCubicKLM[3], SK_ColorBLUE);
draw_klm_line(w, h, canvas, &fCubicKLM[6], SK_ColorRED);
}
} else {
canvas->drawPoints(SkCanvas::kPoints_PointMode, 2, fPoints, pointsPaint);
canvas->drawPoints(SkCanvas::kPoints_PointMode, 1, fPoints + 3, pointsPaint);
@ -274,8 +246,6 @@ void CCPRGeometryView::updateGpuData() {
fPath.moveTo(fPoints[0]);
if (PrimitiveType::kCubics == fPrimitiveType) {
double t[2], s[2];
fCubicType = GrPathUtils::getCubicKLM(fPoints, &fCubicKLM, t, s);
GrCCFillGeometry geometry;
geometry.beginContour(fPoints[0]);
geometry.cubicTo(fPoints, kDebugBloat / 2, kDebugBloat / 2);

View File

@ -162,54 +162,6 @@ uint32_t GrPathUtils::generateCubicPoints(const SkPoint& p0,
return a + b;
}
int GrPathUtils::worstCasePointCount(const SkPath& path, int* subpaths, SkScalar tol) {
// You should have called scaleToleranceToSrc, which guarantees this
SkASSERT(tol >= gMinCurveTol);
int pointCount = 0;
*subpaths = 1;
bool first = true;
SkPath::Iter iter(path, false);
SkPath::Verb verb;
SkPoint pts[4];
while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
switch (verb) {
case SkPath::kLine_Verb:
pointCount += 1;
break;
case SkPath::kConic_Verb: {
SkScalar weight = iter.conicWeight();
SkAutoConicToQuads converter;
const SkPoint* quadPts = converter.computeQuads(pts, weight, tol);
for (int i = 0; i < converter.countQuads(); ++i) {
pointCount += quadraticPointCount(quadPts + 2*i, tol);
}
[[fallthrough]];
}
case SkPath::kQuad_Verb:
pointCount += quadraticPointCount(pts, tol);
break;
case SkPath::kCubic_Verb:
pointCount += cubicPointCount(pts, tol);
break;
case SkPath::kMove_Verb:
pointCount += 1;
if (!first) {
++(*subpaths);
}
break;
default:
break;
}
first = false;
}
return pointCount;
}
void GrPathUtils::QuadUVMatrix::set(const SkPoint qPts[3]) {
SkMatrix m;
// We want M such that M * xy_pt = uv_pt
@ -631,230 +583,3 @@ void GrPathUtils::convertCubicToQuadsConstrainToTangents(const SkPoint p[4],
convert_noninflect_cubic_to_quads_with_constraint(cubic, tolSqd, dir, quads);
}
}
////////////////////////////////////////////////////////////////////////////////
using ExcludedTerm = GrPathUtils::ExcludedTerm;
ExcludedTerm GrPathUtils::calcCubicInverseTransposePowerBasisMatrix(const SkPoint p[4],
SkMatrix* out) {
static_assert(SK_SCALAR_IS_FLOAT);
// 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 |
// C(t,s) = [t^3 t^2*s t*s^2 s^3] * | . . 0 |
// | . . 0 |
// | . . 1 |
//
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] * 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 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:
//
// | y1 -x1 x1*y2 - y1*x2 |
// 1/det * | -y0 x0 -x0*y2 + y0*x2 |
// | 0 0 det |
//
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);
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 skipTerm;
}
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;
}
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;
}
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;
}
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
// 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], double d3, SkMatrix* klm) {
SkMatrix klmAtPts;
klmAtPts.setAll(0, 1.f/3, 1,
0, 0, 1,
0, 1.f/3, 1);
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));
klm->setConcat(klmAtPts, inversePts);
// 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) {
klm->postScale(-1, -1);
}
}
// 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);
}
SkCubicType GrPathUtils::getCubicKLM(const SkPoint src[4], SkMatrix* klm, double tt[2],
double ss[2]) {
double d[4];
SkCubicType type = SkClassifyCubic(src, tt, ss, d);
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::kCuspAtInfinity:
SkASSERT(1 == t1 && 0 == s1); // Infinity.
[[fallthrough]];
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::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;
}
default:
SK_ABORT("Unexpected cubic type.");
break;
}
klm->setConcat(klmCoeffs, CIT);
return type;
}
int GrPathUtils::chopCubicAtLoopIntersection(const SkPoint src[4], SkPoint dst[10], SkMatrix* klm,
int* loopIndex) {
SkSTArray<2, SkScalar> chops;
*loopIndex = -1;
double t[2], s[2];
if (SkCubicType::kLoop == GrPathUtils::getCubicKLM(src, klm, t, s)) {
SkScalar t0 = static_cast<SkScalar>(t[0] / s[0]);
SkScalar t1 = static_cast<SkScalar>(t[1] / s[1]);
SkASSERT(t0 <= t1); // Technically t0 != t1 in a loop, but there may be FP error.
if (t0 < 1 && t1 > 0) {
*loopIndex = 0;
if (t0 > 0) {
chops.push_back(t0);
*loopIndex = 1;
}
if (t1 < 1) {
chops.push_back(t1);
*loopIndex = chops.count() - 1;
}
}
}
SkChopCubicAt(src, dst, chops.begin(), chops.count());
return chops.count() + 1;
}

View File

@ -19,199 +19,110 @@ class SkMatrix;
* Utilities for evaluating paths.
*/
namespace GrPathUtils {
// Very small tolerances will be increased to a minimum threshold value, to avoid division
// problems in subsequent math.
SkScalar scaleToleranceToSrc(SkScalar devTol,
const SkMatrix& viewM,
const SkRect& pathBounds);
int worstCasePointCount(const SkPath&,
int* subpaths,
SkScalar tol);
// Very small tolerances will be increased to a minimum threshold value, to avoid division problems
// in subsequent math.
SkScalar scaleToleranceToSrc(SkScalar devTol,
const SkMatrix& viewM,
const SkRect& pathBounds);
uint32_t quadraticPointCount(const SkPoint points[], SkScalar tol);
uint32_t quadraticPointCount(const SkPoint points[], SkScalar tol);
uint32_t generateQuadraticPoints(const SkPoint& p0,
const SkPoint& p1,
const SkPoint& p2,
SkScalar tolSqd,
SkPoint** points,
uint32_t pointsLeft);
uint32_t cubicPointCount(const SkPoint points[], SkScalar tol);
uint32_t generateCubicPoints(const SkPoint& p0,
uint32_t generateQuadraticPoints(const SkPoint& p0,
const SkPoint& p1,
const SkPoint& p2,
const SkPoint& p3,
SkScalar tolSqd,
SkPoint** points,
uint32_t pointsLeft);
// A 2x3 matrix that goes from the 2d space coordinates to UV space where
// u^2-v = 0 specifies the quad. The matrix is determined by the control
// points of the quadratic.
class QuadUVMatrix {
public:
QuadUVMatrix() {}
// Initialize the matrix from the control pts
QuadUVMatrix(const SkPoint controlPts[3]) { this->set(controlPts); }
void set(const SkPoint controlPts[3]);
uint32_t cubicPointCount(const SkPoint points[], SkScalar tol);
/**
* Applies the matrix to vertex positions to compute UV coords.
*
* vertices is a pointer to the first vertex.
* vertexCount is the number of vertices.
* stride is the size of each vertex.
* uvOffset is the offset of the UV values within each vertex.
*/
void apply(void* vertices, int vertexCount, size_t stride, size_t uvOffset) const {
intptr_t xyPtr = reinterpret_cast<intptr_t>(vertices);
intptr_t uvPtr = reinterpret_cast<intptr_t>(vertices) + uvOffset;
float sx = fM[0];
float kx = fM[1];
float tx = fM[2];
float ky = fM[3];
float sy = fM[4];
float ty = fM[5];
for (int i = 0; i < vertexCount; ++i) {
const SkPoint* xy = reinterpret_cast<const SkPoint*>(xyPtr);
SkPoint* uv = reinterpret_cast<SkPoint*>(uvPtr);
uv->fX = sx * xy->fX + kx * xy->fY + tx;
uv->fY = ky * xy->fX + sy * xy->fY + ty;
xyPtr += stride;
uvPtr += stride;
}
uint32_t generateCubicPoints(const SkPoint& p0,
const SkPoint& p1,
const SkPoint& p2,
const SkPoint& p3,
SkScalar tolSqd,
SkPoint** points,
uint32_t pointsLeft);
// A 2x3 matrix that goes from the 2d space coordinates to UV space where u^2-v = 0 specifies the
// quad. The matrix is determined by the control points of the quadratic.
class QuadUVMatrix {
public:
QuadUVMatrix() {}
// Initialize the matrix from the control pts
QuadUVMatrix(const SkPoint controlPts[3]) { this->set(controlPts); }
void set(const SkPoint controlPts[3]);
/**
* Applies the matrix to vertex positions to compute UV coords.
*
* vertices is a pointer to the first vertex.
* vertexCount is the number of vertices.
* stride is the size of each vertex.
* uvOffset is the offset of the UV values within each vertex.
*/
void apply(void* vertices, int vertexCount, size_t stride, size_t uvOffset) const {
intptr_t xyPtr = reinterpret_cast<intptr_t>(vertices);
intptr_t uvPtr = reinterpret_cast<intptr_t>(vertices) + uvOffset;
float sx = fM[0];
float kx = fM[1];
float tx = fM[2];
float ky = fM[3];
float sy = fM[4];
float ty = fM[5];
for (int i = 0; i < vertexCount; ++i) {
const SkPoint* xy = reinterpret_cast<const SkPoint*>(xyPtr);
SkPoint* uv = reinterpret_cast<SkPoint*>(uvPtr);
uv->fX = sx * xy->fX + kx * xy->fY + tx;
uv->fY = ky * xy->fX + sy * xy->fY + ty;
xyPtr += stride;
uvPtr += stride;
}
private:
float fM[6];
};
}
private:
float fM[6];
};
// 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.
//
// 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);
// 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.
//
// 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
// result is sets of 3 points in quads. This will preserve the starting and
// ending tangent vectors (modulo FP precision).
void convertCubicToQuads(const SkPoint p[4],
SkScalar tolScale,
SkTArray<SkPoint, true>* quads);
// 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 result is sets of 3 points in quads. This will
// preserve the starting and ending tangent vectors (modulo FP precision).
void convertCubicToQuads(const SkPoint p[4],
SkScalar tolScale,
SkTArray<SkPoint, true>* quads);
// When we approximate a cubic {a,b,c,d} with a quadratic we may have to
// ensure that the new control point lies between the lines ab and cd. The
// convex path renderer requires this. It starts with a path where all the
// control points taken together form a convex polygon. It relies on this
// property and the quadratic approximation of cubics step cannot alter it.
// This variation enforces this constraint. The cubic must be simple and dir
// must specify the orientation of the contour containing the cubic.
void convertCubicToQuadsConstrainToTangents(const SkPoint p[4],
SkScalar tolScale,
SkPathFirstDirection dir,
SkTArray<SkPoint, true>* quads);
// When we approximate a cubic {a,b,c,d} with a quadratic we may have to ensure that the new control
// point lies between the lines ab and cd. The convex path renderer requires this. It starts with a
// path where all the control points taken together form a convex polygon. It relies on this
// property and the quadratic approximation of cubics step cannot alter it. This variation enforces
// this constraint. The cubic must be simple and dir must specify the orientation of the contour
// containing the cubic.
void convertCubicToQuadsConstrainToTangents(const SkPoint p[4],
SkScalar tolScale,
SkPathFirstDirection dir,
SkTArray<SkPoint, true>* quads);
enum class ExcludedTerm {
kNonInvertible,
kQuadraticTerm,
kLinearTerm
};
// When tessellating curved paths into linear segments, this defines the maximum distance in screen
// space which a segment may deviate from the mathematically correct value. Above this value, the
// segment will be subdivided.
// This value was chosen to approximate the supersampling accuracy of the raster path (16 samples,
// or one quarter pixel).
static const SkScalar kDefaultTolerance = SkDoubleToScalar(0.25);
// 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);
// We guarantee that no quad or cubic will ever produce more than this many points
static const int kMaxPointsPerCurve = 1 << 10;
// 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:
//
// k^3 < l*m
//
// Output:
//
// klm: Holds the linear functionals K,L,M as row vectors:
//
// | ..K.. | | x | | k |
// | ..L.. | * | y | == | l |
// | ..M.. | | 1 | | m |
//
// NOTE: 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.
//
// t[],s[]: These are set to the two homogeneous parameter values at which points the lines L&M
// intersect with K (See SkClassifyCubic).
//
// Returns the cubic's classification.
SkCubicType getCubicKLM(const SkPoint src[4], SkMatrix* klm, double t[2], double s[2]);
// 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: t1 and t2. We chop the cubic at these values if they are between 0 and 1.
// Return value:
// 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 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 t1 nor t2 are between (0,1), and dst will contain the one original cubic,
// src[0..3]
//
// Output:
//
// klm: Holds the linear functionals K,L,M as row vectors. (See getCubicKLM().)
//
// 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)
int chopCubicAtLoopIntersection(const SkPoint src[4], SkPoint dst[10], SkMatrix* klm,
int* loopIndex);
// 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.
// Above this value, the segment will be subdivided.
// This value was chosen to approximate the supersampling accuracy of the raster path (16
// samples, or one quarter pixel).
static const SkScalar kDefaultTolerance = SkDoubleToScalar(0.25);
// We guarantee that no quad or cubic will ever produce more than this many points
static const int kMaxPointsPerCurve = 1 << 10;
} // namespace GrPathUtils
#endif