int SkPathPriv::GenIDChangeListenersCount(const SkPath& path) { return path.fPathData->genIDChangeListenerCount();
}
void SkPathPriv::AddGenIDChangeListener(const SkPath& path, sk_sp<SkIDChangeListener> listener) { auto pdata = path.fPathData.get(); // SkPath's error-singleton is never deleted, so we don't want to add any listeners to it. if (pdata != SkPath::PeekErrorSingleton()) {
pdata->addGenIDChangeListener(std::move(listener));
}
}
constauto axis_aligned_orthogonal = [](const SkVector& a, const SkVector& b) { // Assuming one of the vectors is known to be axis-aligned, // check whether the other one is orthogonal (and implicitly axis-aligned). // I.e. we have exactly one vertical vector (fX == 0, fY != 0) and one // horizontal vector (fX != 0, fY == 0). return ((a.fX == 0) ^ (b.fX == 0)) &
((a.fY == 0) ^ (b.fY == 0));
};
// We have a rect iff the side vectors are axis aligned and form 3 right corners. // This can be further reduced to one axis aligned vector and 3 orthogonal vectors. // Note: bitwise operators are measurably faster in micro benchmarks (no short-circuiting?). if (!( // Axis-aligned v0.
((v0.fX == 0) ^ (v0.fY == 0)) &
rc = IsRectContour(pts, vbs, raw.fSegmentMask, false); if (rc) {
testDirs[1] = rc->fDirection;
testRects[1] = rc->fRect; if (testRects[0].contains(testRects[1])) { if (rects) {
rects[0] = testRects[0];
rects[1] = testRects[1];
} if (dirs) {
dirs[0] = testDirs[0];
dirs[1] = testDirs[1];
} return true;
} if (testRects[1].contains(testRects[0])) { if (rects) {
rects[0] = testRects[1];
rects[1] = testRects[0];
} if (dirs) {
dirs[0] = testDirs[1];
dirs[1] = testDirs[0];
} return true;
}
} returnfalse;
}
bool SkPathPriv::DrawArcIsConvex(SkScalar sweepAngle,
SkArc::Type arcType, bool isFillNoPathEffect) { if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) { // This gets converted to an oval. return true;
} if (arcType == SkArc::Type::kWedge) { // This is a pie wedge. It's convex if the angle is <= 180. return SkScalarAbs(sweepAngle) <= 180.f;
} // When the angle exceeds 360 this wraps back on top of itself. Otherwise it is a circle clipped // to a secant, i.e. convex. return SkScalarAbs(sweepAngle) <= 360.f;
}
SkPath SkPathPriv::CreateDrawArcPath(const SkArc& arc, bool isFillNoPathEffect) {
SkRect oval = arc.fOval;
SkScalar startAngle = arc.fStartAngle, sweepAngle = arc.fSweepAngle;
SkASSERT(!oval.isEmpty());
SkASSERT(sweepAngle); // We cap the number of total rotations. This keeps the resulting paths simpler. More important, // it prevents values so large that the loops below never terminate (once ULP > 360). if (SkScalarAbs(sweepAngle) > 3600.0f) {
sweepAngle = std::copysign(3600.0f, sweepAngle) + std::fmod(sweepAngle, 360.0f);
}
enum DirChange {
kUnknown_DirChange,
kLeft_DirChange,
kRight_DirChange,
kStraight_DirChange,
kBackwards_DirChange, // if double back, allow simple lines to be convex
kInvalid_DirChange
};
// only valid for a single contour struct Convexicator {
/** The direction returned is only valid if the path is determined convex */
SkPathFirstDirection getFirstDirection() const { return fFirstDirection; }
bool addPt(const SkPoint& pt) { if (fLastPt == pt) { return true;
} // should only be true for first non-zero vector after setMovePt was called. It is possible // we doubled backed at the start so need to check if fLastVec is zero or not. if (fFirstPt == fLastPt && fExpectedDir == kInvalid_DirChange && fLastVec.equals(0,0)) {
fLastVec = pt - fLastPt;
fFirstVec = fLastVec;
} elseif (!this->addVec(pt - fLastPt)) { returnfalse;
}
fLastPt = pt; return true;
}
staticbool IsConcaveBySign(const SkPoint points[], int count) { if (count <= 3) { // point, line, or triangle are always convex returnfalse;
}
const SkPoint* last = points + count;
SkPoint currPt = *points++;
SkPoint firstPt = currPt; int dxes = 0; int dyes = 0; int lastSx = kValueNeverReturnedBySign; int lastSy = kValueNeverReturnedBySign; for (int outerLoop = 0; outerLoop < 2; ++outerLoop ) { while (points != last) {
SkVector vec = *points - currPt; if (!vec.isZero()) { // give up if vector construction failed if (!vec.isFinite()) { return true; // treat as concave
} int sx = sign(vec.fX); int sy = sign(vec.fY);
dxes += (sx != lastSx);
dyes += (sy != lastSy); if (dxes > 3 || dyes > 3) { return true;
}
lastSx = sx;
lastSy = sy;
}
currPt = *points++; if (outerLoop) { break;
}
}
points = &firstPt;
} returnfalse; // that is, it may be convex, don't know yet
}
bool close() { // If this was an explicit close, there was already a lineTo to fFirstPoint, so this // addPt() is a no-op. Otherwise, the addPt implicitly closes the contour. In either case, // we have to check the direction change along the first vector in case it is concave. return this->addPt(fFirstPt) && this->addVec(fFirstVec);
}
bool addVec(const SkVector& curVec) {
DirChange dir = this->directionChange(curVec); switch (dir) { case kLeft_DirChange: // fall through case kRight_DirChange: if (kInvalid_DirChange == fExpectedDir) {
fExpectedDir = dir;
fFirstDirection = (kRight_DirChange == dir) ? SkPathFirstDirection::kCW
: SkPathFirstDirection::kCCW;
} elseif (dir != fExpectedDir) {
fFirstDirection = SkPathFirstDirection::kUnknown; returnfalse;
}
fLastVec = curVec; break; case kStraight_DirChange: break; case kBackwards_DirChange: // allow path to reverse direction twice // Given path.moveTo(0, 0); path.lineTo(1, 1); // - 1st reversal: direction change formed by line (0,0 1,1), line (1,1 0,0) // - 2nd reversal: direction change formed by line (1,1 0,0), line (0,0 1,1)
fLastVec = curVec; return ++fReversals < 3; case kUnknown_DirChange: return (fIsFinite = false); case kInvalid_DirChange:
SK_ABORT("Use of invalid direction change flag"); break;
} return true;
}
SkPoint fFirstPt {0, 0}; // The first point of the contour, e.g. moveTo(x,y)
SkVector fFirstVec {0, 0}; // The direction leaving fFirstPt to the next vertex
SkPoint fLastPt {0, 0}; // The last point passed to addPt()
SkVector fLastVec {0, 0}; // The direction that brought the path to fLastPt
SkPathConvexity SkPathPriv::ComputeConvexity(SkSpan<const SkPoint> points,
SkSpan<const SkPathVerb> vbs,
SkSpan<constfloat> conicWeights) { // callers need to give us finite values
SkASSERT(SkRect::Bounds(points).has_value());
trim_trailing_moves(points, vbs);
if (vbs.empty()) { return SkPathConvexity::kConvex_Degenerate;
}
// Check to see if path changes direction more than three times as quick concave test if (Convexicator::IsConcaveBySign(points.data(), points.size())) { return SkPathConvexity::kConcave;
}
int contourCount = 0; bool needsClose = false;
Convexicator state;
auto iter = SkPathIter(points, vbs, conicWeights); while (auto rec = iter.next()) { auto verb = rec->fVerb; auto pts = rec->fPoints;
// Looking for the last moveTo before non-move verbs start if (contourCount == 0) { if (verb == SkPathVerb::kMove) {
state.setMovePt(pts[0]);
} else { // Starting the actual contour, fall through to c=1 to add the points
contourCount++;
needsClose = true;
}
} // Accumulating points into the Convexicator until we hit a close or another move if (contourCount == 1) { if (verb == SkPathVerb::kClose || verb == SkPathVerb::kMove) { if (!state.close()) { return SkPathConvexity::kConcave;
}
needsClose = false;
contourCount++;
} else { // lines add 1 point, cubics add 3, conics and quads add 2 int count = SkPathPriv::PtsInVerb((unsigned) verb);
SkASSERT(count > 0); for (int i = 1; i <= count; ++i) { if (!state.addPt(pts[i])) { return SkPathConvexity::kConcave;
}
}
}
} else { // The first contour has closed and anything other than spurious trailing moves means // there's multiple contours and the path can't be convex if (verb != SkPathVerb::kMove) { return SkPathConvexity::kConcave;
}
}
}
// If the path isn't explicitly closed do so implicitly if (needsClose && !state.close()) { return SkPathConvexity::kConcave;
}
// returns cross product of (p1 - p0) and (p2 - p0) static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0); // We may get 0 when the above subtracts underflow. We expect this to be // very rare and lazily promote to double. if (0 == cross) { double p0x = SkScalarToDouble(p0.fX); double p0y = SkScalarToDouble(p0.fY);
// Returns the first pt with the maximum Y coordinate staticint find_max_y(const SkPoint pts[], int count) {
SkASSERT(count > 0);
SkScalar max = pts[0].fY; int firstIndex = 0; for (int i = 1; i < count; ++i) {
SkScalar y = pts[i].fY; if (y > max) {
max = y;
firstIndex = i;
}
} return firstIndex;
}
staticint find_diff_pt(const SkPoint pts[], int index, int n, int inc) { int i = index; for (;;) {
i = (i + inc) % n; if (i == index) { // we wrapped around, so abort break;
} if (pts[index] != pts[i]) { // found a different point, success! break;
}
} return i;
}
/** *Startingatindex,andmovingforward(incrementing),findthexminand *xmaxofthecontiguouspointsthathavethesameY.
*/ staticint find_min_max_x_at_y(const SkPoint pts[], int index, int n, int* maxIndexPtr) { const SkScalar y = pts[index].fY;
SkScalar min = pts[index].fX;
SkScalar max = min; int minIndex = index; int maxIndex = index; for (int i = index + 1; i < n; ++i) { if (pts[i].fY != y) { break;
}
SkScalar x = pts[i].fX; if (x < min) {
min = x;
minIndex = i;
} elseif (x > max) {
max = x;
maxIndex = i;
}
}
*maxIndexPtr = maxIndex; return minIndex;
}
class ContourIter {
public:
ContourIter(SkSpan<const SkPoint>, SkSpan<const SkPathVerb>, SkSpan<constfloat> conicWeights);
bool done() const { return fDone; } // if !done() then these may be called int count() const { return fCurrPtCount; } const SkPoint* pts() const { return fCurrPt; } void next();
for (; !iter.done(); iter.next()) { int n = iter.count(); if (n < 3) { continue;
}
const SkPoint* pts = iter.pts();
SkScalar cross = 0; int index = find_max_y(pts, n); if (pts[index].fY < ymax) { continue;
}
// If there is more than 1 distinct point at the y-max, we take the // x-min and x-max of them and just subtract to compute the dir. if (pts[(index + 1) % n].fY == pts[index].fY) { int maxIndex; int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex); if (minIndex == maxIndex) { goto TRY_CROSSPROD;
}
SkASSERT(pts[minIndex].fY == pts[index].fY);
SkASSERT(pts[maxIndex].fY == pts[index].fY);
SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX); // we just subtract the indices, and let that auto-convert to // SkScalar, since we just want - or + to signal the direction.
cross = minIndex - maxIndex;
} else {
TRY_CROSSPROD: // Find a next and prev index to use for the cross-product test, // but we try to find pts that form non-zero vectors from pts[index] // // Its possible that we can't find two non-degenerate vectors, so // we have to guard our search (e.g. all the pts could be in the // same place).
// we pass n - 1 instead of -1 so we don't foul up % operator by // passing it a negative LH argument. int prev = find_diff_pt(pts, index, n, n - 1); if (prev == index) { // completely degenerate, skip to next contour continue;
} int next = find_diff_pt(pts, index, n, 1);
SkASSERT(next != index);
cross = cross_prod(pts[prev], pts[index], pts[next]); // if we get a zero and the points are horizontal, then we look at the spread in // x-direction. We really should continue to walk away from the degeneracy until // there is a divergence. if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) { // construct the subtract so we get the correct Direction below
cross = pts[index].fX - pts[next].fX;
}
}
if (cross) { // record our best guess so far
ymax = pts[index].fY;
ymaxCross = cross;
}
}
staticfloat poly_eval(float A, float B, float C, float t) { return (A * t + B) * t + C;
}
staticfloat poly_eval(float A, float B, float C, float D, float t) { return ((A * t + B) * t + C) * t + D;
}
staticbool between(SkScalar a, SkScalar b, SkScalar c) {
SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
|| (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c))); return (a - b) * (c - b) <= 0;
}
int dir = 1; if (y0 > y3) {
using std::swap;
swap(y0, y3);
dir = -1;
} if (y < y0 || y > y3) { return0;
} if (checkOnCurve(x, y, pts[0], pts[3])) {
*onCurveCount += 1; return0;
} if (y == y3) { return0;
}
// quickreject or quickaccept
SkScalar min, max;
find_minmax<4>(pts, &min, &max); if (x < min) { return0;
} if (x > max) { return dir;
}
// compute the actual x(t) value
SkScalar t; if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) { return0;
}
SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t); if (SkScalarNearlyEqual(xt, x)) { if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
*onCurveCount += 1; return0;
}
} return xt < x ? dir : 0;
}
staticint winding_cubic(SkSpan<const SkPoint> pts, SkScalar x, SkScalar y, int* onCurveCount) {
SkPoint dst[10]; int n = SkChopCubicAtYExtrema(pts.data(), dst); int w = 0; for (int i = 0; i <= n; ++i) {
w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
} return w;
}
int dir = 1; if (y0 > y2) {
using std::swap;
swap(y0, y2);
dir = -1;
} if (y < y0 || y > y2) { return0;
} if (checkOnCurve(x, y, pts[0], pts[2])) {
*onCurveCount += 1; return0;
} if (y == y2) { return0;
}
SkScalar roots[2];
SkScalar A = pts[2].fY;
SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
SkScalar C = pts[0].fY;
A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
B -= C; // B = b*w - w * yCept + yCept - a
C -= y; int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
SkASSERT(n <= 1);
SkScalar xt; if (0 == n) { // zero roots are returned only when y0 == y // Need [0] if dir == 1 // and [2] if dir == -1
xt = pts[1 - dir].fX;
} else {
SkScalar t = roots[0];
xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
} if (SkScalarNearlyEqual(xt, x)) { if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
*onCurveCount += 1; return0;
}
} return xt < x ? dir : 0;
}
staticint winding_conic(SkSpan<const SkPoint> pts, SkScalar x, SkScalar y, SkScalar weight, int* onCurveCount) {
SkConic conic(pts.data(), weight);
SkConic chopped[2]; // If the data points are very large, the conic may not be monotonic but may also // fail to chop. Then, the chopper does not split the original conic in two. bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped); int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount); if (!isMono) {
w += winding_mono_conic(chopped[1], x, y, onCurveCount);
} return w;
}
int dir = 1; if (y0 > y2) {
using std::swap;
swap(y0, y2);
dir = -1;
} if (y < y0 || y > y2) { return0;
} if (checkOnCurve(x, y, pts[0], pts[2])) {
*onCurveCount += 1; return0;
} if (y == y2) { return0;
} // bounds check on X (not required. is it faster?) #if0 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) { return0;
} #endif
SkScalar roots[2]; int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY, 2 * (pts[1].fY - pts[0].fY),
pts[0].fY - y,
roots);
SkASSERT(n <= 1);
SkScalar xt; if (0 == n) { // zero roots are returned only when y0 == y // Need [0] if dir == 1 // and [2] if dir == -1
xt = pts[1 - dir].fX;
} else {
SkScalar t = roots[0];
SkScalar C = pts[0].fX;
SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
SkScalar B = 2 * (pts[1].fX - C);
xt = poly_eval(A, B, C, t);
} if (SkScalarNearlyEqual(xt, x)) { if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
*onCurveCount += 1; return0;
}
} return xt < x ? dir : 0;
}
staticint winding_quad(SkSpan<const SkPoint> pts, SkScalar x, SkScalar y, int* onCurveCount) {
SkPoint spanStorage[5];
SkSpan<const SkPoint> span = pts; int n = 0;
if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
n = SkChopQuadAtYExtrema(pts.data(), spanStorage);
span = spanStorage;
} int w = winding_mono_quad(span, x, y, onCurveCount); if (n > 0) {
w += winding_mono_quad(span.subspan(2), x, y, onCurveCount);
} return w;
}
int dir = 1; if (y0 > y1) {
using std::swap;
swap(y0, y1);
dir = -1;
} if (y < y0 || y > y1) { return0;
} if (checkOnCurve(x, y, pts[0], pts[1])) {
*onCurveCount += 1; return0;
} if (y == y1) { return0;
}
SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
if (!cross) { // zero cross means the point is on the line, and since the case where // y of the query point is at the end point is handled above, we can be // sure that we're on the line (excluding the end point) here if (x != x1 || y != pts[1].fY) {
*onCurveCount += 1;
}
dir = 0;
} elseif (SkScalarSignAsInt(cross) == dir) {
dir = 0;
} return dir;
}
staticvoid tangent_cubic(SkSpan<const SkPoint> pts, SkScalar x, SkScalar y,
SkTDArray<SkVector>* tangents) { if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
&& !between(pts[2].fY, y, pts[3].fY)) { return;
} if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
&& !between(pts[2].fX, x, pts[3].fX)) { return;
}
SkPoint dst[10]; int n = SkChopCubicAtYExtrema(pts.data(), dst); for (int i = 0; i <= n; ++i) {
SkPoint* c = &dst[i * 3];
SkScalar t; if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) { continue;
}
SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t); if (!SkScalarNearlyEqual(x, xt)) { continue;
}
SkVector tangent;
SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
tangents->push_back(tangent);
}
}
staticvoid tangent_conic(SkSpan<const SkPoint> pts, SkScalar x, SkScalar y, SkScalar w,
SkTDArray<SkVector>* tangents) { if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) { return;
} if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) { return;
}
SkScalar roots[2];
SkScalar A = pts[2].fY;
SkScalar B = pts[1].fY * w - y * w + y;
SkScalar C = pts[0].fY;
A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
B -= C; // B = b*w - w * yCept + yCept - a
C -= y; int n = SkFindUnitQuadRoots(A, 2 * B, C, roots); for (int index = 0; index < n; ++index) {
SkScalar t = roots[index];
SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t); if (!SkScalarNearlyEqual(x, xt)) { continue;
}
SkConic conic(pts.data(), w);
tangents->push_back(conic.evalTangentAt(t));
}
}
staticvoid tangent_quad(SkSpan<const SkPoint> pts, SkScalar x, SkScalar y,
SkTDArray<SkVector>* tangents) { if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) { return;
} if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) { return;
}
SkScalar roots[2]; int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY, 2 * (pts[1].fY - pts[0].fY),
pts[0].fY - y,
roots); for (int index = 0; index < n; ++index) {
SkScalar t = roots[index];
SkScalar C = pts[0].fX;
SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
SkScalar B = 2 * (pts[1].fX - C);
SkScalar xt = poly_eval(A, B, C, t); if (!SkScalarNearlyEqual(x, xt)) { continue;
}
tangents->push_back(SkEvalQuadTangentAt(pts.data(), t));
}
}
if (!contains_inclusive(raw.bounds(), p)) { return isInverse;
}
int w = 0; int onCurveCount = 0;
for (auto iter = SkPathEdgeIter(raw); auto rec = iter.next(); ) { switch (rec.fEdge) { case SkPathEdgeIter::Edge::kLine:
w += winding_line({rec.fPts, 2}, p.fX, p.fY, &onCurveCount); break; case SkPathEdgeIter::Edge::kQuad:
w += winding_quad({rec.fPts, 3}, p.fX, p.fY, &onCurveCount); break; case SkPathEdgeIter::Edge::kConic:
w += winding_conic({rec.fPts, 3}, p.fX, p.fY, iter.conicWeight(), &onCurveCount); break; case SkPathEdgeIter::Edge::kCubic:
w += winding_cubic({rec.fPts, 4}, p.fX, p.fY, &onCurveCount); break;
}
} bool evenOddFill = SkPathFillType::kEvenOdd == ft
|| SkPathFillType::kInverseEvenOdd == ft; if (evenOddFill) {
w &= 1;
} if (w) { return !isInverse;
} if (onCurveCount <= 1) { return SkToBool(onCurveCount) ^ isInverse;
} if ((onCurveCount & 1) || evenOddFill) { return SkToBool(onCurveCount & 1) ^ isInverse;
} // If the point touches an even number of curves, and the fill is winding, check for // coincidence. Count coincidence as places where the on curve points have identical tangents.
SkTDArray<SkVector> tangents; for (auto iter = SkPathEdgeIter(raw); auto rec = iter.next(); ) { int oldCount = tangents.size(); switch (rec.fEdge) { case SkPathEdgeIter::Edge::kLine:
tangent_line({rec.fPts, 2}, p.fX, p.fY, &tangents); break; case SkPathEdgeIter::Edge::kQuad:
tangent_quad({rec.fPts, 3}, p.fX, p.fY, &tangents); break; case SkPathEdgeIter::Edge::kConic:
tangent_conic({rec.fPts, 3}, p.fX, p.fY, iter.conicWeight(), &tangents); break; case SkPathEdgeIter::Edge::kCubic:
tangent_cubic({rec.fPts, 4}, p.fX, p.fY, &tangents); break;
} if (tangents.size() > oldCount) { int last = tangents.size() - 1; const SkVector& tangent = tangents[last]; if (SkScalarNearlyZero(SkPointPriv::LengthSqd(tangent))) {
tangents.remove(last);
} else { for (int index = 0; index < last; ++index) { const SkVector& test = tangents[index]; if (SkScalarNearlyZero(test.cross(tangent))
&& SkScalarSignAsInt(tangent.fX * test.fX) <= 0
&& SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
tangents.remove(last);
tangents.removeShuffle(index); break;
}
}
}
}
} return SkToBool(tangents.size()) ^ isInverse;
}
bool SkPathPriv::IsAxisAligned(SkSpan<const SkPoint> pts) { // Conservative (quick) test to see if all segments are axis-aligned. // Multiple contours might give a false-negative, but for speed, we ignore that // and just look at the raw points.
for (size_t i = 1; i < pts.size(); ++i) { if (pts[i-1].fX != pts[i].fX && pts[i-1].fY != pts[i].fY) { returnfalse;
}
} return true;
}
// Due to finite/fragile float numerics, we can't assume that a convex path remains // convex after a transformation, so mark it as unknown here. // However, some transformations are thought to be safe: // axis-aligned values under scale/translate. // if (SkPathConvexity_IsConvex(convexity)) { if (!matrix.isScaleTranslate() || !SkPathPriv::IsAxisAligned(pts)) { // Not safe to still assume we're convex...
convexity = SkPathConvexity::kUnknown;
} else {
SkScalar det2x2 =
matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY); if (det2x2 < 0) {
convexity = SkPathConvexity_OppositeConvexDirection(convexity);
} elseif (det2x2 > 0) { // we keep our direction
} else/* det2x == 0 */ {
convexity = SkPathConvexity::kConvex_Degenerate;
}
}
} return convexity;
}
// initial with the first MoveTo, so we don't have to check inside the switch float L = points[0].fX,
T = points[0].fY,
R = points[0].fX,
B = points[0].fY;
for (auto [verb, pts, w] : SkPathPriv::Iterate(verbs, points.data(), conicWeights.data())) {
SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1 int count = 0; switch (verb) { case SkPathVerb::kMove:
extremas[0] = pts[0];
count = 1; break; case SkPathVerb::kLine:
extremas[0] = pts[1];
count = 1; break; case SkPathVerb::kQuad:
count = compute_quad_extremas(pts, extremas); break; case SkPathVerb::kConic:
count = compute_conic_extremas(pts, *w, extremas); break; case SkPathVerb::kCubic:
count = compute_cubic_extremas(pts, extremas); break; case SkPathVerb::kClose: break;
} for (int i = 0; i < count; ++i) {
SkPoint p = extremas[i];
L = std::fminf(p.fX, L);
T = std::fminf(p.fY, T);
R = std::fmaxf(p.fX, R);
B = std::fmaxf(p.fY, B);
}
} return {L, T, R, B};
}
int rm = 0; if (isRRect) { // Degenerate rrect indices to oval indices and remember the remainder. // Ovals have one index per side whereas rrects have two.
rm = inStart & 0b1;
inStart /= 2;
} // Is the antidiagonal non-zero (otherwise the diagonal is zero) int antiDiag; // Is the non-zero value in the top row (either kMScaleX or kMSkewX) negative int topNeg; // Are the two non-zero diagonal or antidiagonal values the same sign. int sameSign; if (matrix.get(SkMatrix::kMScaleX) != 0) {
antiDiag = 0b00; if (matrix.get(SkMatrix::kMScaleX) > 0) {
topNeg = 0b00;
sameSign = matrix.get(SkMatrix::kMScaleY) > 0 ? 0b01 : 0b00;
} else {
topNeg = 0b10;
sameSign = matrix.get(SkMatrix::kMScaleY) > 0 ? 0b00 : 0b01;
}
} else {
antiDiag = 0b01; if (matrix.get(SkMatrix::kMSkewX) > 0) {
topNeg = 0b00;
sameSign = matrix.get(SkMatrix::kMSkewY) > 0 ? 0b01 : 0b00;
} else {
topNeg = 0b10;
sameSign = matrix.get(SkMatrix::kMSkewY) > 0 ? 0b00 : 0b01;
}
} if (sameSign != antiDiag) { // This is a rotation (and maybe scale). The direction is unchanged. // Trust me on the start computation (or draw yourself some pictures)
start = (inStart + 4 - (topNeg | antiDiag)) % 4;
SkASSERT(start < 4); if (isRRect) {
start = 2 * start + rm;
}
} else { // This is a mirror (and maybe scale). The direction is reversed.
isCCW = !isCCW; // Trust me on the start computation (or draw yourself some pictures)
start = (6 + (topNeg | antiDiag) - inStart) % 4;
SkASSERT(start < 4); if (isRRect) {
start = 2 * start + (rm ? 0 : 1);
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.