From 35c8d2d2006af3fb5ae00c46a44f16e05648824f Mon Sep 17 00:00:00 2001 From: ferhatb Date: Fri, 7 Feb 2020 18:14:59 -0800 Subject: [PATCH 1/8] Add pathMeasure --- .../lib/src/engine/surface/path_metrics.dart | 221 +++++++++++------- 1 file changed, 141 insertions(+), 80 deletions(-) diff --git a/lib/web_ui/lib/src/engine/surface/path_metrics.dart b/lib/web_ui/lib/src/engine/surface/path_metrics.dart index 817f7243cfbbc..0475980f466a0 100644 --- a/lib/web_ui/lib/src/engine/surface/path_metrics.dart +++ b/lib/web_ui/lib/src/engine/surface/path_metrics.dart @@ -20,7 +20,7 @@ part of engine; /// are only valid until the next one is obtained. class SurfacePathMetrics extends IterableBase implements ui.PathMetrics { SurfacePathMetrics._(SurfacePath path, bool forceClosed) - : _iterator = SurfacePathMetricIterator._(SurfacePathMetric._(path, forceClosed)); + : _iterator = SurfacePathMetricIterator._(_SurfacePathMeasure(path, forceClosed)); final SurfacePathMetricIterator _iterator; @@ -28,75 +28,31 @@ class SurfacePathMetrics extends IterableBase implements ui.PathM Iterator get iterator => _iterator; } -/// Tracks iteration from one segment of a path to the next for measurement. -class SurfacePathMetricIterator implements Iterator { - SurfacePathMetricIterator._(this._pathMetric); - - SurfacePathMetric _pathMetric; - bool _firstTime = true; - - @override - SurfacePathMetric get current => - _firstTime ? null : _pathMetric._segments.isEmpty ? null : _pathMetric; - - @override - bool moveNext() { - // PathMetric isn't a normal iterable - it's already initialized to its - // first Path. Should only call _moveNext when done with the first one. - if (_firstTime == true) { - _firstTime = false; - return _pathMetric._segments.isNotEmpty; - } else if (_pathMetric?._moveNext() == true) { - return true; - } - _pathMetric = null; - return false; +/// Maintains a single instance of computed segments for set of PathMetric +/// objects exposed through iterator. +class _SurfacePathMeasure { + _SurfacePathMeasure(this._path, this.forceClosed) { + _currentContourIndex = -1; // nextContour will increment this to the zero based index. + _buildSegments(); } -} -// Maximum range value used in curve subdivision using Casteljau algorithm. -const int _kMaxTValue = 0x3FFFFFFF; -// Distance at which we stop subdividing cubic and quadratic curves. -const double _fTolerance = 0.5; + int _currentContourIndex; + int get currentContourIndex => _currentContourIndex; -/// Utilities for measuring a [Path] and extracting subpaths. -/// -/// Iterate over the object returned by [Path.computeMetrics] to obtain -/// [PathMetric] objects. -/// -/// Once created, metrics will only be valid while the iterator is at the given -/// contour. When the next contour's [PathMetric] is obtained, this object -/// becomes invalid. -/// -/// Implementation is based on -/// https://github.com/google/skia/blob/master/src/core/SkContourMeasure.cpp -/// to maintain consistency with native platforms. -class SurfacePathMetric implements ui.PathMetric { final SurfacePath _path; - final bool _forceClosed; - // If the contour ends with a call to [Path.close] (which may // have been implied when using [Path.addRect]) - bool _isClosed; + final bool forceClosed; // Iterator index into [Path.subPaths] int _subPathIndex = 0; List<_PathSegment> _segments; double _contourLength; - /// Create a new empty [Path] object. - SurfacePathMetric._(this._path, this._forceClosed) { - _buildSegments(); - } - - @override - int get contourIndex { - throw UnimplementedError('contourIndex is not implemented in the HTML backend'); + double length(int contourIndex) { + assert(contourIndex <= currentContourIndex, 'Iterator must be advanced before index $contourIndex can be used.'); + return _segments[contourIndex].distance; } - /// Return the total length of the current contour. - @override - double get length => _contourLength; - /// Computes the position of hte current contour at the given offset, and the /// angle of the path at that point. /// @@ -107,9 +63,8 @@ class SurfacePathMetric implements ui.PathMetric { /// Returns null if the contour has zero [length]. /// /// The distance is clamped to the [length] of the current contour. - @override - ui.Tangent getTangentForOffset(double distance) { - final Float32List posTan = _getPosTan(distance); + ui.Tangent getTangentForOffset(int contourIndex, double distance) { + final Float32List posTan = _getPosTan(contourIndex, distance); // first entry == 0 indicates that Skia returned false if (posTan[0] == 0.0) { return null; @@ -119,26 +74,21 @@ class SurfacePathMetric implements ui.PathMetric { } } - Float32List _getPosTan(double distance) => throw UnimplementedError(); + //!!!! TODO + Float32List _getPosTan(int contourIndex, double distance) => throw UnimplementedError(); - /// Given a start and stop distance, return the intervening segment(s). - /// - /// `start` and `end` are pinned to legal values (0..[length]) - /// Returns null if the segment is 0 length or `start` > `stop`. - /// Begin the segment with a moveTo if `startWithMoveTo` is true. - @override - SurfacePath extractPath(double start, double end, {bool startWithMoveTo = true}) => - throw UnimplementedError(); + bool isClosed(int contourIndex) => throw UnimplementedError(); - /// Whether the contour is closed. - /// - /// Returns true if the contour ends with a call to [Path.close] (which may - /// have been implied when using [Path.addRect]) or if `forceClosed` was - /// specified as true in the call to [Path.computeMetrics]. Returns false - /// otherwise. - @override - bool get isClosed { - return _isClosed; + // Move to the next contour in the path. + // + // A path can have a next contour if [Path.moveTo] was called after drawing began. + // Return true if one exists, or false. + bool _nextContour() { + final bool next = _nativeNextContour(); + if (next) { + _currentContourIndex++; + } + return next; } // Move to the next contour in the path. @@ -151,7 +101,7 @@ class SurfacePathMetric implements ui.PathMetric { // [Iterator.current]. In this case, the [PathMetric] is valid before // calling `_moveNext` - `_moveNext` should be called after the first // iteration is done instead of before. - bool _moveNext() { + bool _nativeNextContour() { if (_subPathIndex == (_path.subpaths.length - 1)) { return false; } @@ -160,6 +110,12 @@ class SurfacePathMetric implements ui.PathMetric { return true; } + ui.Path extractPath(int contourIndex, double start, double end, + {bool startWithMoveTo = true}) { + throw UnimplementedError(); + } + + bool _isClosed; void _buildSegments() { _segments = <_PathSegment>[]; _isClosed = false; @@ -316,7 +272,7 @@ class SurfacePathMetric implements ui.PathMetric { throw UnimplementedError('Unknown path command $command'); } } - if (!_isClosed && _forceClosed && _segments.isNotEmpty) { + if (!_isClosed && forceClosed && _segments.isNotEmpty) { _PathSegment firstSegment = _segments.first; lineToHandler(firstSegment.points[0], firstSegment.points[1]); } @@ -512,6 +468,111 @@ class SurfacePathMetric implements ui.PathMetric { } result.distance = distance; } +} + +/// Tracks iteration from one segment of a path to the next for measurement. +class SurfacePathMetricIterator implements Iterator { + SurfacePathMetricIterator._(this._pathMeasure) : assert(_pathMeasure != null); + + SurfacePathMetric _pathMetric; + _SurfacePathMeasure _pathMeasure; + bool _firstTime = true; + + @override + SurfacePathMetric get current => _pathMetric; + + @override + bool moveNext() { + if (_pathMeasure._nextContour()) { + _pathMetric = SurfacePathMetric._(_pathMeasure); + return true; + } + _pathMetric = null; + return false; + } +} + +// Maximum range value used in curve subdivision using Casteljau algorithm. +const int _kMaxTValue = 0x3FFFFFFF; +// Distance at which we stop subdividing cubic and quadratic curves. +const double _fTolerance = 0.5; + +/// Utilities for measuring a [Path] and extracting sub-paths. +/// +/// Iterate over the object returned by [Path.computeMetrics] to obtain +/// [PathMetric] objects. Callers that want to randomly access elements or +/// iterate multiple times should use `path.computeMetrics().toList()`, since +/// [PathMetrics] does not memoize. +/// +/// Once created, the metrics are only valid for the path as it was specified +/// when [Path.computeMetrics] was called. If additional contours are added or +/// any contours are updated, the metrics need to be recomputed. Previously +/// created metrics will still refer to a snapshot of the path at the time they +/// were computed, rather than to the actual metrics for the new mutations to +/// the path. +/// +/// Implementation is based on +/// https://github.com/google/skia/blob/master/src/core/SkContourMeasure.cpp +/// to maintain consistency with native platforms. +class SurfacePathMetric implements ui.PathMetric { + + SurfacePathMetric._(this._measure) + : assert(_measure != null), + length = _measure.length(_measure.currentContourIndex), + isClosed = _measure.isClosed(_measure.currentContourIndex), + contourIndex = _measure.currentContourIndex; + + /// Return the total length of the current contour. + @override + final double length; + + /// Whether the contour is closed. + /// + /// Returns true if the contour ends with a call to [Path.close] (which may + /// have been implied when using methods like [Path.addRect]) or if + /// `forceClosed` was specified as true in the call to [Path.computeMetrics]. + /// Returns false otherwise. + final bool isClosed; + + /// The zero-based index of the contour. + /// + /// [Path] objects are made up of zero or more contours. The first contour is + /// created once a drawing command (e.g. [Path.lineTo]) is issued. A + /// [Path.moveTo] command after a drawing command may create a new contour, + /// although it may not if optimizations are applied that determine the move + /// command did not actually result in moving the pen. + /// + /// This property is only valid with reference to its original iterator and + /// the contours of the path at the time the path's metrics were computed. If + /// additional contours were added or existing contours updated, this metric + /// will be invalid for the current state of the path. + final int contourIndex; + + final _SurfacePathMeasure _measure; + + /// Computes the position of the current contour at the given offset, and the + /// angle of the path at that point. + /// + /// For example, calling this method with a distance of 1.41 for a line from + /// 0.0,0.0 to 2.0,2.0 would give a point 1.0,1.0 and the angle 45 degrees + /// (but in radians). + /// + /// Returns null if the contour has zero [length]. + /// + /// The distance is clamped to the [length] of the current contour. + @override + ui.Tangent getTangentForOffset(double distance) { + return _measure.getTangentForOffset(contourIndex, distance); + } + + /// Given a start and stop distance, return the intervening segment(s). + /// + /// `start` and `end` are pinned to legal values (0..[length]) + /// Returns null if the segment is 0 length or `start` > `stop`. + /// Begin the segment with a moveTo if `startWithMoveTo` is true. + ui.Path extractPath(double start, double end, {bool startWithMoveTo = true}) { + return _measure.extractPath(contourIndex, start, end, startWithMoveTo: startWithMoveTo); + } @override String toString() => 'PathMetric'; From c1b728b99cb4c9cdca7f2332e4fd1b9c6f03d025 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Mon, 10 Feb 2020 15:47:58 -0800 Subject: [PATCH 2/8] Implement tangetAt --- .../lib/src/engine/surface/path_metrics.dart | 211 +++++++++++++++--- 1 file changed, 182 insertions(+), 29 deletions(-) diff --git a/lib/web_ui/lib/src/engine/surface/path_metrics.dart b/lib/web_ui/lib/src/engine/surface/path_metrics.dart index 0475980f466a0..15e3d526b0b60 100644 --- a/lib/web_ui/lib/src/engine/surface/path_metrics.dart +++ b/lib/web_ui/lib/src/engine/surface/path_metrics.dart @@ -4,6 +4,8 @@ part of engine; +const double kEpsilon = 0.000000001; + /// An iterable collection of [PathMetric] objects describing a [Path]. /// /// A [PathMetrics] object is created by using the [Path.computeMetrics] method, @@ -33,24 +35,26 @@ class SurfacePathMetrics extends IterableBase implements ui.PathM class _SurfacePathMeasure { _SurfacePathMeasure(this._path, this.forceClosed) { _currentContourIndex = -1; // nextContour will increment this to the zero based index. - _buildSegments(); } - int _currentContourIndex; - int get currentContourIndex => _currentContourIndex; - final SurfacePath _path; + final List<_PathContourMeasure> _contours = []; + // If the contour ends with a call to [Path.close] (which may // have been implied when using [Path.addRect]) final bool forceClosed; + + int _currentContourIndex; + int get currentContourIndex => _currentContourIndex; + // Iterator index into [Path.subPaths] int _subPathIndex = 0; - List<_PathSegment> _segments; - double _contourLength; + _PathContourMeasure _contourMeasure; double length(int contourIndex) { - assert(contourIndex <= currentContourIndex, 'Iterator must be advanced before index $contourIndex can be used.'); - return _segments[contourIndex].distance; + assert(contourIndex <= + currentContourIndex, 'Iterator must be advanced before index $contourIndex can be used.'); + return _contours[contourIndex].length; } /// Computes the position of hte current contour at the given offset, and the @@ -64,20 +68,10 @@ class _SurfacePathMeasure { /// /// The distance is clamped to the [length] of the current contour. ui.Tangent getTangentForOffset(int contourIndex, double distance) { - final Float32List posTan = _getPosTan(contourIndex, distance); - // first entry == 0 indicates that Skia returned false - if (posTan[0] == 0.0) { - return null; - } else { - return ui.Tangent( - ui.Offset(posTan[1], posTan[2]), ui.Offset(posTan[3], posTan[4])); - } + return _contours[contourIndex].getTangentForOffset(distance); } - //!!!! TODO - Float32List _getPosTan(int contourIndex, double distance) => throw UnimplementedError(); - - bool isClosed(int contourIndex) => throw UnimplementedError(); + bool isClosed(int contourIndex) => _contours[contourIndex].isClosed; // Move to the next contour in the path. // @@ -106,28 +100,88 @@ class _SurfacePathMeasure { return false; } ++_subPathIndex; - _buildSegments(); + _contourMeasure = _PathContourMeasure(_path.subpaths[_subPathIndex], forceClosed); + _contours.add(_contourMeasure); return true; } ui.Path extractPath(int contourIndex, double start, double end, {bool startWithMoveTo = true}) { + _contours[contourIndex].extractPath(start, end, startWithMoveTo); + } +} + +/// Builds segments for a single contour to measure distance, compute tangent +/// and extract a sub path. +class _PathContourMeasure { + _PathContourMeasure(this.subPath, this.forceClosed) { + _buildSegments(); + } + + final List<_PathSegment> _segments = []; + + final Subpath subPath; + final bool forceClosed; + double get length => _contourLength; + bool get isClosed => _isClosed; + + double _contourLength = 0.0; + bool _isClosed = false; + + ui.Tangent getTangentForOffset(double distance) { + if (distance.isNaN) { + return null; + } + // Pin distance to legal range. + if (distance < 0.0) { + distance = 0.0; + } else if (distance > _contourLength) { + distance = _contourLength; + } + + // Binary search through segments to find segment at distance. + if (_segments.isEmpty) { + return null; + } + int lo = 0; + int hi = _segments.length - 1; + while (lo < hi) { + int mid = (lo + hi) >> 1; + if (_segments[mid].distance < distance) { + lo = mid + 1; + } else { + hi = mid; + } + } + if (_segments[hi].distance < distance) { + hi++; + } + return _getPosTan(hi, distance); + } + + ui.Tangent _getPosTan(int segmentIndex, double distance) { + _PathSegment segment = _segments[segmentIndex]; + // Compute distance to segment. Since distance is cumulative to find + // t = 0..1 on the segment, we need to calculate start distance using prior + // segment. + final double startDistance = segmentIndex == 0 ? 0 : _segments[segmentIndex - 1].distance; + final double totalDistance = segment.distance - startDistance; + final double t = totalDistance < kEpsilon ? 0 : + (distance - startDistance) / totalDistance; + return segment.computeTangent(t); + } + + ui.Path extractPath(double start, double end, bool startWithMoveTo) { throw UnimplementedError(); } - bool _isClosed; void _buildSegments() { - _segments = <_PathSegment>[]; + assert(_segments.isEmpty, '_buildSegments should be called once'); _isClosed = false; double distance = 0.0; bool haveSeenMoveTo = false; - if (_path.subpaths.isEmpty) { - _contourLength = 0; - return; - } - final Subpath subpath = _path.subpaths[_subPathIndex]; - final List commands = subpath.commands; + final List commands = subPath.commands; double currentX = 0.0, currentY = 0.0; final Function lineToHandler = (double x, double y) { final double dx = currentX - x; @@ -585,10 +639,109 @@ class _EllipseSegmentResult { _EllipseSegmentResult(); } +// Given a vector dx, dy representing slope, normalize and return as [ui.Offset]. +ui.Offset _normalizeSlope(double dx, double dy) { + final double length = math.sqrt(dx * dx + dy * dy); + return length < kEpsilon ? ui.Offset(0.0, 0.0) : ui.Offset(dx / length, dy / length); +} + class _PathSegment { _PathSegment(this.segmentType, this.distance, this.points); final int segmentType; final double distance; final List points; + + ui.Tangent computeTangent(double t) { + switch (segmentType) { + case PathCommandTypes.lineTo: + // Simple line. Position is simple interpolation from start to end point. + final double xAtDistance = (points[2] * t) + (points[0] * (1.0 - t)); + final double yAtDistance = (points[3] * t) + (points[1] * (1.0 - t)); + return ui.Tangent(ui.Offset(xAtDistance, yAtDistance), + _normalizeSlope(points[2] - points[0], points[3] - points[1])); + case PathCommandTypes.bezierCurveTo: + return tangentForCubicAt(t, points[0], points[1], points[2], points[3], points[4], points[5], points[6], points[7]); + case PathCommandTypes.quadraticCurveTo: + return tangentForQuadAt(t, points[0], points[1], points[2], points[3], points[4], points[5]); + default: + throw UnsupportedError('Invalid segment type'); + } + } + + ui.Tangent tangentForQuadAt(double t, double x0, double y0, double x1, double y1, double x2, double y2) { + assert(t >= 0 && t <= 1); + final _SkQuadCoefficients _quadEval = _SkQuadCoefficients(x0, y0, x1, y1, x2, y2); + final ui.Offset pos = ui.Offset(_quadEval.evalX(t), _quadEval.evalY(t)); + // Derivative of quad curve is 2(b - a + (a - 2b + c)t). + // If control point is at start or end point, this yields 0 for t = 0 and + // t = 1. In that case use the quad end points to compute tangent instead + // of derivative. + final ui.Offset tangentVector = + ((t == 0 && x0 == x1 && y0 == y1) || (t == 0 && x1 == x2 && y1 == y2)) + ? _normalizeSlope(x2 - x0, y2 - y0) + : _normalizeSlope(2 * ((x2 - x0) * t + (x1 - x0)), 2 * ((y2 - y0) * t + (y1 - y0))); + return ui.Tangent(pos, tangentVector); + } + + ui.Tangent tangentForCubicAt(double t, double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3) { + assert(t >= 0 && t <= 1); + final _SkCubicCoefficients _cubicEval = _SkCubicCoefficients(x0, y0, x1, y1, x2, y2, x3, y3); + final ui.Offset pos = ui.Offset(_cubicEval.evalX(t), _cubicEval.evalY(t)); + // Derivative of cubic is zero when t = 0 or 1 and adjacent control point + // is on the start or end point of curve. Use the other control point + // to compute the tangent or if both control points are on end points + // use end points for tangent. + final bool tAtZero = t == 0; + ui.Offset tangentVector; + if ((tAtZero && x0 == x1 && y0 == y1) || (t == 1 && x2 == x3 && y2 == y3)) { + double dx = tAtZero ? x2 - x0 : x3 - x1; + double dy = tAtZero ? y2 - y0 : y3 - y1; + if (dx == 0 && dy == 0) { + dx = x3 - x0; + dy = y3 - y0; + } + tangentVector = _normalizeSlope(dx, dy); + } else { + final double ax = x3 + (3 * (x1 - x2)) - x0; + final double ay = y3 + (3 * (y1 - y2)) - y0; + final double bx = 2 * (x2 - (2 * x1) + x0); + final double by = 2 * (y2 - (2 * y1) + y0); + final double cx = x1 - x0; + final double cy = y1 - y0; + final double tx = (ax * t + bx) * t + cx; + final double ty = (ay * t + by) * t + cy; + tangentVector = _normalizeSlope(tx, ty); + } + } +} + +/// Evaluates A * t^2 + B * t + C = 0 for quadratic curve. +class _SkQuadCoefficients { + _SkQuadCoefficients(double x0, double y0, double x1, double y1, double x2, double y2) + : cx = x0, cy = y0, bx = 2 * (x1 - x0), by = 2 * (y1 - y0), + ax = x2 - (2 * x1) + x0, ay = y2 - (2 * y1) + y0; + final double ax, ay, bx, by, cx, cy; + + double evalX(double t) => (ax * t + bx) * t + cx; + + double evalY(double t) => (ay * t + by) * t + cy; +} + +// Evaluates A * t^3 + B * t^2 + Ct + D = 0 for cubic curve. +class _SkCubicCoefficients { + final double ax, ay, bx, by, cx, cy, dx, dy; + _SkCubicCoefficients(double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3) : + ax = x3 + (3 * (x1 - x2)) - x0, + ay = y3 + (3 * (y1 - y2)) - y0, + bx = 3 * (x2 - (2 * x1) + x0), + by = 3 * (y2 - (2 * y1) + y0), + cx = 3 * (x1 - x0), + cy = 3 * (y1 - y0), + dx = x0, + dy = y0; + + double evalX(double t) => (((ax * t + bx) * t) + cx) * t + dx; + + double evalY(double t) => (((ay * t + by) * t) + cy) * t + dy; } From fd9af85b9eab338e42474739f39404c58d49a249 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Mon, 10 Feb 2020 17:00:38 -0800 Subject: [PATCH 3/8] add extractPath segment iterator --- .../lib/src/engine/surface/path_metrics.dart | 89 ++++++++++++++++--- 1 file changed, 78 insertions(+), 11 deletions(-) diff --git a/lib/web_ui/lib/src/engine/surface/path_metrics.dart b/lib/web_ui/lib/src/engine/surface/path_metrics.dart index 15e3d526b0b60..9090115b387bd 100644 --- a/lib/web_ui/lib/src/engine/surface/path_metrics.dart +++ b/lib/web_ui/lib/src/engine/surface/path_metrics.dart @@ -129,9 +129,18 @@ class _PathContourMeasure { bool _isClosed = false; ui.Tangent getTangentForOffset(double distance) { - if (distance.isNaN) { + final segmentIndex = _segmentIndexAtDistance(distance); + if (segmentIndex == -1) { return null; } + return _getPosTan(segmentIndex, distance); + } + + // Returns segment at [distance]. + int _segmentIndexAtDistance(double distance) { + if (distance.isNaN) { + return -1; + } // Pin distance to legal range. if (distance < 0.0) { distance = 0.0; @@ -141,7 +150,7 @@ class _PathContourMeasure { // Binary search through segments to find segment at distance. if (_segments.isEmpty) { - return null; + return -1; } int lo = 0; int hi = _segments.length - 1; @@ -156,10 +165,10 @@ class _PathContourMeasure { if (_segments[hi].distance < distance) { hi++; } - return _getPosTan(hi, distance); + return hi; } - ui.Tangent _getPosTan(int segmentIndex, double distance) { + _SurfaceTangent _getPosTan(int segmentIndex, double distance) { _PathSegment segment = _segments[segmentIndex]; // Compute distance to segment. Since distance is cumulative to find // t = 0..1 on the segment, we need to calculate start distance using prior @@ -171,7 +180,53 @@ class _PathContourMeasure { return segment.computeTangent(t); } - ui.Path extractPath(double start, double end, bool startWithMoveTo) { + ui.Path extractPath(double startDistance, double stopDistance, bool startWithMoveTo) { + if (startDistance < 0) { + startDistance = 0; + } + if (stopDistance > _contourLength) { + stopDistance = _contourLength; + } + if (startDistance > stopDistance || _segments.isEmpty) { + return null; + } + final ui.Path path = ui.Path(); + int startSegmentIndex = _segmentIndexAtDistance(startDistance); + int stopSegmentIndex = _segmentIndexAtDistance(stopDistance); + if (startSegmentIndex == 0 || stopSegmentIndex == 0) { + return null; + } + int currentSegmentIndex = startSegmentIndex; + _PathSegment seg = _segments[currentSegmentIndex]; + final _SurfaceTangent startTangent = _getPosTan(startSegmentIndex, startDistance); + if (startWithMoveTo) { + final ui.Offset startPosition = startTangent.position; + path.moveTo(startPosition.dx, startPosition.dy); + } + final _SurfaceTangent stopTangent = _getPosTan(stopSegmentIndex, stopDistance); + double startT = startTangent.t; + final double stopT = stopTangent.t; + if (startSegmentIndex == stopSegmentIndex) { + // We only have a single segment that covers the complete distance. + _outputSegmentTo(seg, startT, stopT, path); + } else { + do { + // Write this segment from startT to end (t = 1.0). + _outputSegmentTo(seg, startT, 1.0, path); + // Move to next segment until we hit stop segment. + ++currentSegmentIndex; + seg = _segments[currentSegmentIndex]; + startT = 0; + } while(currentSegmentIndex != stopSegmentIndex); + // Final write last segment from t=0.0 to t=stopT. + _outputSegmentTo(seg, 0.0, stopT, path); + } + return path; + } + + // Chops the segment at startT and endT and writes it to output [path]. + void _outputSegmentTo(_PathSegment segment, double startT, double stopT, + ui.Path path) { throw UnimplementedError(); } @@ -645,6 +700,17 @@ ui.Offset _normalizeSlope(double dx, double dy) { return length < kEpsilon ? ui.Offset(0.0, 0.0) : ui.Offset(dx / length, dy / length); } +class _SurfaceTangent extends ui.Tangent { + const _SurfaceTangent(ui.Offset position, ui.Offset vector, this.t) + : assert(position != null), + assert(vector != null), + assert(t != null), + super(position, vector); + + // Normalized distance of tangent point from start of a contour. + final double t; +} + class _PathSegment { _PathSegment(this.segmentType, this.distance, this.points); @@ -652,14 +718,14 @@ class _PathSegment { final double distance; final List points; - ui.Tangent computeTangent(double t) { + _SurfaceTangent computeTangent(double t) { switch (segmentType) { case PathCommandTypes.lineTo: // Simple line. Position is simple interpolation from start to end point. final double xAtDistance = (points[2] * t) + (points[0] * (1.0 - t)); final double yAtDistance = (points[3] * t) + (points[1] * (1.0 - t)); - return ui.Tangent(ui.Offset(xAtDistance, yAtDistance), - _normalizeSlope(points[2] - points[0], points[3] - points[1])); + return _SurfaceTangent(ui.Offset(xAtDistance, yAtDistance), + _normalizeSlope(points[2] - points[0], points[3] - points[1]), t); case PathCommandTypes.bezierCurveTo: return tangentForCubicAt(t, points[0], points[1], points[2], points[3], points[4], points[5], points[6], points[7]); case PathCommandTypes.quadraticCurveTo: @@ -669,7 +735,7 @@ class _PathSegment { } } - ui.Tangent tangentForQuadAt(double t, double x0, double y0, double x1, double y1, double x2, double y2) { + _SurfaceTangent tangentForQuadAt(double t, double x0, double y0, double x1, double y1, double x2, double y2) { assert(t >= 0 && t <= 1); final _SkQuadCoefficients _quadEval = _SkQuadCoefficients(x0, y0, x1, y1, x2, y2); final ui.Offset pos = ui.Offset(_quadEval.evalX(t), _quadEval.evalY(t)); @@ -681,10 +747,10 @@ class _PathSegment { ((t == 0 && x0 == x1 && y0 == y1) || (t == 0 && x1 == x2 && y1 == y2)) ? _normalizeSlope(x2 - x0, y2 - y0) : _normalizeSlope(2 * ((x2 - x0) * t + (x1 - x0)), 2 * ((y2 - y0) * t + (y1 - y0))); - return ui.Tangent(pos, tangentVector); + return _SurfaceTangent(pos, tangentVector, t); } - ui.Tangent tangentForCubicAt(double t, double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3) { + _SurfaceTangent tangentForCubicAt(double t, double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3) { assert(t >= 0 && t <= 1); final _SkCubicCoefficients _cubicEval = _SkCubicCoefficients(x0, y0, x1, y1, x2, y2, x3, y3); final ui.Offset pos = ui.Offset(_cubicEval.evalX(t), _cubicEval.evalY(t)); @@ -713,6 +779,7 @@ class _PathSegment { final double ty = (ay * t + by) * t + cy; tangentVector = _normalizeSlope(tx, ty); } + return _SurfaceTangent(pos, tangentVector, t); } } From 2b388d7e9d7695b25f9086f013f5ff964108140e Mon Sep 17 00:00:00 2001 From: ferhatb Date: Tue, 11 Feb 2020 15:23:20 -0800 Subject: [PATCH 4/8] Implement extractPath/chopCubicAt/chopQuadAt --- .../lib/src/engine/surface/path_metrics.dart | 158 +++++++++++++++++- 1 file changed, 157 insertions(+), 1 deletion(-) diff --git a/lib/web_ui/lib/src/engine/surface/path_metrics.dart b/lib/web_ui/lib/src/engine/surface/path_metrics.dart index 9090115b387bd..e67f6c32c53e2 100644 --- a/lib/web_ui/lib/src/engine/surface/path_metrics.dart +++ b/lib/web_ui/lib/src/engine/surface/path_metrics.dart @@ -119,6 +119,8 @@ class _PathContourMeasure { } final List<_PathSegment> _segments = []; + // Allocate buffer large enough for returning cubic curve chop result. + static final Float32List _buffer = Float32List(4 * 2); final Subpath subPath; final bool forceClosed; @@ -227,7 +229,24 @@ class _PathContourMeasure { // Chops the segment at startT and endT and writes it to output [path]. void _outputSegmentTo(_PathSegment segment, double startT, double stopT, ui.Path path) { - throw UnimplementedError(); + final List points = segment.points; + switch(segment.segmentType) { + case PathCommandTypes.lineTo: + final double toX = (points[2] * stopT) + (points[0] * (1.0 - stopT)); + final double toY = (points[3] * stopT) + (points[1] * (1.0 - stopT)); + path.lineTo(toX, toY); + break; + case PathCommandTypes.bezierCurveTo: + _chopCubicAt(points, startT, stopT, _buffer); + path.cubicTo(_buffer[2], _buffer[3], _buffer[4], _buffer[5], _buffer[6], _buffer[7]); + break; + case PathCommandTypes.quadraticCurveTo: + _chopQuadAt(points, startT, stopT, _buffer); + path.quadraticBezierTo(_buffer[2], _buffer[3], _buffer[4], _buffer[5]); + break; + default: + throw UnsupportedError('Invalid segment type'); + } } void _buildSegments() { @@ -812,3 +831,140 @@ class _SkCubicCoefficients { double evalY(double t) => (((ay * t + by) * t) + cy) * t + dy; } + +/// Chops cubic spline at startT and stopT, writes result to buffer. +void _chopCubicAt(List 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 = p0x * (1 - t) + p1x * t; + final double ab1y = p0y * (1 - t) + p1y * t; + final double bc1x = p1x * (1 - t) + p2x * t; + final double bc1y = p1y * (1 - t) + p2y * t; + final double cd1x = p2x * (1 - t) + p3x * t; + final double cd1y = p2y * (1 - t) + p3y * t; + final double abc1x = ab1x * (1 - t) + bc1x * t; + final double abc1y = ab1y * (1 - t) + bc1y * t; + final double bcd1x = bc1x * (1 - t) + cd1x * t; + final double bcd1y = bc1y * (1 - t) + cd1y * t; + final double abcd1x = abc1x * (1 - t) + bcd1x * t; + final double abcd1y = abc1y * (1 - t) + 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 = abcd1x * (1 - endT) + bcd1x * endT; + final double ab2y = abcd1y * (1 - endT) + bcd1y * endT; + final double bc2x = bcd1x * (1 - endT) + cd1x * endT; + final double bc2y = bcd1y * (1 - endT) + cd1y * endT; + final double cd2x = cd1x * (1 - endT) + p3x * endT; + final double cd2y = cd1y * (1 - endT) + p3y * endT; + final double abc2x = ab2x * (1 - endT) + bc2x * endT; + final double abc2y = ab2y * (1 - endT) + bc2y * endT; + final double bcd2x = bc2x * (1 - endT) + cd2x * endT; + final double bcd2y = bc2y * (1 - endT) + cd2y * endT; + final double abcd2x = abc2x * (1 - endT) + bcd2x * endT; + final double abcd2y = abc2y * (1 - endT) + 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; +} + +/// Chops quadratic curve at startT and stopT and writes result to buffer. +void _chopQuadAt(List points, double startT, double stopT, + Float32List buffer) { + assert(startT != 0 || stopT != 0); + final double p2y = points[5]; + 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]; + + // If startT == 0 chop at end point and return curve. + final bool chopStart = startT != 0; + final double t = chopStart ? startT : stopT; + + final double ab1x = p0x * (1 - t) + p1x * t; + final double ab1y = p0y * (1 - t) + p1y * t; + final double bc1x = p1x * (1 - t) + p2x * t; + final double bc1y = p1y * (1 - t) + p2y * t; + final double abc1x = ab1x * (1 - t) + bc1x * t; + final double abc1y = ab1y * (1 - t) + bc1y * 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; + return; + } + if (stopT == 1) { + // Return right side of curve. + buffer[0] = abc1x; + buffer[1] = abc1y; + buffer[2] = bc1x; + buffer[3] = bc1y; + buffer[4] = p2x; + buffer[5] = p2y; + return; + } + // We chopped at startT, now the right hand side of curve is at + // abc1x, abc1y, bc1x, bc1y, p2x, p2y + final double endT = (stopT - startT) / (1 - startT); + final double ab2x = abc1x * (1 - endT) + bc1x * endT; + final double ab2y = abc1y * (1 - endT) + bc1y * endT; + final double bc2x = bc1x * (1 - endT) + p2x * endT; + final double bc2y = bc1y * (1 - endT) + p2y * endT; + final double abc2x = ab2x * (1 - endT) + bc2x * endT; + final double abc2y = ab2y * (1 - endT) + bc2y * endT; + + buffer[0] = abc1x; + buffer[1] = abc1y; + buffer[2] = ab2x; + buffer[3] = ab2y; + buffer[4] = abc2x; + buffer[5] = abc2y; + } +} From 7d4dd0c018b29238cc181e19a08efb0a059f2ba7 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Wed, 12 Feb 2020 16:40:28 -0800 Subject: [PATCH 5/8] Wire up extractPath and add tests for cubic/quadratic --- lib/web_ui/dev/goldens_lock.yaml | 2 +- .../lib/src/engine/surface/path_metrics.dart | 107 ++++++------ .../engine/path_metrics_test.dart | 160 ++++++++++++++++++ 3 files changed, 214 insertions(+), 55 deletions(-) create mode 100644 lib/web_ui/test/golden_tests/engine/path_metrics_test.dart diff --git a/lib/web_ui/dev/goldens_lock.yaml b/lib/web_ui/dev/goldens_lock.yaml index 7e5c711178ccc..1464267125a47 100644 --- a/lib/web_ui/dev/goldens_lock.yaml +++ b/lib/web_ui/dev/goldens_lock.yaml @@ -1,2 +1,2 @@ repository: https://github.com/flutter/goldens.git -revision: 956d4e1862b108b31afd06cbf0a767cefc72f4c5 +revision: 69d52af79e30754f1aeca26042c6abd5674cc606 diff --git a/lib/web_ui/lib/src/engine/surface/path_metrics.dart b/lib/web_ui/lib/src/engine/surface/path_metrics.dart index e67f6c32c53e2..07f06b6b78e80 100644 --- a/lib/web_ui/lib/src/engine/surface/path_metrics.dart +++ b/lib/web_ui/lib/src/engine/surface/path_metrics.dart @@ -48,7 +48,7 @@ class _SurfacePathMeasure { int get currentContourIndex => _currentContourIndex; // Iterator index into [Path.subPaths] - int _subPathIndex = 0; + int _subPathIndex = -1; _PathContourMeasure _contourMeasure; double length(int contourIndex) { @@ -107,7 +107,7 @@ class _SurfacePathMeasure { ui.Path extractPath(int contourIndex, double start, double end, {bool startWithMoveTo = true}) { - _contours[contourIndex].extractPath(start, end, startWithMoveTo); + return _contours[contourIndex].extractPath(start, end, startWithMoveTo); } } @@ -195,7 +195,7 @@ class _PathContourMeasure { final ui.Path path = ui.Path(); int startSegmentIndex = _segmentIndexAtDistance(startDistance); int stopSegmentIndex = _segmentIndexAtDistance(stopDistance); - if (startSegmentIndex == 0 || stopSegmentIndex == 0) { + if (startSegmentIndex == -1 || stopSegmentIndex == -1) { return null; } int currentSegmentIndex = startSegmentIndex; @@ -912,59 +912,58 @@ void _chopCubicAt(List points, double startT, double stopT, /// Chops quadratic curve at startT and stopT and writes result to buffer. void _chopQuadAt(List points, double startT, double stopT, Float32List buffer) { - assert(startT != 0 || stopT != 0); - final double p2y = points[5]; - 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]; - - // If startT == 0 chop at end point and return curve. - final bool chopStart = startT != 0; - final double t = chopStart ? startT : stopT; - - final double ab1x = p0x * (1 - t) + p1x * t; - final double ab1y = p0y * (1 - t) + p1y * t; - final double bc1x = p1x * (1 - t) + p2x * t; - final double bc1y = p1y * (1 - t) + p2y * t; - final double abc1x = ab1x * (1 - t) + bc1x * t; - final double abc1y = ab1y * (1 - t) + bc1y * 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; - return; - } - if (stopT == 1) { - // Return right side of curve. - buffer[0] = abc1x; - buffer[1] = abc1y; - buffer[2] = bc1x; - buffer[3] = bc1y; - buffer[4] = p2x; - buffer[5] = p2y; - return; - } - // We chopped at startT, now the right hand side of curve is at - // abc1x, abc1y, bc1x, bc1y, p2x, p2y - final double endT = (stopT - startT) / (1 - startT); - final double ab2x = abc1x * (1 - endT) + bc1x * endT; - final double ab2y = abc1y * (1 - endT) + bc1y * endT; - final double bc2x = bc1x * (1 - endT) + p2x * endT; - final double bc2y = bc1y * (1 - endT) + p2y * endT; - final double abc2x = ab2x * (1 - endT) + bc2x * endT; - final double abc2y = ab2y * (1 - endT) + bc2y * endT; + assert(startT != 0 || stopT != 0); + final double p2y = points[5]; + 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]; + // If startT == 0 chop at end point and return curve. + final bool chopStart = startT != 0; + final double t = chopStart ? startT : stopT; + + final double ab1x = p0x * (1 - t) + p1x * t; + final double ab1y = p0y * (1 - t) + p1y * t; + final double bc1x = p1x * (1 - t) + p2x * t; + final double bc1y = p1y * (1 - t) + p2y * t; + final double abc1x = ab1x * (1 - t) + bc1x * t; + final double abc1y = ab1y * (1 - t) + bc1y * 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; + return; + } + if (stopT == 1) { + // Return right side of curve. buffer[0] = abc1x; buffer[1] = abc1y; - buffer[2] = ab2x; - buffer[3] = ab2y; - buffer[4] = abc2x; - buffer[5] = abc2y; + buffer[2] = bc1x; + buffer[3] = bc1y; + buffer[4] = p2x; + buffer[5] = p2y; + return; } + // We chopped at startT, now the right hand side of curve is at + // abc1x, abc1y, bc1x, bc1y, p2x, p2y + final double endT = (stopT - startT) / (1 - startT); + final double ab2x = abc1x * (1 - endT) + bc1x * endT; + final double ab2y = abc1y * (1 - endT) + bc1y * endT; + final double bc2x = bc1x * (1 - endT) + p2x * endT; + final double bc2y = bc1y * (1 - endT) + p2y * endT; + final double abc2x = ab2x * (1 - endT) + bc2x * endT; + final double abc2y = ab2y * (1 - endT) + bc2y * endT; + + buffer[0] = abc1x; + buffer[1] = abc1y; + buffer[2] = ab2x; + buffer[3] = ab2y; + buffer[4] = abc2x; + buffer[5] = abc2y; } diff --git a/lib/web_ui/test/golden_tests/engine/path_metrics_test.dart b/lib/web_ui/test/golden_tests/engine/path_metrics_test.dart new file mode 100644 index 0000000000000..525b2fb777537 --- /dev/null +++ b/lib/web_ui/test/golden_tests/engine/path_metrics_test.dart @@ -0,0 +1,160 @@ +// 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. + +import 'dart:html' as html; +import 'dart:typed_data'; + +import 'package:ui/ui.dart' hide TextStyle; +import 'package:ui/src/engine.dart'; +import 'package:test/test.dart'; + +import 'package:web_engine_tester/golden_tester.dart'; + +void main() async { + const double screenWidth = 600.0; + const double screenHeight = 800.0; + const Rect screenRect = Rect.fromLTWH(0, 0, screenWidth, screenHeight); + const Color black12Color = Color(0x1F000000); + const Color redAccentColor = Color(0xFFFF1744); + const double kDashLength = 5.0; + + // Commit a recording canvas to a bitmap, and compare with the expected + Future _checkScreenshot(RecordingCanvas rc, String fileName, + {Rect region = const Rect.fromLTWH(0, 0, 500, 500), + bool write = false}) async { + final EngineCanvas engineCanvas = BitmapCanvas(screenRect); + rc.apply(engineCanvas); + + // Wrap in so that our CSS selectors kick in. + final html.Element sceneElement = html.Element.tag('flt-scene'); + try { + sceneElement.append(engineCanvas.rootElement); + html.document.body.append(sceneElement); + await matchGoldenFile('$fileName.png', region: region); + } finally { + // The page is reused across tests, so remove the element after taking the + // Scuba screenshot. + sceneElement.remove(); + } + } + + setUp(() async { + debugEmulateFlutterTesterEnvironment = true; + await webOnlyInitializePlatform(); + webOnlyFontCollection.debugRegisterTestFonts(); + await webOnlyFontCollection.ensureFontsLoaded(); + }); + + // Test for extractPath to draw 5 pixel length dashed line using quad curve. + test('Should draw dashed line on quadratic curve.', () async { + final RecordingCanvas rc = + RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500)); + + final Paint paint = Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 3 + ..color = black12Color; + final Paint redPaint = Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 1 + ..color = redAccentColor; + + final Path path = Path(); + path.moveTo(50, 130); + path.lineTo(150, 20); + double p0x = 150; + double p0y = 20; + double p1x = 240; + double p1y = 120; + double p2x = 320; + double p2y = 25; + path.quadraticBezierTo(p1x, p1y, p2x , p2y); + + rc.drawPath(path, paint); + + final Float32List buffer = Float32List(6); + List points = [p0x, p0y, p1x, p1y, p2x, p2y]; + double t0 = 0.2; + double t1 = 0.7; + + List metrics = path.computeMetrics().toList(); + double totalLength = 0; + for (PathMetric m in metrics) { + totalLength += m.length; + } + Path dashedPath = Path(); + for (final PathMetric measurePath in path.computeMetrics()) { + double distance = totalLength * t0; + bool draw = true; + while (distance < measurePath.length * t1) { + final double length = kDashLength; + if (draw) { + dashedPath.addPath(measurePath.extractPath(distance, distance + length), + Offset.zero); + } + distance += length; + draw = !draw; + } + } + rc.drawPath(dashedPath, redPaint); + await _checkScreenshot(rc, 'path_dash_quadratic'); + }); + + // Test for extractPath to draw 5 pixel length dashed line using cubic curve. + test('Should draw dashed line on cubic curve.', () async { + final RecordingCanvas rc = + RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500)); + + final Paint paint = Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 3 + ..color = black12Color; + final Paint redPaint = Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 1 + ..color = redAccentColor; + + final Path path = Path(); + path.moveTo(50, 130); + path.lineTo(150, 20); + double p0x = 150; + double p0y = 20; + double p1x = 40; + double p1y = 120; + double p2x = 300; + double p2y = 130; + double p3x = 320; + double p3y = 25; + path.cubicTo(p1x, p1y, p2x , p2y, p3x, p3y); + + rc.drawPath(path, paint); + + final Float32List buffer = Float32List(6); + List points = [p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y]; + double t0 = 0.2; + double t1 = 0.7; + + List metrics = path.computeMetrics().toList(); + double totalLength = 0; + for (PathMetric m in metrics) { + totalLength += m.length; + } + Path dashedPath = Path(); + for (final PathMetric measurePath in path.computeMetrics()) { + double distance = totalLength * t0; + bool draw = true; + while (distance < measurePath.length * t1) { + final double length = kDashLength; + if (draw) { + dashedPath.addPath(measurePath.extractPath(distance, distance + length), + Offset.zero); + } + distance += length; + draw = !draw; + } + } + rc.drawPath(dashedPath, redPaint); + await _checkScreenshot(rc, 'path_dash_cubic'); + }); +} From e4a6c718ebc3401e49c9eb514703455949d3cf3b Mon Sep 17 00:00:00 2001 From: ferhatb Date: Wed, 12 Feb 2020 17:11:44 -0800 Subject: [PATCH 6/8] Add Tangent test and dartfmt --- .../lib/src/engine/surface/path_metrics.dart | 137 +++++++++++------- .../engine/path_metrics_test.dart | 64 +++++++- 2 files changed, 138 insertions(+), 63 deletions(-) diff --git a/lib/web_ui/lib/src/engine/surface/path_metrics.dart b/lib/web_ui/lib/src/engine/surface/path_metrics.dart index 07f06b6b78e80..2d9eaf5c6a037 100644 --- a/lib/web_ui/lib/src/engine/surface/path_metrics.dart +++ b/lib/web_ui/lib/src/engine/surface/path_metrics.dart @@ -20,9 +20,11 @@ const double kEpsilon = 0.000000001; /// /// When iterating across a [PathMetrics]' contours, the [PathMetric] objects /// are only valid until the next one is obtained. -class SurfacePathMetrics extends IterableBase implements ui.PathMetrics { +class SurfacePathMetrics extends IterableBase + implements ui.PathMetrics { SurfacePathMetrics._(SurfacePath path, bool forceClosed) - : _iterator = SurfacePathMetricIterator._(_SurfacePathMeasure(path, forceClosed)); + : _iterator = + SurfacePathMetricIterator._(_SurfacePathMeasure(path, forceClosed)); final SurfacePathMetricIterator _iterator; @@ -34,7 +36,8 @@ class SurfacePathMetrics extends IterableBase implements ui.PathM /// objects exposed through iterator. class _SurfacePathMeasure { _SurfacePathMeasure(this._path, this.forceClosed) { - _currentContourIndex = -1; // nextContour will increment this to the zero based index. + _currentContourIndex = + -1; // nextContour will increment this to the zero based index. } final SurfacePath _path; @@ -52,8 +55,8 @@ class _SurfacePathMeasure { _PathContourMeasure _contourMeasure; double length(int contourIndex) { - assert(contourIndex <= - currentContourIndex, 'Iterator must be advanced before index $contourIndex can be used.'); + assert(contourIndex <= currentContourIndex, + 'Iterator must be advanced before index $contourIndex can be used.'); return _contours[contourIndex].length; } @@ -100,7 +103,8 @@ class _SurfacePathMeasure { return false; } ++_subPathIndex; - _contourMeasure = _PathContourMeasure(_path.subpaths[_subPathIndex], forceClosed); + _contourMeasure = + _PathContourMeasure(_path.subpaths[_subPathIndex], forceClosed); _contours.add(_contourMeasure); return true; } @@ -175,14 +179,17 @@ class _PathContourMeasure { // Compute distance to segment. Since distance is cumulative to find // t = 0..1 on the segment, we need to calculate start distance using prior // segment. - final double startDistance = segmentIndex == 0 ? 0 : _segments[segmentIndex - 1].distance; + final double startDistance = + segmentIndex == 0 ? 0 : _segments[segmentIndex - 1].distance; final double totalDistance = segment.distance - startDistance; - final double t = totalDistance < kEpsilon ? 0 : - (distance - startDistance) / totalDistance; + final double t = totalDistance < kEpsilon + ? 0 + : (distance - startDistance) / totalDistance; return segment.computeTangent(t); } - ui.Path extractPath(double startDistance, double stopDistance, bool startWithMoveTo) { + ui.Path extractPath( + double startDistance, double stopDistance, bool startWithMoveTo) { if (startDistance < 0) { startDistance = 0; } @@ -200,12 +207,14 @@ class _PathContourMeasure { } int currentSegmentIndex = startSegmentIndex; _PathSegment seg = _segments[currentSegmentIndex]; - final _SurfaceTangent startTangent = _getPosTan(startSegmentIndex, startDistance); + final _SurfaceTangent startTangent = + _getPosTan(startSegmentIndex, startDistance); if (startWithMoveTo) { final ui.Offset startPosition = startTangent.position; path.moveTo(startPosition.dx, startPosition.dy); } - final _SurfaceTangent stopTangent = _getPosTan(stopSegmentIndex, stopDistance); + final _SurfaceTangent stopTangent = + _getPosTan(stopSegmentIndex, stopDistance); double startT = startTangent.t; final double stopT = stopTangent.t; if (startSegmentIndex == stopSegmentIndex) { @@ -219,7 +228,7 @@ class _PathContourMeasure { ++currentSegmentIndex; seg = _segments[currentSegmentIndex]; startT = 0; - } while(currentSegmentIndex != stopSegmentIndex); + } while (currentSegmentIndex != stopSegmentIndex); // Final write last segment from t=0.0 to t=stopT. _outputSegmentTo(seg, 0.0, stopT, path); } @@ -227,10 +236,10 @@ class _PathContourMeasure { } // Chops the segment at startT and endT and writes it to output [path]. - void _outputSegmentTo(_PathSegment segment, double startT, double stopT, - ui.Path path) { + void _outputSegmentTo( + _PathSegment segment, double startT, double stopT, ui.Path path) { final List points = segment.points; - switch(segment.segmentType) { + switch (segment.segmentType) { case PathCommandTypes.lineTo: final double toX = (points[2] * stopT) + (points[0] * (1.0 - stopT)); final double toY = (points[3] * stopT) + (points[1] * (1.0 - stopT)); @@ -238,7 +247,8 @@ class _PathContourMeasure { break; case PathCommandTypes.bezierCurveTo: _chopCubicAt(points, startT, stopT, _buffer); - path.cubicTo(_buffer[2], _buffer[3], _buffer[4], _buffer[5], _buffer[6], _buffer[7]); + path.cubicTo(_buffer[2], _buffer[3], _buffer[4], _buffer[5], _buffer[6], + _buffer[7]); break; case PathCommandTypes.quadraticCurveTo: _chopQuadAt(points, startT, stopT, _buffer); @@ -266,8 +276,8 @@ class _PathContourMeasure { // actually made it larger, since a very small delta might be > 0, but // still have no effect on distance (if distance >>> delta). if (distance > prevDistance) { - _segments.add(_PathSegment(PathCommandTypes.lineTo, distance, - [currentX, currentY, x, y])); + _segments.add(_PathSegment( + PathCommandTypes.lineTo, distance, [currentX, currentY, x, y])); } currentX = x; currentY = y; @@ -467,10 +477,10 @@ class _PathContourMeasure { final double abcdX = (abcX + bcdX) / 2; final double abcdY = (abcY + bcdY) / 2; final int tHalf = (tMin + tMax) >> 1; - distance = _computeCubicSegments( - x0, y0, abX, abY, abcX, abcY, abcdX, abcdY, distance, tMin, tHalf, segments); - distance = _computeCubicSegments( - abcdX, abcdY, bcdX, bcdY, cdX, cdY, x3, y3, distance, tHalf, tMax, segments); + distance = _computeCubicSegments(x0, y0, abX, abY, abcX, abcY, abcdX, + abcdY, distance, tMin, tHalf, segments); + distance = _computeCubicSegments(abcdX, abcdY, bcdX, bcdY, cdX, cdY, x3, + y3, distance, tHalf, tMax, segments); } else { final double dx = x0 - x3; final double dy = y0 - y3; @@ -478,8 +488,8 @@ class _PathContourMeasure { final double prevDistance = distance; distance += startToEndDistance; if (distance > prevDistance) { - segments.add(_PathSegment(PathCommandTypes.bezierCurveTo, - distance, [x0, y0, x1, y1, x2, y2, x3, y3])); + segments.add(_PathSegment(PathCommandTypes.bezierCurveTo, distance, + [x0, y0, x1, y1, x2, y2, x3, y3])); } } return distance; @@ -520,8 +530,8 @@ class _PathContourMeasure { final double prevDistance = distance; distance += startToEndDistance; if (distance > prevDistance) { - _segments.add(_PathSegment(PathCommandTypes.quadraticCurveTo, - distance, [x0, y0, x1, y1, x2, y2])); + _segments.add(_PathSegment(PathCommandTypes.quadraticCurveTo, distance, + [x0, y0, x1, y1, x2, y2])); } } return distance; @@ -643,7 +653,6 @@ const double _fTolerance = 0.5; /// https://github.com/google/skia/blob/master/src/core/SkContourMeasure.cpp /// to maintain consistency with native platforms. class SurfacePathMetric implements ui.PathMetric { - SurfacePathMetric._(this._measure) : assert(_measure != null), length = _measure.length(_measure.currentContourIndex), @@ -699,7 +708,8 @@ class SurfacePathMetric implements ui.PathMetric { /// Returns null if the segment is 0 length or `start` > `stop`. /// Begin the segment with a moveTo if `startWithMoveTo` is true. ui.Path extractPath(double start, double end, {bool startWithMoveTo = true}) { - return _measure.extractPath(contourIndex, start, end, startWithMoveTo: startWithMoveTo); + return _measure.extractPath(contourIndex, start, end, + startWithMoveTo: startWithMoveTo); } @override @@ -716,7 +726,9 @@ class _EllipseSegmentResult { // Given a vector dx, dy representing slope, normalize and return as [ui.Offset]. ui.Offset _normalizeSlope(double dx, double dy) { final double length = math.sqrt(dx * dx + dy * dy); - return length < kEpsilon ? ui.Offset(0.0, 0.0) : ui.Offset(dx / length, dy / length); + return length < kEpsilon + ? ui.Offset(0.0, 0.0) + : ui.Offset(dx / length, dy / length); } class _SurfaceTangent extends ui.Tangent { @@ -746,32 +758,39 @@ class _PathSegment { return _SurfaceTangent(ui.Offset(xAtDistance, yAtDistance), _normalizeSlope(points[2] - points[0], points[3] - points[1]), t); case PathCommandTypes.bezierCurveTo: - return tangentForCubicAt(t, points[0], points[1], points[2], points[3], points[4], points[5], points[6], points[7]); + return tangentForCubicAt(t, points[0], points[1], points[2], points[3], + points[4], points[5], points[6], points[7]); case PathCommandTypes.quadraticCurveTo: - return tangentForQuadAt(t, points[0], points[1], points[2], points[3], points[4], points[5]); + return tangentForQuadAt(t, points[0], points[1], points[2], points[3], + points[4], points[5]); default: throw UnsupportedError('Invalid segment type'); } } - _SurfaceTangent tangentForQuadAt(double t, double x0, double y0, double x1, double y1, double x2, double y2) { + _SurfaceTangent tangentForQuadAt(double t, double x0, double y0, double x1, + double y1, double x2, double y2) { assert(t >= 0 && t <= 1); - final _SkQuadCoefficients _quadEval = _SkQuadCoefficients(x0, y0, x1, y1, x2, y2); + final _SkQuadCoefficients _quadEval = + _SkQuadCoefficients(x0, y0, x1, y1, x2, y2); final ui.Offset pos = ui.Offset(_quadEval.evalX(t), _quadEval.evalY(t)); // Derivative of quad curve is 2(b - a + (a - 2b + c)t). // If control point is at start or end point, this yields 0 for t = 0 and // t = 1. In that case use the quad end points to compute tangent instead // of derivative. - final ui.Offset tangentVector = - ((t == 0 && x0 == x1 && y0 == y1) || (t == 0 && x1 == x2 && y1 == y2)) - ? _normalizeSlope(x2 - x0, y2 - y0) - : _normalizeSlope(2 * ((x2 - x0) * t + (x1 - x0)), 2 * ((y2 - y0) * t + (y1 - y0))); + final ui.Offset tangentVector = ((t == 0 && x0 == x1 && y0 == y1) || + (t == 0 && x1 == x2 && y1 == y2)) + ? _normalizeSlope(x2 - x0, y2 - y0) + : _normalizeSlope( + 2 * ((x2 - x0) * t + (x1 - x0)), 2 * ((y2 - y0) * t + (y1 - y0))); return _SurfaceTangent(pos, tangentVector, t); } - _SurfaceTangent tangentForCubicAt(double t, double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3) { + _SurfaceTangent tangentForCubicAt(double t, double x0, double y0, double x1, + double y1, double x2, double y2, double x3, double y3) { assert(t >= 0 && t <= 1); - final _SkCubicCoefficients _cubicEval = _SkCubicCoefficients(x0, y0, x1, y1, x2, y2, x3, y3); + final _SkCubicCoefficients _cubicEval = + _SkCubicCoefficients(x0, y0, x1, y1, x2, y2, x3, y3); final ui.Offset pos = ui.Offset(_cubicEval.evalX(t), _cubicEval.evalY(t)); // Derivative of cubic is zero when t = 0 or 1 and adjacent control point // is on the start or end point of curve. Use the other control point @@ -804,9 +823,14 @@ class _PathSegment { /// Evaluates A * t^2 + B * t + C = 0 for quadratic curve. class _SkQuadCoefficients { - _SkQuadCoefficients(double x0, double y0, double x1, double y1, double x2, double y2) - : cx = x0, cy = y0, bx = 2 * (x1 - x0), by = 2 * (y1 - y0), - ax = x2 - (2 * x1) + x0, ay = y2 - (2 * y1) + y0; + _SkQuadCoefficients( + double x0, double y0, double x1, double y1, double x2, double y2) + : cx = x0, + cy = y0, + bx = 2 * (x1 - x0), + by = 2 * (y1 - y0), + ax = x2 - (2 * x1) + x0, + ay = y2 - (2 * y1) + y0; final double ax, ay, bx, by, cx, cy; double evalX(double t) => (ax * t + bx) * t + cx; @@ -817,15 +841,16 @@ class _SkQuadCoefficients { // Evaluates A * t^3 + B * t^2 + Ct + D = 0 for cubic curve. class _SkCubicCoefficients { final double ax, ay, bx, by, cx, cy, dx, dy; - _SkCubicCoefficients(double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3) : - ax = x3 + (3 * (x1 - x2)) - x0, - ay = y3 + (3 * (y1 - y2)) - y0, - bx = 3 * (x2 - (2 * x1) + x0), - by = 3 * (y2 - (2 * y1) + y0), - cx = 3 * (x1 - x0), - cy = 3 * (y1 - y0), - dx = x0, - dy = y0; + _SkCubicCoefficients(double x0, double y0, double x1, double y1, double x2, + double y2, double x3, double y3) + : ax = x3 + (3 * (x1 - x2)) - x0, + ay = y3 + (3 * (y1 - y2)) - y0, + bx = 3 * (x2 - (2 * x1) + x0), + by = 3 * (y2 - (2 * y1) + y0), + cx = 3 * (x1 - x0), + cy = 3 * (y1 - y0), + dx = x0, + dy = y0; double evalX(double t) => (((ax * t + bx) * t) + cx) * t + dx; @@ -833,8 +858,8 @@ class _SkCubicCoefficients { } /// Chops cubic spline at startT and stopT, writes result to buffer. -void _chopCubicAt(List points, double startT, double stopT, - Float32List buffer) { +void _chopCubicAt( + List points, double startT, double stopT, Float32List buffer) { assert(startT != 0 || stopT != 0); final double p3y = points[7]; final double p0x = points[0]; @@ -910,8 +935,8 @@ void _chopCubicAt(List points, double startT, double stopT, } /// Chops quadratic curve at startT and stopT and writes result to buffer. -void _chopQuadAt(List points, double startT, double stopT, - Float32List buffer) { +void _chopQuadAt( + List points, double startT, double stopT, Float32List buffer) { assert(startT != 0 || stopT != 0); final double p2y = points[5]; final double p0x = points[0]; diff --git a/lib/web_ui/test/golden_tests/engine/path_metrics_test.dart b/lib/web_ui/test/golden_tests/engine/path_metrics_test.dart index 525b2fb777537..8bb7c5865d65a 100644 --- a/lib/web_ui/test/golden_tests/engine/path_metrics_test.dart +++ b/lib/web_ui/test/golden_tests/engine/path_metrics_test.dart @@ -8,6 +8,7 @@ import 'dart:typed_data'; import 'package:ui/ui.dart' hide TextStyle; import 'package:ui/src/engine.dart'; import 'package:test/test.dart'; +import '../../matchers.dart'; import 'package:web_engine_tester/golden_tester.dart'; @@ -22,7 +23,7 @@ void main() async { // Commit a recording canvas to a bitmap, and compare with the expected Future _checkScreenshot(RecordingCanvas rc, String fileName, {Rect region = const Rect.fromLTWH(0, 0, 500, 500), - bool write = false}) async { + bool write = false}) async { final EngineCanvas engineCanvas = BitmapCanvas(screenRect); rc.apply(engineCanvas); @@ -46,10 +47,57 @@ void main() async { await webOnlyFontCollection.ensureFontsLoaded(); }); + test('Should calculate tangent on line', () async { + final Path path = Path(); + path.moveTo(50, 130); + path.lineTo(150, 20); + + PathMetric metric = path.computeMetrics().first; + Tangent t = metric.getTangentForOffset(50.0); + expect(t.position.dx, within(from: 83.633, distance: 0.01)); + expect(t.position.dy, within(from: 93.0, distance: 0.01)); + expect(t.vector.dx, within(from: 0.672, distance: 0.01)); + expect(t.vector.dy, within(from: -0.739, distance: 0.01)); + }); + + test('Should calculate tangent on cubic curve', () async { + final Path path = Path(); + double p0x = 150; + double p0y = 20; + double p1x = 240; + double p1y = 120; + double p2x = 320; + double p2y = 25; + path.moveTo(150, 20); + path.quadraticBezierTo(p1x, p1y, p2x, p2y); + PathMetric metric = path.computeMetrics().first; + Tangent t = metric.getTangentForOffset(50.0); + expect(t.position.dx, within(from: 187.25, distance: 0.01)); + expect(t.position.dy, within(from: 53.33, distance: 0.01)); + expect(t.vector.dx, within(from: 0.82, distance: 0.01)); + expect(t.vector.dy, within(from: 0.56, distance: 0.01)); + }); + + test('Should calculate tangent on quadratic curve', () async { + final Path path = Path(); + double p0x = 150; + double p0y = 20; + double p1x = 320; + double p1y = 25; + path.moveTo(150, 20); + path.quadraticBezierTo(p0x, p0y, p1x, p1y); + PathMetric metric = path.computeMetrics().first; + Tangent t = metric.getTangentForOffset(50.0); + expect(t.position.dx, within(from: 199.82, distance: 0.01)); + expect(t.position.dy, within(from: 21.46, distance: 0.01)); + expect(t.vector.dx, within(from: 0.99, distance: 0.01)); + expect(t.vector.dy, within(from: 0.02, distance: 0.01)); + }); + // Test for extractPath to draw 5 pixel length dashed line using quad curve. test('Should draw dashed line on quadratic curve.', () async { final RecordingCanvas rc = - RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500)); + RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500)); final Paint paint = Paint() ..style = PaintingStyle.stroke @@ -69,7 +117,7 @@ void main() async { double p1y = 120; double p2x = 320; double p2y = 25; - path.quadraticBezierTo(p1x, p1y, p2x , p2y); + path.quadraticBezierTo(p1x, p1y, p2x, p2y); rc.drawPath(path, paint); @@ -90,7 +138,8 @@ void main() async { while (distance < measurePath.length * t1) { final double length = kDashLength; if (draw) { - dashedPath.addPath(measurePath.extractPath(distance, distance + length), + dashedPath.addPath( + measurePath.extractPath(distance, distance + length), Offset.zero); } distance += length; @@ -104,7 +153,7 @@ void main() async { // Test for extractPath to draw 5 pixel length dashed line using cubic curve. test('Should draw dashed line on cubic curve.', () async { final RecordingCanvas rc = - RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500)); + RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500)); final Paint paint = Paint() ..style = PaintingStyle.stroke @@ -126,7 +175,7 @@ void main() async { double p2y = 130; double p3x = 320; double p3y = 25; - path.cubicTo(p1x, p1y, p2x , p2y, p3x, p3y); + path.cubicTo(p1x, p1y, p2x, p2y, p3x, p3y); rc.drawPath(path, paint); @@ -147,7 +196,8 @@ void main() async { while (distance < measurePath.length * t1) { final double length = kDashLength; if (draw) { - dashedPath.addPath(measurePath.extractPath(distance, distance + length), + dashedPath.addPath( + measurePath.extractPath(distance, distance + length), Offset.zero); } distance += length; From 3eab07ec9589d5900f9660d1aeb0acae55454d44 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Thu, 13 Feb 2020 15:45:19 -0800 Subject: [PATCH 7/8] Switch to interpolate on chopCubic --- .../lib/src/engine/surface/path_metrics.dart | 75 ++++++++++--------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/lib/web_ui/lib/src/engine/surface/path_metrics.dart b/lib/web_ui/lib/src/engine/surface/path_metrics.dart index 2d9eaf5c6a037..98f0ce3de7c4d 100644 --- a/lib/web_ui/lib/src/engine/surface/path_metrics.dart +++ b/lib/web_ui/lib/src/engine/surface/path_metrics.dart @@ -873,18 +873,18 @@ void _chopCubicAt( final bool chopStart = startT != 0; final double t = chopStart ? startT : stopT; - final double ab1x = p0x * (1 - t) + p1x * t; - final double ab1y = p0y * (1 - t) + p1y * t; - final double bc1x = p1x * (1 - t) + p2x * t; - final double bc1y = p1y * (1 - t) + p2y * t; - final double cd1x = p2x * (1 - t) + p3x * t; - final double cd1y = p2y * (1 - t) + p3y * t; - final double abc1x = ab1x * (1 - t) + bc1x * t; - final double abc1y = ab1y * (1 - t) + bc1y * t; - final double bcd1x = bc1x * (1 - t) + cd1x * t; - final double bcd1y = bc1y * (1 - t) + cd1y * t; - final double abcd1x = abc1x * (1 - t) + bcd1x * t; - final double abcd1y = abc1y * (1 - t) + bcd1y * t; + 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; @@ -912,18 +912,18 @@ void _chopCubicAt( // 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 = abcd1x * (1 - endT) + bcd1x * endT; - final double ab2y = abcd1y * (1 - endT) + bcd1y * endT; - final double bc2x = bcd1x * (1 - endT) + cd1x * endT; - final double bc2y = bcd1y * (1 - endT) + cd1y * endT; - final double cd2x = cd1x * (1 - endT) + p3x * endT; - final double cd2y = cd1y * (1 - endT) + p3y * endT; - final double abc2x = ab2x * (1 - endT) + bc2x * endT; - final double abc2y = ab2y * (1 - endT) + bc2y * endT; - final double bcd2x = bc2x * (1 - endT) + cd2x * endT; - final double bcd2y = bc2y * (1 - endT) + cd2y * endT; - final double abcd2x = abc2x * (1 - endT) + bcd2x * endT; - final double abcd2y = abc2y * (1 - endT) + bcd2y * endT; + 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; @@ -949,12 +949,12 @@ void _chopQuadAt( final bool chopStart = startT != 0; final double t = chopStart ? startT : stopT; - final double ab1x = p0x * (1 - t) + p1x * t; - final double ab1y = p0y * (1 - t) + p1y * t; - final double bc1x = p1x * (1 - t) + p2x * t; - final double bc1y = p1y * (1 - t) + p2y * t; - final double abc1x = ab1x * (1 - t) + bc1x * t; - final double abc1y = ab1y * (1 - t) + bc1y * t; + 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 abc1x = _interpolate(ab1x, bc1x, t); + final double abc1y = _interpolate(ab1y, bc1y, t); if (!chopStart) { // Return left side of curve. buffer[0] = p0x; @@ -978,12 +978,12 @@ void _chopQuadAt( // We chopped at startT, now the right hand side of curve is at // abc1x, abc1y, bc1x, bc1y, p2x, p2y final double endT = (stopT - startT) / (1 - startT); - final double ab2x = abc1x * (1 - endT) + bc1x * endT; - final double ab2y = abc1y * (1 - endT) + bc1y * endT; - final double bc2x = bc1x * (1 - endT) + p2x * endT; - final double bc2y = bc1y * (1 - endT) + p2y * endT; - final double abc2x = ab2x * (1 - endT) + bc2x * endT; - final double abc2y = ab2y * (1 - endT) + bc2y * endT; + final double ab2x = _interpolate(abc1x, bc1x, endT); + final double ab2y = _interpolate(abc1y, bc1y, endT); + final double bc2x = _interpolate(bc1x, p2x, endT); + final double bc2y = _interpolate(bc1y, p2y, endT); + final double abc2x = _interpolate(ab2x, bc2x, endT); + final double abc2y = _interpolate(ab2y, bc2y, endT); buffer[0] = abc1x; buffer[1] = abc1y; @@ -992,3 +992,6 @@ void _chopQuadAt( buffer[4] = abc2x; buffer[5] = abc2y; } + +double _interpolate(double startValue, double endValue, double t) + => (startValue * (1 - t)) + endValue * t; From fb138e357e369ff6f23c637864dbe39f23b16e0c Mon Sep 17 00:00:00 2001 From: ferhatb Date: Fri, 14 Feb 2020 08:59:42 -0800 Subject: [PATCH 8/8] addressed review comments --- lib/web_ui/lib/src/engine/surface/path_metrics.dart | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/web_ui/lib/src/engine/surface/path_metrics.dart b/lib/web_ui/lib/src/engine/surface/path_metrics.dart index 98f0ce3de7c4d..5f641c51c4fea 100644 --- a/lib/web_ui/lib/src/engine/surface/path_metrics.dart +++ b/lib/web_ui/lib/src/engine/surface/path_metrics.dart @@ -36,8 +36,8 @@ class SurfacePathMetrics extends IterableBase /// objects exposed through iterator. class _SurfacePathMeasure { _SurfacePathMeasure(this._path, this.forceClosed) { - _currentContourIndex = - -1; // nextContour will increment this to the zero based index. + // nextContour will increment this to the zero based index. + _currentContourIndex = -1; } final SurfacePath _path; @@ -124,7 +124,8 @@ class _PathContourMeasure { final List<_PathSegment> _segments = []; // Allocate buffer large enough for returning cubic curve chop result. - static final Float32List _buffer = Float32List(4 * 2); + // 2 floats for each coordinate x (start, end & control point 1 & 2). + static final Float32List _buffer = Float32List(8); final Subpath subPath; final bool forceClosed; @@ -779,7 +780,7 @@ class _PathSegment { // t = 1. In that case use the quad end points to compute tangent instead // of derivative. final ui.Offset tangentVector = ((t == 0 && x0 == x1 && y0 == y1) || - (t == 0 && x1 == x2 && y1 == y2)) + (t == 1 && x1 == x2 && y1 == y2)) ? _normalizeSlope(x2 - x0, y2 - y0) : _normalizeSlope( 2 * ((x2 - x0) * t + (x1 - x0)), 2 * ((y2 - y0) * t + (y1 - y0))); @@ -993,5 +994,7 @@ void _chopQuadAt( buffer[5] = abc2y; } +// Interpolate between two doubles (Not using lerpDouble here since it null +// checks and treats values as 0). double _interpolate(double startValue, double endValue, double t) => (startValue * (1 - t)) + endValue * t;