未验证 提交 f10f1725 编写于 作者: F Ferhat 提交者: GitHub

[web] Update SurfacePath to use PathRef (#19557)

* Update SurfacePath to use PathRef
* Fix test analysis errors
* Switch DDRect to use path to reduce paint time processing
* Implement toString() for debug mode
* Fix contains (bounds inclusive) edge case, add test
* Update recording canvas test for drawDRRect change
* Update diffrate for arc/conic render change
* Add test for winding for beveled border. Fix checkOnCurve
* Fix mono quad winding on curve check
* fix _chopCubicAtYExtrema and add test case
* Address reviewer comments / setAll API usage
上级 9b3e3410
......@@ -492,9 +492,14 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/offset.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/opacity.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/painting.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/path/conic.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/path/cubic.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/path/path.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/path/path_metrics.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/path/path_ref.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/path/path_to_svg.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/path/path_utils.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/path/path_windings.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/path/tangent.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/picture.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/platform_view.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/recording_canvas.dart
......
......@@ -100,9 +100,14 @@ part 'engine/surface/offset.dart';
part 'engine/surface/opacity.dart';
part 'engine/surface/painting.dart';
part 'engine/surface/path/conic.dart';
part 'engine/surface/path/cubic.dart';
part 'engine/surface/path/path.dart';
part 'engine/surface/path/path_metrics.dart';
part 'engine/surface/path/path_ref.dart';
part 'engine/surface/path/path_to_svg.dart';
part 'engine/surface/path/path_utils.dart';
part 'engine/surface/path/path_windings.dart';
part 'engine/surface/path/tangent.dart';
part 'engine/surface/picture.dart';
part 'engine/surface/platform_view.dart';
part 'engine/surface/recording_canvas.dart';
......
......@@ -509,69 +509,47 @@ class _CanvasPool extends _SaveStackTracking {
}
}
// Float buffer used for path iteration.
static Float32List _runBuffer = Float32List(PathRefIterator.kMaxBufferSize);
/// 'Runs' the given [path] by applying all of its commands to the canvas.
void _runPath(html.CanvasRenderingContext2D ctx, SurfacePath path) {
ctx.beginPath();
final List<Subpath> subpaths = path.subpaths;
final int subpathCount = subpaths.length;
for (int subPathIndex = 0; subPathIndex < subpathCount; subPathIndex++) {
final Subpath subpath = subpaths[subPathIndex];
final List<PathCommand> commands = subpath.commands;
final int commandCount = commands.length;
for (int c = 0; c < commandCount; c++) {
final PathCommand command = commands[c];
switch (command.type) {
case PathCommandTypes.bezierCurveTo:
final BezierCurveTo curve = command as BezierCurveTo;
ctx.bezierCurveTo(
curve.x1, curve.y1, curve.x2, curve.y2, curve.x3, curve.y3);
break;
case PathCommandTypes.close:
ctx.closePath();
break;
case PathCommandTypes.ellipse:
final Ellipse ellipse = command as Ellipse;
if (c == 0) {
// Ellipses that start a new path need to set start point,
// otherwise it incorrectly uses last point.
ctx.moveTo(subpath.startX, subpath.startY);
}
DomRenderer.ellipse(ctx,
ellipse.x,
ellipse.y,
ellipse.radiusX,
ellipse.radiusY,
ellipse.rotation,
ellipse.startAngle,
ellipse.endAngle,
ellipse.anticlockwise);
break;
case PathCommandTypes.lineTo:
final LineTo lineTo = command as LineTo;
ctx.lineTo(lineTo.x, lineTo.y);
break;
case PathCommandTypes.moveTo:
final MoveTo moveTo = command as MoveTo;
ctx.moveTo(moveTo.x, moveTo.y);
break;
case PathCommandTypes.rRect:
final RRectCommand rrectCommand = command as RRectCommand;
_RRectToCanvasRenderer(ctx)
.render(rrectCommand.rrect, startNewPath: false);
break;
case PathCommandTypes.rect:
final RectCommand rectCommand = command as RectCommand;
ctx.rect(rectCommand.x, rectCommand.y, rectCommand.width,
rectCommand.height);
break;
case PathCommandTypes.quadraticCurveTo:
final QuadraticCurveTo quadraticCurveTo = command as QuadraticCurveTo;
ctx.quadraticCurveTo(quadraticCurveTo.x1, quadraticCurveTo.y1,
quadraticCurveTo.x2, quadraticCurveTo.y2);
break;
default:
throw UnimplementedError('Unknown path command $command');
}
final Float32List p = _runBuffer;
final PathRefIterator iter = PathRefIterator(path.pathRef);
int verb = 0;
while ((verb = iter.next(p)) != SPath.kDoneVerb) {
switch (verb) {
case SPath.kMoveVerb:
ctx.moveTo(p[0], p[1]);
break;
case SPath.kLineVerb:
ctx.lineTo(p[2], p[3]);
break;
case SPath.kCubicVerb:
ctx.bezierCurveTo(p[2], p[3], p[4], p[5], p[6], p[7]);
break;
case SPath.kQuadVerb:
ctx.quadraticCurveTo(p[2], p[3], p[4], p[5]);
break;
case SPath.kConicVerb:
final double w = iter.conicWeight;
Conic conic = Conic(p[0], p[1], p[2], p[3], p[4], p[5], w);
List<ui.Offset> points = conic.toQuads();
final int len = points.length;
for (int i = 1; i < len; i += 2) {
final double p1x = points[i].dx;
final double p1y = points[i].dy;
final double p2x = points[i + 1].dx;
final double p2y = points[i + 1].dy;
ctx.quadraticCurveTo(p1x, p1y, p2x, p2y);
}
break;
case SPath.kCloseVerb:
ctx.closePath();
break;
default:
throw UnimplementedError('Unknown path verb $verb');
}
}
}
......
......@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
part of engine;
/// Mixin used by surfaces that clip their contents using an overflowing DOM
......@@ -261,15 +260,15 @@ class PersistedPhysicalShape extends PersistedContainerSurface
}
return;
} else {
final Ellipse? ellipse = path.webOnlyPathAsCircle;
if (ellipse != null) {
final double rx = ellipse.radiusX;
final double ry = ellipse.radiusY;
final ui.Rect? ovalRect = path.webOnlyPathAsCircle;
if (ovalRect != null) {
final double rx = ovalRect.width / 2.0;
final double ry = ovalRect.height / 2.0;
final String borderRadius =
rx == ry ? '${rx}px ' : '${rx}px ${ry}px ';
final html.CssStyleDeclaration style = rootElement!.style;
final double left = ellipse.x - rx;
final double top = ellipse.y - ry;
final double left = ovalRect.left;
final double top = ovalRect.top;
style
..left = '${left}px'
..top = '${top}px'
......
......@@ -78,10 +78,6 @@ class Conic {
return pointList;
}
static bool _between(double a, double b, double c) {
return (a - b) * (c - b) <= 0;
}
// Subdivides a conic and writes to points list.
static void _subdivide(Conic src, int level, List<ui.Offset> pointList) {
assert(level >= 0);
......@@ -99,30 +95,30 @@ class Conic {
final double startY = src.p0y;
final double endY = src.p2y;
final double cpY = src.p1y;
if (_between(startY, cpY, endY)) {
if (SPath.between(startY, cpY, endY)) {
// Ensure that chopped conics maintain their y-order.
final double midY = conic0.p2y;
if (!_between(startY, midY, endY)) {
if (!SPath.between(startY, midY, endY)) {
// The computed midpoint is outside end points, move it to
// closer one.
final double closerY =
(midY - startY).abs() < (midY - endY).abs() ? startY : endY;
conic0.p2y = conic1.p0y = closerY;
}
if (!_between(startY, conic0.p1y, conic0.p2y)) {
if (!SPath.between(startY, conic0.p1y, conic0.p2y)) {
// First control point not between start and end points, move it
// to start.
conic0.p1y = startY;
}
if (!_between(conic1.p0y, conic1.p1y, endY)) {
if (!SPath.between(conic1.p0y, conic1.p1y, endY)) {
// Second control point not between start and end points, move it
// to end.
conic1.p1y = endY;
}
// Verify that conics points are ordered.
assert(_between(startY, conic0.p1y, conic0.p2y));
assert(_between(conic0.p1y, conic0.p2y, conic1.p1y));
assert(_between(conic0.p2y, conic1.p1y, endY));
assert(SPath.between(startY, conic0.p1y, conic0.p2y));
assert(SPath.between(conic0.p1y, conic0.p2y, conic1.p1y));
assert(SPath.between(conic0.p2y, conic1.p1y, endY));
}
--level;
_subdivide(conic0, level, pointList);
......@@ -152,6 +148,118 @@ class Conic {
(p2y + wp1.dy) * scale, p2x, p2y, newW);
}
void chopAtYExtrema(List<Conic> dst) {
double? t = _findYExtrema();
if (t == null) {
dst.add(this);
return;
}
if (!_chopAt(t, dst, cleanupMiddle: true)) {
// If chop can't return finite values, don't chop.
dst.add(this);
return;
}
}
///////////////////////////////////////////////////////////////////////////////
//
// NURB representation for conics. Helpful explanations at:
//
// http://citeseerx.ist.psu.edu/viewdoc/
// download?doi=10.1.1.44.5740&rep=rep1&type=ps
// and
// http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/NURBS/RB-conics.html
//
// F = (A (1 - t)^2 + C t^2 + 2 B (1 - t) t w)
// ------------------------------------------
// ((1 - t)^2 + t^2 + 2 (1 - t) t w)
//
// = {t^2 (P0 + P2 - 2 P1 w), t (-2 P0 + 2 P1 w), P0}
// ------------------------------------------------
// {t^2 (2 - 2 w), t (-2 + 2 w), 1}
//
// F' = 2 (C t (1 + t (-1 + w)) - A (-1 + t) (t (-1 + w) - w) + B (1 - 2 t) w)
//
// t^2 : (2 P0 - 2 P2 - 2 P0 w + 2 P2 w)
// t^1 : (-2 P0 + 2 P2 + 4 P0 w - 4 P1 w)
// t^0 : -2 P0 w + 2 P1 w
//
// We disregard magnitude, so we can freely ignore the denominator of F', and
// divide the numerator by 2
//
// coeff[0] for t^2
// coeff[1] for t^1
// coeff[2] for t^0
//
double? _findYExtrema() {
final double p20 = p2y - p0y;
final double p10 = p1y - p0y;
final double wP10 = fW * p10;
final double coeff0 = fW * p20 - p20;
final double coeff1 = p20 - 2 * wP10;
final double coeff2 = wP10;
final _QuadRoots quadRoots = _QuadRoots();
int rootCount = quadRoots.findRoots(coeff0, coeff1, coeff2);
assert(rootCount == 0 || rootCount == 1);
if (rootCount == 1) {
return quadRoots.root0;
}
return null;
}
bool _chopAt(double t, List<Conic> dst, {bool cleanupMiddle = false}) {
// Map conic to 3D.
final double tx0 = p0x;
final double ty0 = p0y;
final double tz0 = 1;
final double tx1 = p1x * fW;
final double ty1 = p1y * fW;
final double tz1 = fW;
final double tx2 = p2x;
final double ty2 = p2y;
final double tz2 = 1;
// Now interpolate each dimension.
final double dx0 = tx0 + (tx1 - tx0) * t;
final double dx2 = tx1 + (tx2 - tx1) * t;
final double dx1 = dx0 + (dx2 - dx0) * t;
final double dy0 = ty0 + (ty1 - ty0) * t;
final double dy2 = ty1 + (ty2 - ty1) * t;
final double dy1 = dy0 + (dy2 - dy0) * t;
final double dz0 = tz0 + (tz1 - tz0) * t;
final double dz2 = tz1 + (tz2 - tz1) * t;
final double dz1 = dz0 + (dz2 - dz0) * t;
// Compute new weights.
final double root = math.sqrt(dz1);
if (_nearlyEqual(root, 0)) {
return false;
}
final double w0 = dz0 / root;
final double w2 = dz2 / root;
if (_nearlyEqual(dz0, 0) || _nearlyEqual(dz1, 0) || _nearlyEqual(dz2, 0)) {
return false;
}
// Now we can construct the 2 conics by projecting 3D down to 2D.
final double chopPointX = dx1 / dz1;
final double chopPointY = dy1 / dz1;
double cp0y = dy0 / dz0;
double cp1y = dy2 / dz2;
if (cleanupMiddle) {
// Clean-up the middle, since we know t was meant to be at
// an Y-extrema.
cp0y = chopPointY;
cp1y = chopPointY;
}
final Conic conic0 =
Conic(p0x, p0y, dx0 / dz0, cp0y, chopPointX, chopPointY, w0);
final Conic conic1 =
Conic(chopPointX, chopPointY, dx2 / dz2, cp1y, p2x, p2y, w2);
dst.add(conic0);
dst.add(conic1);
return true;
}
/// Computes number of binary subdivisions of the curve given
/// the tolerance.
///
......@@ -186,6 +294,179 @@ class Conic {
}
return pow2;
}
ui.Offset evalTangentAt(double t) {
// The derivative equation returns a zero tangent vector when t is 0 or 1,
// and the control point is equal to the end point.
// In this case, use the conic endpoints to compute the tangent.
if ((t == 0 && p0x == p1x && p0y == p1y) ||
(t == 1 && p1x == p2x && p1y == p2y)) {
return ui.Offset(p2x - p0x, p2y - p0y);
}
double p20x = p2x - p0x;
double p20y = p2y - p0y;
double p10x = p1x - p0x;
double p10y = p1y - p0y;
double cx = fW * p10x;
double cy = fW * p10y;
double ax = fW * p20x - p20x;
double ay = fW * p20y - p20y;
double bx = p20x - cx - cx;
double by = p20y - cy - cy;
_SkQuadCoefficients quadC = _SkQuadCoefficients(ax, ay, bx, by, cx, cy);
return ui.Offset(quadC.evalX(t), quadC.evalY(t));
}
}
double _conicEvalNumerator(
double p0, double p1, double p2, double w, double t) {
assert(t >= 0 && t <= 1);
final double src2w = p1 * w;
final C = p0;
final A = p2 - 2 * src2w + C;
final B = 2 * (src2w - C);
return polyEval(A, B, C, t);
}
double _conicEvalDenominator(double w, double t) {
double B = 2 * (w - 1);
double C = 1;
double A = -B;
return polyEval(A, B, C, t);
}
class _QuadBounds {
double minX = 0;
double minY = 0;
double maxX = 0;
double maxY = 0;
void calculateBounds(Float32List points, int pointIndex) {
final double x1 = points[pointIndex++];
final double y1 = points[pointIndex++];
final double cpX = points[pointIndex++];
final double cpY = points[pointIndex++];
final double x2 = points[pointIndex++];
final double y2 = points[pointIndex++];
minX = math.min(x1, x2);
minY = math.min(y1, y2);
maxX = math.max(x1, x2);
maxY = math.max(y1, y2);
// At extrema's derivative = 0.
// Solve for
// -2x1+2tx1 + 2cpX + 4tcpX + 2tx2 = 0
// -2x1 + 2cpX +2t(x1 + 2cpX + x2) = 0
// t = (x1 - cpX) / (x1 - 2cpX + x2)
double denom = x1 - (2 * cpX) + x2;
if (denom.abs() > SPath.scalarNearlyZero) {
final double t1 = (x1 - cpX) / denom;
if ((t1 >= 0) && (t1 <= 1.0)) {
// Solve (x,y) for curve at t = tx to find extrema
final double tprime = 1.0 - t1;
final double extremaX =
(tprime * tprime * x1) + (2 * t1 * tprime * cpX) + (t1 * t1 * x2);
final double extremaY =
(tprime * tprime * y1) + (2 * t1 * tprime * cpY) + (t1 * t1 * y2);
// Expand bounds.
minX = math.min(minX, extremaX);
maxX = math.max(maxX, extremaX);
minY = math.min(minY, extremaY);
maxY = math.max(maxY, extremaY);
}
}
// Now calculate dy/dt = 0
denom = y1 - (2 * cpY) + y2;
if (denom.abs() > SPath.scalarNearlyZero) {
final double t2 = (y1 - cpY) / denom;
if ((t2 >= 0) && (t2 <= 1.0)) {
final double tprime2 = 1.0 - t2;
final double extrema2X = (tprime2 * tprime2 * x1) +
(2 * t2 * tprime2 * cpX) +
(t2 * t2 * x2);
final double extrema2Y = (tprime2 * tprime2 * y1) +
(2 * t2 * tprime2 * cpY) +
(t2 * t2 * y2);
// Expand bounds.
minX = math.min(minX, extrema2X);
maxX = math.max(maxX, extrema2X);
minY = math.min(minY, extrema2Y);
maxY = math.max(maxY, extrema2Y);
}
}
}
}
class _ConicBounds {
double minX = 0;
double minY = 0;
double maxX = 0;
double maxY = 0;
void calculateBounds(Float32List points, double w, int pointIndex) {
final double x1 = points[pointIndex++];
final double y1 = points[pointIndex++];
final double cpX = points[pointIndex++];
final double cpY = points[pointIndex++];
final double x2 = points[pointIndex++];
final double y2 = points[pointIndex++];
minX = math.min(x1, x2);
minY = math.min(y1, y2);
maxX = math.max(x1, x2);
maxY = math.max(y1, y2);
// {t^2 (P0 + P2 - 2 P1 w), t (-2 P0 + 2 P1 w), P0}
// ------------------------------------------------
// {t^2 (2 - 2 w), t (-2 + 2 w), 1}
// Calculate coefficients and solve root.
_QuadRoots roots = _QuadRoots();
final double P20x = x2 - x1;
final double P10x = cpX - x1;
final double wP10x = w * P10x;
double ax = w * P20x - P20x;
double bx = P20x - 2 * wP10x;
double cx = wP10x;
int n = roots.findRoots(ax, bx, cx);
if (n != 0) {
final double t1 = roots.root0!;
if ((t1 >= 0) && (t1 <= 1.0)) {
final double denom = _conicEvalDenominator(w, t1);
double numerator = _conicEvalNumerator(x1, cpX, x2, w, t1);
final double extremaX = numerator / denom;
numerator = _conicEvalNumerator(y1, cpY, y2, w, t1);
final double extremaY = numerator / denom;
// Expand bounds.
minX = math.min(minX, extremaX);
maxX = math.max(maxX, extremaX);
minY = math.min(minY, extremaY);
maxY = math.max(maxY, extremaY);
}
}
final double P20y = y2 - y1;
final double P10y = cpY - y1;
final double wP10y = w * P10y;
double a = w * P20y - P20y;
double b = P20y - 2 * wP10y;
double c = wP10y;
n = roots.findRoots(a, b, c);
if (n != 0) {
final double t2 = roots.root0!;
if ((t2 >= 0) && (t2 <= 1.0)) {
final double denom = _conicEvalDenominator(w, t2);
double numerator = _conicEvalNumerator(x1, cpX, x2, w, t2);
final double extrema2X = numerator / denom;
numerator = _conicEvalNumerator(y1, cpY, y2, w, t2);
final double extrema2Y = numerator / denom;
// Expand bounds.
minX = math.min(minX, extrema2X);
maxX = math.max(maxX, extrema2X);
minY = math.min(minY, extrema2Y);
maxY = math.max(maxY, extrema2Y);
}
}
}
}
class _ConicPair {
......
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
part of engine;
/// Chops cubic at Y extrema points and writes result to [dest].
///
/// [points] and [dest] are allowed to share underlying storage as long.
int _chopCubicAtYExtrema(Float32List points, Float32List dest) {
final double y0 = points[1];
final double y1 = points[3];
final double y2 = points[5];
final double y3 = points[7];
_QuadRoots _quadRoots = _findCubicExtrema(y0, y1, y2, y3);
final List<double> roots = _quadRoots.roots;
if (roots.isEmpty) {
// No roots, just use input cubic.
return 0;
}
_chopCubicAt(roots, points, dest);
final int rootCount = roots.length;
if (rootCount > 0) {
// Cleanup to ensure Y extrema are flat.
dest[5] = dest[9] = dest[7];
if (rootCount == 2) {
dest[11] = dest[15] = dest[13];
}
}
return rootCount;
}
_QuadRoots _findCubicExtrema(double a, double b, double c, double d) {
// A,B,C scaled by 1/3 to simplify
final double A = d - a + 3 * (b - c);
final double B = 2 * (a - b - b + c);
final double C = b - a;
return _QuadRoots()..findRoots(A, B, C);
}
/// Subdivides cubic curve for a list of t values.
void _chopCubicAt(
List<double> tValues, Float32List points, Float32List outPts) {
if (assertionsEnabled) {
for (int i = 0; i < tValues.length - 1; i++) {
final double tValue = tValues[i];
assert(tValue > 0 && tValue < 1,
'Not expecting to chop curve at start, end points');
}
for (int i = 0; i < tValues.length - 1; i++) {
final double tValue = tValues[i];
final double nextTValue = tValues[i + 1];
assert(
nextTValue > tValue, 'Expecting t value to monotonically increase');
}
}
int rootCount = tValues.length;
if (0 == rootCount) {
for (int i = 0; i < 8; i++) {
outPts[i] = points[i];
}
} else {
// Chop curve at t value and loop through right side of curve
// while normalizing t value based on prior t.
double? t = tValues[0];
int bufferPos = 0;
for (int i = 0; i < rootCount; i++) {
_chopCubicAtT(points, bufferPos, outPts, bufferPos, t!);
if (i == rootCount - 1) {
break;
}
bufferPos += 6;
// watch out in case the renormalized t isn't in range
if ((t = _validUnitDivide(
tValues[i + 1] - tValues[i], 1.0 - tValues[i])) ==
null) {
// Can't renormalize last point, just create a degenerate cubic.
outPts[bufferPos + 4] = outPts[bufferPos + 5] =
outPts[bufferPos + 6] = points[bufferPos + 3];
break;
}
}
}
}
/// Subdivides cubic curve at [t] and writes to [outPts] at position [outIndex].
///
/// The cubic points are read from [points] at [bufferPos] offset.
void _chopCubicAtT(Float32List points, int bufferPos, Float32List outPts,
int outIndex, double t) {
assert(t > 0 && t < 1);
final double p3y = points[bufferPos + 7];
final double p0x = points[bufferPos + 0];
final double p0y = points[bufferPos + 1];
final double p1x = points[bufferPos + 2];
final double p1y = points[bufferPos + 3];
final double p2x = points[bufferPos + 4];
final double p2y = points[bufferPos + 5];
final double p3x = points[bufferPos + 6];
// If startT == 0 chop at end point and return curve.
final double ab1x = _interpolate(p0x, p1x, t);
final double ab1y = _interpolate(p0y, p1y, t);
final double bc1x = _interpolate(p1x, p2x, t);
final double bc1y = _interpolate(p1y, p2y, t);
final double cd1x = _interpolate(p2x, p3x, t);
final double cd1y = _interpolate(p2y, p3y, t);
final double abc1x = _interpolate(ab1x, bc1x, t);
final double abc1y = _interpolate(ab1y, bc1y, t);
final double bcd1x = _interpolate(bc1x, cd1x, t);
final double bcd1y = _interpolate(bc1y, cd1y, t);
final double abcd1x = _interpolate(abc1x, bcd1x, t);
final double abcd1y = _interpolate(abc1y, bcd1y, t);
// Return left side of curve.
outPts[outIndex++] = p0x;
outPts[outIndex++] = p0y;
outPts[outIndex++] = ab1x;
outPts[outIndex++] = ab1y;
outPts[outIndex++] = abc1x;
outPts[outIndex++] = abc1y;
outPts[outIndex++] = abcd1x;
outPts[outIndex++] = abcd1y;
// Return right side of curve.
outPts[outIndex++] = bcd1x;
outPts[outIndex++] = bcd1y;
outPts[outIndex++] = cd1x;
outPts[outIndex++] = cd1y;
outPts[outIndex++] = p3x;
outPts[outIndex++] = p3y;
}
// Returns t at Y for cubic curve. null if y is out of range.
//
// Options are Newton Raphson (quadratic convergence with typically
// 3 iterations or bisection with 16 iterations.
double? _chopMonoAtY(Float32List _buffer, int bufferStartPos, double y) {
// Translate curve points relative to y.
final double ycrv0 = _buffer[1 + bufferStartPos] - y;
final double ycrv1 = _buffer[3 + bufferStartPos] - y;
final double ycrv2 = _buffer[5 + bufferStartPos] - y;
final double ycrv3 = _buffer[7 + bufferStartPos] - y;
// Positive and negative function parameters.
double tNeg, tPos;
// Set initial t points to converge from.
if (ycrv0 < 0) {
if (ycrv3 < 0) {
// Start and end points out of range.
return null;
}
tNeg = 0;
tPos = 1.0;
} else if (ycrv0 > 0) {
tNeg = 1.0;
tPos = 0;
} else {
// Start is at y.
return 0.0;
}
// Bisection / linear convergance.
final double tolerance = 1.0 / 65536;
do {
final double tMid = (tPos + tNeg) / 2.0;
final double y01 = ycrv0 + (ycrv1 - ycrv0) * tMid;
final double y12 = ycrv1 + (ycrv2 - ycrv1) * tMid;
final double y23 = ycrv2 + (ycrv3 - ycrv2) * tMid;
final double y012 = y01 + (y12 - y01) * tMid;
final double y123 = y12 + (y23 - y12) * tMid;
final double y0123 = y012 + (y123 - y012) * tMid;
if (y0123 == 0) {
return tMid;
}
if (y0123 < 0) {
tNeg = tMid;
} else {
tPos = tMid;
}
} while (((tPos - tNeg).abs() > tolerance));
return (tNeg + tPos) / 2;
}
double _evalCubicPts(double c0, double c1, double c2, double c3, double t) {
double A = c3 + 3 * (c1 - c2) - c0;
double B = 3 * (c2 - c1 - c1 + c0);
double C = 3 * (c1 - c0);
double D = c0;
return polyEval4(A, B, C, D, t);
}
// Reusable class to compute bounds without object allocation.
class _CubicBounds {
double minX = 0.0;
double maxX = 0.0;
double minY = 0.0;
double maxY = 0.0;
/// Sets resulting bounds as [minX], [minY], [maxX], [maxY].
///
/// The cubic is defined by 4 points (8 floats) in [points].
void calculateBounds(Float32List points, int pointIndex) {
final double startX = points[pointIndex++];
final double startY = points[pointIndex++];
final double cpX1 = points[pointIndex++];
final double cpY1 = points[pointIndex++];
final double cpX2 = points[pointIndex++];
final double cpY2 = points[pointIndex++];
final double endX = points[pointIndex++];
final double endY = points[pointIndex++];
// Bounding box is defined by all points on the curve where
// monotonicity changes.
minX = math.min(startX, endX);
minY = math.min(startY, endY);
maxX = math.max(startX, endX);
maxY = math.max(startY, endY);
double extremaX;
double extremaY;
double a, b, c;
// Check for simple case of strong ordering before calculating
// extrema
if (!(((startX < cpX1) && (cpX1 < cpX2) && (cpX2 < endX)) ||
((startX > cpX1) && (cpX1 > cpX2) && (cpX2 > endX)))) {
// The extrema point is dx/dt B(t) = 0
// The derivative of B(t) for cubic bezier is a quadratic equation
// with multiple roots
// B'(t) = a*t*t + b*t + c*t
a = -startX + (3 * (cpX1 - cpX2)) + endX;
b = 2 * (startX - (2 * cpX1) + cpX2);
c = -startX + cpX1;
// Now find roots for quadratic equation with known coefficients
// a,b,c
// The roots are (-b+-sqrt(b*b-4*a*c)) / 2a
num s = (b * b) - (4 * a * c);
// If s is negative, we have no real roots
if ((s >= 0.0) && (a.abs() > SPath.scalarNearlyZero)) {
if (s == 0.0) {
// we have only 1 root
final double t = -b / (2 * a);
final double tprime = 1.0 - t;
if ((t >= 0.0) && (t <= 1.0)) {
extremaX = ((tprime * tprime * tprime) * startX) +
((3 * tprime * tprime * t) * cpX1) +
((3 * tprime * t * t) * cpX2) +
(t * t * t * endX);
minX = math.min(extremaX, minX);
maxX = math.max(extremaX, maxX);
}
} else {
// we have 2 roots
s = math.sqrt(s);
double t = (-b - s) / (2 * a);
double tprime = 1.0 - t;
if ((t >= 0.0) && (t <= 1.0)) {
extremaX = ((tprime * tprime * tprime) * startX) +
((3 * tprime * tprime * t) * cpX1) +
((3 * tprime * t * t) * cpX2) +
(t * t * t * endX);
minX = math.min(extremaX, minX);
maxX = math.max(extremaX, maxX);
}
// check 2nd root
t = (-b + s) / (2 * a);
tprime = 1.0 - t;
if ((t >= 0.0) && (t <= 1.0)) {
extremaX = ((tprime * tprime * tprime) * startX) +
((3 * tprime * tprime * t) * cpX1) +
((3 * tprime * t * t) * cpX2) +
(t * t * t * endX);
minX = math.min(extremaX, minX);
maxX = math.max(extremaX, maxX);
}
}
}
}
// Now calc extremes for dy/dt = 0 just like above
if (!(((startY < cpY1) && (cpY1 < cpY2) && (cpY2 < endY)) ||
((startY > cpY1) && (cpY1 > cpY2) && (cpY2 > endY)))) {
// The extrema point is dy/dt B(t) = 0
// The derivative of B(t) for cubic bezier is a quadratic equation
// with multiple roots
// B'(t) = a*t*t + b*t + c*t
a = -startY + (3 * (cpY1 - cpY2)) + endY;
b = 2 * (startY - (2 * cpY1) + cpY2);
c = -startY + cpY1;
// Now find roots for quadratic equation with known coefficients
// a,b,c
// The roots are (-b+-sqrt(b*b-4*a*c)) / 2a
double s = (b * b) - (4 * a * c);
// If s is negative, we have no real roots
if ((s >= 0.0) && (a.abs() > SPath.scalarNearlyZero)) {
if (s == 0.0) {
// we have only 1 root
final double t = -b / (2 * a);
final double tprime = 1.0 - t;
if ((t >= 0.0) && (t <= 1.0)) {
extremaY = ((tprime * tprime * tprime) * startY) +
((3 * tprime * tprime * t) * cpY1) +
((3 * tprime * t * t) * cpY2) +
(t * t * t * endY);
minY = math.min(extremaY, minY);
maxY = math.max(extremaY, maxY);
}
} else {
// we have 2 roots
s = math.sqrt(s);
final double t = (-b - s) / (2 * a);
final double tprime = 1.0 - t;
if ((t >= 0.0) && (t <= 1.0)) {
extremaY = ((tprime * tprime * tprime) * startY) +
((3 * tprime * tprime * t) * cpY1) +
((3 * tprime * t * t) * cpY2) +
(t * t * t * endY);
minY = math.min(extremaY, minY);
maxY = math.max(extremaY, maxY);
}
// check 2nd root
final double t2 = (-b + s) / (2 * a);
final double tprime2 = 1.0 - t2;
if ((t2 >= 0.0) && (t2 <= 1.0)) {
extremaY = ((tprime2 * tprime2 * tprime2) * startY) +
((3 * tprime2 * tprime2 * t2) * cpY1) +
((3 * tprime2 * t2 * t2) * cpY2) +
(t2 * t2 * t2 * endY);
minY = math.min(extremaY, minY);
maxY = math.max(extremaY, maxY);
}
}
}
}
}
}
/// Chops cubic spline at startT and stopT, writes result to buffer.
void _chopCubicBetweenT(
List<double> points, double startT, double stopT, Float32List buffer) {
assert(startT != 0 || stopT != 0);
final double p3y = points[7];
final double p0x = points[0];
final double p0y = points[1];
final double p1x = points[2];
final double p1y = points[3];
final double p2x = points[4];
final double p2y = points[5];
final double p3x = points[6];
// If startT == 0 chop at end point and return curve.
final bool chopStart = startT != 0;
final double t = chopStart ? startT : stopT;
final double ab1x = _interpolate(p0x, p1x, t);
final double ab1y = _interpolate(p0y, p1y, t);
final double bc1x = _interpolate(p1x, p2x, t);
final double bc1y = _interpolate(p1y, p2y, t);
final double cd1x = _interpolate(p2x, p3x, t);
final double cd1y = _interpolate(p2y, p3y, t);
final double abc1x = _interpolate(ab1x, bc1x, t);
final double abc1y = _interpolate(ab1y, bc1y, t);
final double bcd1x = _interpolate(bc1x, cd1x, t);
final double bcd1y = _interpolate(bc1y, cd1y, t);
final double abcd1x = _interpolate(abc1x, bcd1x, t);
final double abcd1y = _interpolate(abc1y, bcd1y, t);
if (!chopStart) {
// Return left side of curve.
buffer[0] = p0x;
buffer[1] = p0y;
buffer[2] = ab1x;
buffer[3] = ab1y;
buffer[4] = abc1x;
buffer[5] = abc1y;
buffer[6] = abcd1x;
buffer[7] = abcd1y;
return;
}
if (stopT == 1) {
// Return right side of curve.
buffer[0] = abcd1x;
buffer[1] = abcd1y;
buffer[2] = bcd1x;
buffer[3] = bcd1y;
buffer[4] = cd1x;
buffer[5] = cd1y;
buffer[6] = p3x;
buffer[7] = p3y;
return;
}
// We chopped at startT, now the right hand side of curve is at
// abcd1, bcd1, cd1, p3x, p3y. Chop this part using endT;
final double endT = (stopT - startT) / (1 - startT);
final double ab2x = _interpolate(abcd1x, bcd1x, endT);
final double ab2y = _interpolate(abcd1y, bcd1y, endT);
final double bc2x = _interpolate(bcd1x, cd1x, endT);
final double bc2y = _interpolate(bcd1y, cd1y, endT);
final double cd2x = _interpolate(cd1x, p3x, endT);
final double cd2y = _interpolate(cd1y, p3y, endT);
final double abc2x = _interpolate(ab2x, bc2x, endT);
final double abc2y = _interpolate(ab2y, bc2y, endT);
final double bcd2x = _interpolate(bc2x, cd2x, endT);
final double bcd2y = _interpolate(bc2y, cd2y, endT);
final double abcd2x = _interpolate(abc2x, bcd2x, endT);
final double abcd2y = _interpolate(abc2y, bcd2y, endT);
buffer[0] = abcd1x;
buffer[1] = abcd1y;
buffer[2] = ab2x;
buffer[3] = ab2y;
buffer[4] = abc2x;
buffer[5] = abc2y;
buffer[6] = abcd2x;
buffer[7] = abcd2y;
}
此差异已折叠。
此差异已折叠。
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
part of engine;
/// Computes tangent at point x,y on a line.
void tangentLine(
Float32List pts, double x, double y, List<ui.Offset> tangents) {
final double y0 = pts[1];
final double y1 = pts[3];
if (!SPath.between(y0, y, y1)) {
return;
}
final double x0 = pts[0];
final double x1 = pts[2];
if (!SPath.between(x0, x, x1)) {
return;
}
final double dx = x1 - x0;
final double dy = y1 - y0;
if (!_nearlyEqual((x - x0) * dy, dx * (y - y0))) {
return;
}
tangents.add(ui.Offset(dx, dy));
}
/// Computes tangent at point x,y on a quadratic curve.
void tangentQuad(
Float32List pts, double x, double y, List<ui.Offset> tangents) {
final double y0 = pts[1];
final double y1 = pts[3];
final double y2 = pts[5];
if (!SPath.between(y0, y, y1) && !SPath.between(y1, y, y2)) {
return;
}
final double x0 = pts[0];
final double x1 = pts[2];
final double x2 = pts[4];
if (!SPath.between(x0, x, x1) && !SPath.between(x1, x, x2)) {
return;
}
final _QuadRoots roots = _QuadRoots();
int n = roots.findRoots(y0 - 2 * y1 + y2, 2 * (y1 - y0), y0 - y);
for (int index = 0; index < n; ++index) {
double t = index == 0 ? roots.root0! : roots.root1!;
double C = x0;
double A = x2 - 2 * x1 + C;
double B = 2 * (x1 - C);
double xt = polyEval(A, B, C, t);
if (!_nearlyEqual(x, xt)) {
continue;
}
tangents.add(_evalQuadTangentAt(x0, y0, x1, y1, x2, y2, t));
}
}
ui.Offset _evalQuadTangentAt(double x0, double y0, double x1, double y1,
double x2, double y2, double t) {
// The derivative of a quad equation is 2(b - a +(a - 2b +c)t).
// This returns a zero tangent vector when t is 0 or 1, and the control
// point is equal to the end point. In this case, use the quad end points to
// compute the tangent.
if ((t == 0 && x0 == x1 && y0 == y1) || (t == 1 && x1 == x2 && y1 == y2)) {
return ui.Offset(x2 - x0, y2 - y0);
}
assert(t >= 0 && t <= 1.0);
double bx = x1 - x0;
double by = y1 - y0;
double ax = x2 - x1 - bx;
double ay = y2 - y1 - by;
double tx = ax * t + bx;
double ty = ay * t + by;
return ui.Offset(tx * 2, ty * 2);
}
/// Computes tangent at point x,y on a conic curve.
void tangentConic(Float32List pts, double x, double y, double weight,
List<ui.Offset> tangents) {
final double y0 = pts[1];
final double y1 = pts[3];
final double y2 = pts[5];
if (!SPath.between(y0, y, y1) && !SPath.between(y1, y, y2)) {
return;
}
final double x0 = pts[0];
final double x1 = pts[2];
final double x2 = pts[4];
if (!SPath.between(x0, x, x1) && !SPath.between(x1, x, x2)) {
return;
}
// Check extrema.
double A = y2;
double B = y1 * weight - y * weight + y;
double C = y0;
// A = a + c - 2*(b*w - yCept*w + yCept)
A += C - 2 * B;
// B = b*w - w * yCept + yCept - a
B -= C;
C -= y;
final _QuadRoots quadRoots = _QuadRoots();
int n = quadRoots.findRoots(A, 2 * B, C);
for (int index = 0; index < n; ++index) {
double t = index == 0 ? quadRoots.root0! : quadRoots.root1!;
double xt = _conicEvalNumerator(x0, x1, x2, weight, t) /
_conicEvalDenominator(weight, t);
if (!_nearlyEqual(x, xt)) {
continue;
}
Conic conic = Conic(x0, y0, x1, y1, x2, y2, weight);
tangents.add(conic.evalTangentAt(t));
}
}
/// Computes tangent at point x,y on a cubic curve.
void tangentCubic(
Float32List pts, double x, double y, List<ui.Offset> tangents) {
final double y3 = pts[7];
final double y0 = pts[1];
final double y1 = pts[3];
final double y2 = pts[5];
if (!SPath.between(y0, y, y1) &&
!SPath.between(y1, y, y2) &&
!SPath.between(y2, y, y3)) {
return;
}
final double x0 = pts[0];
final double x1 = pts[2];
final double x2 = pts[4];
final double x3 = pts[6];
if (!SPath.between(x0, x, x1) &&
!SPath.between(x1, x, x2) &&
!SPath.between(x2, x, x3)) {
return;
}
final Float32List dst = Float32List(20);
int n = _chopCubicAtYExtrema(pts, dst);
for (int i = 0; i <= n; ++i) {
int bufferPos = i * 6;
double? t = _chopMonoAtY(dst, i * 6, y);
if (t == null) {
continue;
}
double xt = _evalCubicPts(dst[bufferPos], dst[bufferPos + 2],
dst[bufferPos + 4], dst[bufferPos + 6], t);
if (!_nearlyEqual(x, xt)) {
continue;
}
tangents.add(_evalCubicTangentAt(dst, bufferPos, t));
}
}
ui.Offset _evalCubicTangentAt(Float32List points, int bufferPos, double t) {
assert(t >= 0 && t <= 1.0);
final double y3 = points[7 + bufferPos];
final double y0 = points[1 + bufferPos];
final double y1 = points[3 + bufferPos];
final double y2 = points[5 + bufferPos];
final double x0 = points[0 + bufferPos];
final double x1 = points[2 + bufferPos];
final double x2 = points[4 + bufferPos];
final double x3 = points[6 + bufferPos];
// The derivative equation returns a zero tangent vector when t is 0 or 1,
// and the adjacent control point is equal to the end point. In this case,
// use the next control point or the end points to compute the tangent.
if ((t == 0 && x0 == x1 && y0 == y1) || (t == 1 && x2 == x3 && y2 == y3)) {
double dx, dy;
if (t == 0) {
dx = x2 - x0;
dy = y2 - y0;
} else {
dx = x3 - x1;
dy = y3 - y1;
}
if (dx == 0 && dy == 0) {
dx = x3 - x0;
dy = y3 - y0;
}
return ui.Offset(dx, dy);
} else {
return _evalCubicDerivative(x0, y0, x1, y1, x2, y2, x3, y3, t);
}
}
ui.Offset _evalCubicDerivative(double x0, double y0, double x1, double y1,
double x2, double y2, double x3, double y3, double t) {
final _SkQuadCoefficients coeff = _SkQuadCoefficients(
x3 + 3 * (x1 - x2) - x0,
y3 + 3 * (y1 - y2) - y0,
2 * (x2 - (2 * x1) + x0),
2 * (y2 - (2 * y1) + y0),
x1 - x0,
y1 - y0,
);
return ui.Offset(coeff.evalX(t), coeff.evalY(t));
}
......@@ -641,7 +641,7 @@ void main() async {
await matchGoldenFile(
'paint_spread_bounds.png',
region: const Rect.fromLTRB(0, 0, 250, 600),
maxDiffRatePercent: 0.0,
maxDiffRatePercent: 0.01,
pixelComparison: PixelComparison.precise,
);
} finally {
......
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册