Skip to content

Commit

Permalink
Use newer Skia API for PathMeasure (flutter#7809)
Browse files Browse the repository at this point in the history
dnfield authored Feb 13, 2019
1 parent da56ff9 commit 816921b
Showing 4 changed files with 88 additions and 63 deletions.
72 changes: 34 additions & 38 deletions lib/ui/painting.dart
Original file line number Diff line number Diff line change
@@ -2234,13 +2234,6 @@ class PathMetricIterator implements Iterator<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 (_pathMeasure.currentContourIndex == -1) {
_pathMeasure.currentContourIndex++;
_pathMetric = PathMetric._(_pathMeasure);
return true;
}
if (_pathMeasure._nextContour()) {
_pathMetric = PathMetric._(_pathMeasure);
return true;
@@ -2256,16 +2249,15 @@ class PathMetricIterator implements Iterator<PathMetric> {
/// [PathMetric] objects.
///
/// Once created, the methods on this class will only be valid while the
/// iterator is at the contour for which they were created. When the next
/// contour's [PathMetric] is obtained, the [length] and [isClosed] properties
/// remain valid, but the [getTangentForOffset] and [extractPath] will throw a
/// [StateError].
// TODO(dnfield): Fix this if/when https://bugs.chromium.org/p/skia/issues/detail?id=8721 lands.
/// iterator is at the contour for which they were created. It will also only be
/// valid for the path as it was specifed when [Path.computeMetrics] was called.
/// If additional contours are added or any contours are updated, the metrics
/// need to be recomputed.
class PathMetric {
PathMetric._(this._measure)
: assert(_measure != null),
length = _measure.length,
isClosed = _measure.isClosed,
length = _measure.length(_measure.currentContourIndex),
isClosed = _measure.isClosed(_measure.currentContourIndex),
contourIndex = _measure.currentContourIndex;

/// Return the total length of the current contour.
@@ -2287,10 +2279,10 @@ class PathMetric {
/// 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. If
/// [getTangetForOffset] or [extractPath] are called when this property does
/// not match the actual count of the iterator, those methods will throw a
/// [StateError].
/// 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 _PathMeasure _measure;
@@ -2307,10 +2299,7 @@ class PathMetric {
///
/// The distance is clamped to the [length] of the current contour.
Tangent getTangentForOffset(double distance) {
if (contourIndex != _measure.currentContourIndex) {
throw StateError('This method cannot be invoked once the underlying iterator has advanced.');
}
return _measure.getTangentForOffset(distance);
return _measure.getTangentForOffset(contourIndex, distance);
}

/// Given a start and stop distance, return the intervening segment(s).
@@ -2319,10 +2308,7 @@ class PathMetric {
/// Returns null if the segment is 0 length or `start` > `stop`.
/// Begin the segment with a moveTo if `startWithMoveTo` is true.
Path extractPath(double start, double end, {bool startWithMoveTo: true}) {
if (contourIndex != _measure.currentContourIndex) {
throw StateError('This method cannot be invoked once the underlying iterator has advanced.');
}
return _measure.extractPath(start, end, startWithMoveTo: startWithMoveTo);
return _measure.extractPath(contourIndex, start, end, startWithMoveTo: startWithMoveTo);
}

@override
@@ -2331,15 +2317,20 @@ class PathMetric {

class _PathMeasure extends NativeFieldWrapperClass2 {
_PathMeasure(Path path, bool forceClosed) {
currentContourIndex = -1; // PathMetricIterator will increment this the first time.
currentContourIndex = -1; // nextContour will incremenet this to the zero based index.
_constructor(path, forceClosed);
}
void _constructor(Path path, bool forceClosed) native 'PathMeasure_constructor';

double get length native 'PathMeasure_getLength';
double length(int contourIndex) {
assert(contourIndex <= currentContourIndex, 'Iterator must be advanced before index $contourIndex can be used.');
return _length(contourIndex);
}
double _length(int contourIndex) native 'PathMeasure_getLength';

Tangent getTangentForOffset(double distance) {
final Float32List posTan = _getPosTan(distance);
Tangent getTangentForOffset(int contourIndex, double distance) {
assert(contourIndex <= currentContourIndex, 'Iterator must be advanced before index $contourIndex can be used.');
final Float32List posTan = _getPosTan(contourIndex, distance);
// first entry == 0 indicates that Skia returned false
if (posTan[0] == 0.0) {
return null;
@@ -2350,22 +2341,27 @@ class _PathMeasure extends NativeFieldWrapperClass2 {
);
}
}
Float32List _getPosTan(double distance) native 'PathMeasure_getPosTan';
Float32List _getPosTan(int contourIndex, double distance) native 'PathMeasure_getPosTan';

Path extractPath(double start, double end, {bool startWithMoveTo: true}) native 'PathMeasure_getSegment';
Path extractPath(int contourIndex, double start, double end, {bool startWithMoveTo: true}) {
assert(contourIndex <= currentContourIndex, 'Iterator must be advanced before index $contourIndex can be used.');
return _extractPath(contourIndex, start, end, startWithMoveTo: startWithMoveTo);
}
Path _extractPath(int contourIndex, double start, double end, {bool startWithMoveTo: true}) native 'PathMeasure_getSegment';

bool get isClosed native 'PathMeasure_isClosed';
bool isClosed(int contourIndex) {
assert(contourIndex <= currentContourIndex, 'Iterator must be advanced before index $contourIndex can be used.');
return _isClosed(contourIndex);
}
bool _isClosed(int contourIndex) native 'PathMeasure_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.
//
// This is not exactly congruent with a regular [Iterator.moveNext].
// Typically, [Iterator.moveNext] should be called before accessing the
// [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.
// Before Skia introduced an SkPathContourMeasureIter, this didn't work like
// a normal iterator. Now it does.
bool _nextContour() {
final bool next = _nativeNextContour();
if (next) {
55 changes: 39 additions & 16 deletions lib/ui/painting/path_measure.cc
Original file line number Diff line number Diff line change
@@ -49,9 +49,9 @@ fml::RefPtr<CanvasPathMeasure> CanvasPathMeasure::Create(const CanvasPath* path,
const SkPath skPath = path->path();
SkScalar resScale = 1;
pathMeasure->path_measure_ =
std::make_unique<SkPathMeasure>(skPath, forceClosed, resScale);
std::make_unique<SkContourMeasureIter>(skPath, forceClosed, resScale);
} else {
pathMeasure->path_measure_ = std::make_unique<SkPathMeasure>();
pathMeasure->path_measure_ = std::make_unique<SkContourMeasureIter>();
}
return pathMeasure;
}
@@ -61,52 +61,75 @@ CanvasPathMeasure::CanvasPathMeasure() {}
CanvasPathMeasure::~CanvasPathMeasure() {}

void CanvasPathMeasure::setPath(const CanvasPath* path, bool isClosed) {
const SkPath* skPath = &(path->path());
path_measure_->setPath(skPath, isClosed);
const SkPath& skPath = path->path();
path_measure_->reset(skPath, isClosed);
}

float CanvasPathMeasure::getLength() {
return path_measure_->getLength();
float CanvasPathMeasure::getLength(int contourIndex) {
if (static_cast<std::vector<sk_sp<SkContourMeasure>>::size_type>(
contourIndex) < measures_.size()) {
return measures_[contourIndex]->length();
}
return -1;
}

tonic::Float32List CanvasPathMeasure::getPosTan(float distance) {
tonic::Float32List CanvasPathMeasure::getPosTan(int contourIndex,
float distance) {
tonic::Float32List posTan(Dart_NewTypedData(Dart_TypedData_kFloat32, 5));
posTan[0] = 0; // dart code will check for this for failure
if (static_cast<std::vector<sk_sp<SkContourMeasure>>::size_type>(
contourIndex) >= measures_.size()) {
return posTan;
}

SkPoint pos;
SkVector tan;
bool success = path_measure_->getPosTan(distance, &pos, &tan);
bool success = measures_[contourIndex]->getPosTan(distance, &pos, &tan);

tonic::Float32List posTan(Dart_NewTypedData(Dart_TypedData_kFloat32, 5));
if (success) {
posTan[0] = 1; // dart code will check for this for success
posTan[1] = pos.x();
posTan[2] = pos.y();
posTan[3] = tan.x();
posTan[4] = tan.y();
} else {
posTan[0] = 0; // dart code will check for this for failure
}

return posTan;
}

fml::RefPtr<CanvasPath> CanvasPathMeasure::getSegment(float startD,
fml::RefPtr<CanvasPath> CanvasPathMeasure::getSegment(int contourIndex,
float startD,
float stopD,
bool startWithMoveTo) {
if (static_cast<std::vector<sk_sp<SkContourMeasure>>::size_type>(
contourIndex) >= measures_.size()) {
return CanvasPath::Create();
}
SkPath dst;
bool success =
path_measure_->getSegment(startD, stopD, &dst, startWithMoveTo);
measures_[contourIndex]->getSegment(startD, stopD, &dst, startWithMoveTo);
if (!success) {
return CanvasPath::Create();
} else {
return CanvasPath::CreateFrom(dst);
}
}

bool CanvasPathMeasure::isClosed() {
return path_measure_->isClosed();
bool CanvasPathMeasure::isClosed(int contourIndex) {
if (static_cast<std::vector<sk_sp<SkContourMeasure>>::size_type>(
contourIndex) < measures_.size()) {
return measures_[contourIndex]->isClosed();
}
return false;
}

bool CanvasPathMeasure::nextContour() {
return path_measure_->nextContour();
auto measure = path_measure_->next();
if (measure) {
measures_.push_back(std::move(measure));
return true;
}
return false;
}

} // namespace blink
18 changes: 11 additions & 7 deletions lib/ui/painting/path_measure.h
Original file line number Diff line number Diff line change
@@ -5,10 +5,12 @@
#ifndef FLUTTER_LIB_UI_PAINTING_PATH_MEASURE_H_
#define FLUTTER_LIB_UI_PAINTING_PATH_MEASURE_H_

#include <vector>

#include "flutter/lib/ui/dart_wrapper.h"
#include "flutter/lib/ui/painting/path.h"
#include "third_party/skia/include/core/SkContourMeasure.h"
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/core/SkPathMeasure.h"
#include "third_party/tonic/typed_data/float64_list.h"

namespace tonic {
@@ -30,22 +32,24 @@ class CanvasPathMeasure : public RefCountedDartWrappable<CanvasPathMeasure> {
bool forceClosed);

void setPath(const CanvasPath* path, bool isClosed);
float getLength();
tonic::Float32List getPosTan(float distance);
fml::RefPtr<CanvasPath> getSegment(float startD,
float getLength(int contourIndex);
tonic::Float32List getPosTan(int contourIndex, float distance);
fml::RefPtr<CanvasPath> getSegment(int contourIndex,
float startD,
float stopD,
bool startWithMoveTo);
bool isClosed();
bool isClosed(int contourIndex);
bool nextContour();

static void RegisterNatives(tonic::DartLibraryNatives* natives);

const SkPathMeasure& pathMeasure() const { return *path_measure_; }
const SkContourMeasureIter& pathMeasure() const { return *path_measure_; }

private:
CanvasPathMeasure();

std::unique_ptr<SkPathMeasure> path_measure_;
std::unique_ptr<SkContourMeasureIter> path_measure_;
std::vector<sk_sp<SkContourMeasure>> measures_;
};

} // namespace blink
6 changes: 4 additions & 2 deletions testing/dart/path_test.dart
Original file line number Diff line number Diff line change
@@ -210,9 +210,11 @@ void main() {
print(metrics);
expect(metrics[0].length, 20);
expect(metrics[0].isClosed, true);
expect(() => metrics[0].getTangentForOffset(4.0), throwsStateError);
expect(() => metrics[0].getTangentForOffset(4.0), throwsStateError);
expect(() => metrics[0].getTangentForOffset(4.0), isNotNull);
expect(() => metrics[0].extractPath(4.0, 10.0), isNotNull);
expect(metrics[1].length, 10);
expect(metrics[1].isClosed, false);
expect(() => metrics[1].getTangentForOffset(4.0), isNotNull);
expect(() => metrics[1].extractPath(4.0, 10.0), isNotNull);
});
}

0 comments on commit 816921b

Please sign in to comment.