提交 c249b702 编写于 作者: D Dan Field 提交者: Ian Hickson

Path metrics/getBounds/combinations again (#4957)

* add path measure

* fix typo

* getBound and addPathWithMatrix

* Add myself to Authors, add PathOps

* fix linting issues

* update licenses_flutter to add new files

* Use matrix4 instead of matrix3 for consistency/interop

* put pubspec back

* fix bug in getSegment

* fix typo

* Add return value for PathOp

* refactoring from review

* refactoring from review - still TBD on computeMetrics()

* add doc

* lint issue

* fix computeMetrics, add Path.from

* add missing wireup for clone

* change PathMetrics to iterable, fix bug with angle on Tangent

* prefer std::make_unique

* cleanup docs

* add path measure

* fix typo

* getBound and addPathWithMatrix

* Add myself to Authors, add PathOps

* fix linting issues

* update licenses_flutter to add new files

* Use matrix4 instead of matrix3 for consistency/interop

* put pubspec back

* fix bug in getSegment

* fix typo

* Add return value for PathOp

* refactoring from review

* refactoring from review - still TBD on computeMetrics()

* add doc

* lint issue

* fix computeMetrics, add Path.from

* add missing wireup for clone

* change PathMetrics to iterable, fix bug with angle on Tangent

* prefer std::make_unique

* cleanup docs

* fix iterator bug

* remove unnecessary clone for computeMetrics

* fix some doc issues

* fix PathMeasure iterator, extendWithPath, isClosed, and pubspec.lock

* get rid of orElse; use StateException

* StateError, not StateException

* doc improvements and nits

* add unit tests, fix bugs found during testing

* fix two uncommited doc changes

* one more

* change sign of tangent angle, update docs

* update unit tests for inverted angle

* update tangent to include vector

* Doc fixes

* Fix MSVC compilation and unit test
上级 df255b82
......@@ -36,6 +36,8 @@ source_set("ui") {
"painting/paint.h",
"painting/path.cc",
"painting/path.h",
"painting/path_measure.cc",
"painting/path_measure.h",
"painting/picture.cc",
"painting/picture.h",
"painting/picture_recorder.cc",
......
......@@ -15,6 +15,7 @@
#include "flutter/lib/ui/painting/image_filter.h"
#include "flutter/lib/ui/painting/image_shader.h"
#include "flutter/lib/ui/painting/path.h"
#include "flutter/lib/ui/painting/path_measure.h"
#include "flutter/lib/ui/painting/picture.h"
#include "flutter/lib/ui/painting/picture_recorder.h"
#include "flutter/lib/ui/painting/vertices.h"
......@@ -53,6 +54,7 @@ void DartUI::InitForGlobal() {
CanvasGradient::RegisterNatives(g_natives);
CanvasImage::RegisterNatives(g_natives);
CanvasPath::RegisterNatives(g_natives);
CanvasPathMeasure::RegisterNatives(g_natives);
Codec::RegisterNatives(g_natives);
DartRuntimeHooks::RegisterNatives(g_natives);
FrameInfo::RegisterNatives(g_natives);
......
......@@ -39,6 +39,12 @@ bool _offsetIsValid(Offset offset) {
return true;
}
bool _matrix4IsValid(Float64List matrix4) {
assert(matrix4 != null, 'Matrix4 argument was null.');
assert(matrix4.length == 16, 'Matrix4 must have 16 entries.');
return true;
}
bool _radiusIsValid(Radius radius) {
assert(radius != null, 'Radius argument was null.');
assert(!radius.x.isNaN && !radius.y.isNaN, 'Radius argument contained a NaN value.');
......@@ -1390,6 +1396,62 @@ enum PathFillType {
evenOdd,
}
/// Strategies for combining paths.
///
/// See also:
///
/// * [Path.combine], which uses this enum to decide how to combine two paths.
// Must be kept in sync with SkPathOp
enum PathOperation {
/// Subtract the second path from the first path.
///
/// For example, if the two paths are overlapping circles of equal diameter
/// but differing centers, the result would be a crescent portion of the
/// first circle that was not overlapped by the second circle.
///
/// See also:
///
/// * [reverseDifference], which is the same but subtracting the first path
/// from the second.
difference,
/// Create a new path that is the intersection of the two paths, leaving the
/// overlapping pieces of the path.
///
/// For example, if the two paths are overlapping circles of equal diameter
/// but differing centers, the result would be only the overlapping portion
/// of the two circles.
///
/// See also:
/// * [xor], which is the inverse of this operation
intersect,
/// Create a new path that is the union (inclusive-or) of the two paths.
///
/// For example, if the two paths are overlapping circles of equal diameter
/// but differing centers, the result would be a figure-eight like shape
/// matching the outter boundaries of both circles.
union,
/// Create a new path that is the exclusive-or of the two paths, leaving
/// everything but the overlapping pieces of the path.
///
/// For example, if the two paths are overlapping circles of equal diameter
/// but differing centers, the figure-eight like shape less the overlapping parts
///
/// See also:
/// * [intersect], which is the inverse of this operation
xor,
/// Subtract the first path from the second path.
///
/// For example, if the two paths are overlapping circles of equal diameter
/// but differing centers, the result would be a crescent portion of the
/// second circle that was not overlapped by the first circle.
///
/// See also:
///
/// * [difference], which is the same but subtracting the second path
/// from the frist.
reverseDifference,
}
/// A complex, one-dimensional subset of a plane.
///
/// A path consists of a number of subpaths, and a _current point_.
......@@ -1412,6 +1474,15 @@ class Path extends NativeFieldWrapperClass2 {
Path() { _constructor(); }
void _constructor() native 'Path_constructor';
/// Creates a copy of another [Path].
///
/// This copy is fast and does not require additional memory unless either
/// the `source` path or the path returned by this constructor are modified.
factory Path.from(Path source) {
return source._clone();
}
Path _clone() native 'Path_clone';
/// Determines how the interior of this path is calculated.
///
/// Defaults to the non-zero winding rule, [PathFillType.nonZero].
......@@ -1609,23 +1680,43 @@ class Path extends NativeFieldWrapperClass2 {
}
void _addRRect(Float32List rrect) native 'Path_addRRect';
/// Adds a new subpath that consists of the given path offset by the given
/// offset.
void addPath(Path path, Offset offset) {
/// Adds a new subpath that consists of the given `path` offset by the given
/// `offset`.
///
/// If `matrix4` is specified, the path will be transformed by this matrix
/// after the matrix is translated by the given offset. The matrix is a 4x4
/// matrix stored in column major order.
void addPath(Path path, Offset offset, {Float64List matrix4}) {
assert(path != null); // path is checked on the engine side
assert(_offsetIsValid(offset));
_addPath(path, offset.dx, offset.dy);
if (matrix4 != null) {
assert(_matrix4IsValid(matrix4));
_addPathWithMatrix(path, offset.dx, offset.dy, matrix4);
} else {
_addPath(path, offset.dx, offset.dy);
}
}
void _addPath(Path path, double dx, double dy) native 'Path_addPath';
void _addPathWithMatrix(Path path, double dx, double dy, Float64List matrix) native 'Path_addPathWithMatrix';
/// Adds the given path to this path by extending the current segment of this
/// path with the the first segment of the given path.
void extendWithPath(Path path, Offset offset) {
///
/// If `matrix4` is specified, the path will be transformed by this matrix
/// after the matrix is translated by the given `offset`. The matrix is a 4x4
/// matrix stored in column major order.
void extendWithPath(Path path, Offset offset, {Float64List matrix4}) {
assert(path != null); // path is checked on the engine side
assert(_offsetIsValid(offset));
_extendWithPath(path, offset.dx, offset.dy);
if (matrix4 != null) {
assert(_matrix4IsValid(matrix4));
_extendWithPathAndMatrix(path, offset.dx, offset.dy, matrix4);
} else {
_extendWithPath(path, offset.dx, offset.dy);
}
}
void _extendWithPath(Path path, double dx, double dy) native 'Path_extendWithPath';
void _extendWithPathAndMatrix(Path path, double dx, double dy, Float64List matrix) native 'Path_extendWithPathAndMatrix';
/// Closes the last subpath, as if a straight line had been drawn
/// from the current point to the first point of the subpath.
......@@ -1660,12 +1751,207 @@ class Path extends NativeFieldWrapperClass2 {
/// Returns a copy of the path with all the segments of every
/// subpath transformed by the given matrix.
Path transform(Float64List matrix4) {
assert(matrix4 != null);
if (matrix4.length != 16)
throw new ArgumentError('"matrix4" must have 16 entries.');
assert(_matrix4IsValid(matrix4));
return _transform(matrix4);
}
Path _transform(Float64List matrix4) native 'Path_transform';
/// Computes the bounding rectangle for this path.
Rect getBounds() {
final Float32List rect = _getBounds();
return new Rect.fromLTRB(rect[0], rect[1], rect[2], rect[3]);
}
Float32List _getBounds() native 'Path_getBounds';
/// Combines the two paths according to the manner specified by the given
/// `operation`.
///
/// The resulting path will be constructed from non-overlapping contours. The
/// curve order is reduced where possible so that cubics may be turned into
/// quadratics, and quadratics maybe turned into lines.
static Path combine(PathOperation operation, Path path1, Path path2) {
assert(path1 != null);
assert(path2 != null);
final Path path = new Path();
if (path._op(path1, path2, operation.index)) {
return path;
}
throw new StateError('Path.combine() failed. This may be due an invalid path; in particular, check for NaN values.');
}
bool _op(Path path1, Path path2, int operation) native 'Path_op';
/// Creates a [PathMetrics] object for this path.
///
/// If `forceClosed` is set to true, the contours of the path will be measured
/// as if they had been closed, even if they were not explicitly closed.
PathMetrics computeMetrics({bool forceClosed: false}) {
return new PathMetrics._(this, forceClosed);
}
}
/// The geometric description of a tangent: the angle at a point.
///
/// See also:
/// * [PathMetric.getTangentForOffset], which returns the tangent of an offset along a path.
class Tangent {
/// Creates a [Tangent] with the given values.
///
/// The arguments must not be null.
const Tangent(this.position, this.vector)
: assert(position != null),
assert(vector != null);
/// Creates a [Tangent] based on the angle rather than the vector.
///
/// The [vector] is computed to be the unit vector at the given angle, interpreted
/// as clockwise radians from the x axis.
factory Tangent.fromAngle(Offset position, double angle) {
return new Tangent(position, new Offset(math.cos(angle), math.sin(angle)));
}
/// Position of the tangent.
///
/// When used with [PathMetric.getTangentForOffset], this represents the precise
/// position that the given offset along the path corresponds to.
final Offset position;
/// The vector of the curve at [position].
///
/// When used with [PathMetric.getTangentForOffset], this is the vector of the
/// curve that is at the given offset along the path (i.e. the direction of the
/// curve at [position]).
final Offset vector;
/// The direction of the curve at [position].
///
/// When used with [PathMetric.getTangentForOffset], this is the angle of the
/// curve that is the given offset along the path (i.e. the direction of the
/// curve at [position]).
///
/// This value is in radians, with 0.0 meaning pointing along the x axis in
/// the positive x-axis direction, positive numbers pointing downward toward
/// the negative y-axis, i.e. in a clockwise direction, and negative numbers
/// pointing upward toward the positive y-axis, i.e. in a counter-clockwise
/// direction.
// flip the sign to be consistent with [Path.arcTo]'s `sweepAngle`
double get angle => -math.atan2(vector.dy, vector.dx);
}
/// An iterable collection of [PathMetric] objects describing a [Path].
///
/// A [PathMetrics] object is created by using the [Path.computeMetrics] method,
/// and represents the path as it stood at the time of the call. Subsequent
/// modifications of the path do not affect the [PathMetrics] object.
///
/// Each path metric corresponds to a segment, or contour, of a path.
///
/// For example, a path consisting of a [Path.lineTo], a [Path.moveTo], and
/// another [Path.lineTo] will contain two contours and thus be represented by
/// two [PathMetric] objects.
///
/// When iterating across a [PathMetrics]' contours, the [PathMetric] objects are only
/// valid until the next one is obtained.
class PathMetrics extends collection.IterableBase<PathMetric> {
PathMetrics._(Path path, bool forceClosed) :
_iterator = new PathMetricIterator._(new PathMetric._(path, forceClosed));
final Iterator<PathMetric> _iterator;
@override
Iterator<PathMetric> get iterator => _iterator;
}
/// Tracks iteration from one segment of a path to the next for measurement.
class PathMetricIterator implements Iterator<PathMetric> {
PathMetricIterator._(this._pathMetric);
PathMetric _pathMetric;
bool _firstTime = true;
@override
PathMetric get current => _firstTime ? 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 true;
} else if (_pathMetric?._moveNext() == true) {
return true;
}
_pathMetric = null;
return false;
}
}
/// 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.
class PathMetric extends NativeFieldWrapperClass2 {
/// Create a new empty [Path] object.
PathMetric._(Path path, bool forceClosed) { _constructor(path, forceClosed); }
void _constructor(Path path, bool forceClosed) native 'PathMeasure_constructor';
/// Return the total length of the current contour.
double get length native 'PathMeasure_getLength';
/// Computes the position of hte 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.
Tangent getTangentForOffset(double distance) {
final Float32List posTan = _getPosTan(distance);
// first entry == 0 indicates that Skia returned false
if (posTan[0] == 0.0) {
return null;
} else {
return new Tangent(
new Offset(posTan[1], posTan[2]),
new Offset(posTan[3], posTan[4])
);
}
}
Float32List _getPosTan(double distance) native 'PathMeasure_getPosTan';
/// 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.
Path extractPath(double start, double end, {bool startWithMoveTo: true}) native 'PathMeasure_getSegment';
/// 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.
bool get isClosed 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.
bool _moveNext() native 'PathMeasure_nextContour';
}
/// Styles to use for blurs in [MaskFilter] objects.
......
......@@ -38,6 +38,7 @@ IMPLEMENT_WRAPPERTYPEINFO(ui, Path);
V(Path, contains) \
V(Path, cubicTo) \
V(Path, extendWithPath) \
V(Path, extendWithPathAndMatrix) \
V(Path, getFillType) \
V(Path, lineTo) \
V(Path, moveTo) \
......@@ -51,7 +52,11 @@ IMPLEMENT_WRAPPERTYPEINFO(ui, Path);
V(Path, reset) \
V(Path, setFillType) \
V(Path, shift) \
V(Path, transform)
V(Path, transform) \
V(Path, getBounds) \
V(Path, addPathWithMatrix) \
V(Path, op) \
V(Path, clone)
FOR_EACH_BINDING(DART_NATIVE_CALLBACK)
......@@ -207,6 +212,17 @@ void CanvasPath::addPath(CanvasPath* path, double dx, double dy) {
path_.addPath(path->path(), dx, dy, SkPath::kAppend_AddPathMode);
}
void CanvasPath::addPathWithMatrix(CanvasPath* path, double dx, double dy, tonic::Float64List& matrix4) {
if (!path)
Dart_ThrowException(ToDart("Path.addPathWithMatrix called with non-genuine Path."));
SkMatrix matrix = ToSkMatrix(matrix4);
matrix.setTranslateX(matrix.getTranslateX() + dx);
matrix.setTranslateY(matrix.getTranslateY() + dy);
path_.addPath(path->path(), matrix, SkPath::kAppend_AddPathMode);
matrix4.Release();
}
void CanvasPath::extendWithPath(CanvasPath* path, double dx, double dy) {
if (!path)
Dart_ThrowException(
......@@ -214,6 +230,17 @@ void CanvasPath::extendWithPath(CanvasPath* path, double dx, double dy) {
path_.addPath(path->path(), dx, dy, SkPath::kExtend_AddPathMode);
}
void CanvasPath::extendWithPathAndMatrix(CanvasPath* path, double dx, double dy, tonic::Float64List& matrix4) {
if (!path)
Dart_ThrowException(ToDart("Path.addPathWithMatrix called with non-genuine Path."));
SkMatrix matrix = ToSkMatrix(matrix4);
matrix.setTranslateX(matrix.getTranslateX() + dx);
matrix.setTranslateY(matrix.getTranslateY() + dy);
path_.addPath(path->path(), matrix, SkPath::kExtend_AddPathMode);
matrix4.Release();
}
void CanvasPath::close() {
path_.close();
}
......@@ -239,4 +266,27 @@ fxl::RefPtr<CanvasPath> CanvasPath::transform(tonic::Float64List& matrix4) {
return path;
}
tonic::Float32List CanvasPath::getBounds() {
tonic::Float32List rect(Dart_NewTypedData(Dart_TypedData_kFloat32, 4));
const SkRect& bounds = path_.getBounds();
rect[0] = bounds.left();
rect[1] = bounds.top();
rect[2] = bounds.right();
rect[3] = bounds.bottom();
return rect;
}
bool CanvasPath::op(CanvasPath* path1, CanvasPath* path2, int operation) {
return Op(path1->path(), path2->path(), (SkPathOp)operation, &path_);
}
fxl::RefPtr<CanvasPath> CanvasPath::clone() {
fxl::RefPtr<CanvasPath> path = CanvasPath::Create();
// per Skia docs, this will create a fast copy
// data is shared until the source path or dest path are mutated
path->path_ = path_;
return path;
}
} // namespace blink
......@@ -10,6 +10,7 @@
#include "lib/tonic/typed_data/float32_list.h"
#include "lib/tonic/typed_data/float64_list.h"
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/pathops/SkPathOps.h"
namespace tonic {
class DartLibraryNatives;
......@@ -28,6 +29,12 @@ class CanvasPath : public fxl::RefCountedThreadSafe<CanvasPath>,
return fxl::MakeRefCounted<CanvasPath>();
}
static fxl::RefPtr<CanvasPath> CreateFrom(const SkPath& src) {
fxl::RefPtr<CanvasPath> path = CanvasPath::Create();
path->path_ = src;
return path;
}
int getFillType();
void setFillType(int fill_type);
......@@ -78,12 +85,23 @@ class CanvasPath : public fxl::RefCountedThreadSafe<CanvasPath>,
void addPolygon(const tonic::Float32List& points, bool close);
void addRRect(const RRect& rrect);
void addPath(CanvasPath* path, double dx, double dy);
void addPathWithMatrix(CanvasPath* path,
double dx,
double dy,
tonic::Float64List& matrix4);
void extendWithPath(CanvasPath* path, double dx, double dy);
void extendWithPathAndMatrix(CanvasPath* path,
double dx,
double dy,
tonic::Float64List& matrix4);
void close();
void reset();
bool contains(double x, double y);
fxl::RefPtr<CanvasPath> shift(double dx, double dy);
fxl::RefPtr<CanvasPath> transform(tonic::Float64List& matrix4);
tonic::Float32List getBounds();
bool op(CanvasPath* path1, CanvasPath* path2, int operation);
fxl::RefPtr<CanvasPath> clone();
const SkPath& path() const { return path_; }
......
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/lib/ui/painting/path_measure.h"
#include <math.h>
#include "flutter/lib/ui/painting/matrix.h"
#include "lib/tonic/converter/dart_converter.h"
#include "lib/tonic/dart_args.h"
#include "lib/tonic/dart_binding_macros.h"
#include "lib/tonic/dart_library_natives.h"
using tonic::ToDart;
namespace blink {
typedef CanvasPathMeasure PathMeasure;
static void PathMeasure_constructor(Dart_NativeArguments args) {
DartCallConstructor(&CanvasPathMeasure::Create, args);
}
IMPLEMENT_WRAPPERTYPEINFO(ui, PathMeasure);
#define FOR_EACH_BINDING(V) \
V(PathMeasure, setPath) \
V(PathMeasure, getLength) \
V(PathMeasure, getPosTan) \
V(PathMeasure, getSegment) \
V(PathMeasure, isClosed) \
V(PathMeasure, nextContour)
FOR_EACH_BINDING(DART_NATIVE_CALLBACK)
void CanvasPathMeasure::RegisterNatives(tonic::DartLibraryNatives* natives) {
natives->Register(
{{"PathMeasure_constructor", PathMeasure_constructor, 3, true},
FOR_EACH_BINDING(DART_REGISTER_NATIVE)});
}
fxl::RefPtr<CanvasPathMeasure> CanvasPathMeasure::Create(const CanvasPath* path,
bool forceClosed) {
fxl::RefPtr<CanvasPathMeasure> pathMeasure =
fxl::MakeRefCounted<CanvasPathMeasure>();
if (path) {
const SkPath skPath = path->path();
SkScalar resScale = 1;
pathMeasure->path_measure_ =
std::make_unique<SkPathMeasure>(skPath, forceClosed, resScale);
} else {
pathMeasure->path_measure_ = std::make_unique<SkPathMeasure>();
}
return pathMeasure;
}
CanvasPathMeasure::CanvasPathMeasure() {}
CanvasPathMeasure::~CanvasPathMeasure() {}
void CanvasPathMeasure::setPath(const CanvasPath* path, bool isClosed) {
const SkPath* skPath = &(path->path());
path_measure_->setPath(skPath, isClosed);
}
float CanvasPathMeasure::getLength() {
return path_measure_->getLength();
}
tonic::Float32List CanvasPathMeasure::getPosTan(float distance) {
SkPoint pos;
SkVector tan;
bool success = path_measure_->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;
}
fxl::RefPtr<CanvasPath> CanvasPathMeasure::getSegment(float startD,
float stopD,
bool startWithMoveTo) {
SkPath dst;
bool success =
path_measure_->getSegment(startD, stopD, &dst, startWithMoveTo);
if (!success) {
return CanvasPath::Create();
} else {
return CanvasPath::CreateFrom(dst);
}
}
bool CanvasPathMeasure::isClosed() {
return path_measure_->isClosed();
}
bool CanvasPathMeasure::nextContour() {
return path_measure_->nextContour();
}
} // namespace blink
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_LIB_UI_PAINTING_PATH_MEASURE_H_
#define FLUTTER_LIB_UI_PAINTING_PATH_MEASURE_H_
#include "flutter/lib/ui/painting/path.h"
#include "lib/tonic/dart_wrappable.h"
#include "lib/tonic/typed_data/float64_list.h"
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/core/SkPathMeasure.h"
namespace tonic {
class DartLibraryNatives;
} // namespace tonic
// Be sure that the client doesn't modify a path on us before Skia finishes
// See AOSP's reasoning in PathMeasure.cpp
namespace blink {
class CanvasPathMeasure : public fxl::RefCountedThreadSafe<CanvasPathMeasure>,
public tonic::DartWrappable {
DEFINE_WRAPPERTYPEINFO();
FRIEND_MAKE_REF_COUNTED(CanvasPathMeasure);
public:
~CanvasPathMeasure() override;
static fxl::RefPtr<CanvasPathMeasure> Create(const CanvasPath* path, bool forceClosed);
void setPath(const CanvasPath* path, bool isClosed);
float getLength();
tonic::Float32List getPosTan(float distance);
fxl::RefPtr<CanvasPath> getSegment(float startD, float stopD, bool startWithMoveTo);
bool isClosed();
bool nextContour();
static void RegisterNatives(tonic::DartLibraryNatives* natives);
const SkPathMeasure& pathMeasure() const { return *path_measure_; }
private:
CanvasPathMeasure();
std::unique_ptr<SkPathMeasure> path_measure_;
};
} // namespace blink
#endif // FLUTTER_LIB_UI_PAINTING_PATH_MEASURE_H_
......@@ -13,6 +13,7 @@ library dart.ui;
import 'dart:_internal' hide Symbol;
import 'dart:async';
import 'dart:collection' as collection;
import 'dart:convert';
import 'dart:developer' as developer;
import 'dart:math' as math;
......
// Copyright 2018 The Chromium 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:typed_data' show Float64List;
import 'dart:ui';
import 'package:test/test.dart';
void main() {
test('path getBounds', () {
final Rect r = new Rect.fromLTRB(1.0, 3.0, 5.0, 7.0);
final Path p = new Path()..addRect(r);
expect(p.getBounds(), equals(r));
p.lineTo(20.0, 15.0);
expect(p.getBounds(), equals(new Rect.fromLTRB(1.0, 3.0, 20.0, 15.0)));
});
test('path combine rect', () {
final Rect c1 =
new Rect.fromCircle(center: const Offset(10.0, 10.0), radius: 10.0);
final Rect c2 =
new Rect.fromCircle(center: const Offset(5.0, 5.0), radius: 10.0);
final Rect c1UnionC2 = c1.expandToInclude(c2);
final Rect c1IntersectC2 = c1.intersect(c2);
final Path pathCircle1 = new Path()..addRect(c1);
final Path pathCircle2 = new Path()..addRect(c2);
final Path difference =
Path.combine(PathOperation.difference, pathCircle1, pathCircle2);
expect(difference.getBounds(), equals(c1));
final Path reverseDifference =
Path.combine(PathOperation.reverseDifference, pathCircle1, pathCircle2);
expect(reverseDifference.getBounds(), equals(c2));
final Path union =
Path.combine(PathOperation.union, pathCircle1, pathCircle2);
expect(union.getBounds(), equals(c1UnionC2));
final Path intersect =
Path.combine(PathOperation.intersect, pathCircle1, pathCircle2);
expect(intersect.getBounds(), equals(c1IntersectC2));
// the bounds on this will be the same as union - but would draw a missing inside piece.
final Path xor = Path.combine(PathOperation.xor, pathCircle1, pathCircle2);
expect(xor.getBounds(), equals(c1UnionC2));
});
test('path combine oval', () {
final Rect c1 =
new Rect.fromCircle(center: const Offset(10.0, 10.0), radius: 10.0);
final Rect c2 =
new Rect.fromCircle(center: const Offset(5.0, 5.0), radius: 10.0);
final Rect c1UnionC2 = c1.expandToInclude(c2);
final Rect c1IntersectC2 = c1.intersect(c2);
final Path pathCircle1 = new Path()..addOval(c1);
final Path pathCircle2 = new Path()..addOval(c2);
final Path difference =
Path.combine(PathOperation.difference, pathCircle1, pathCircle2);
expect(difference.getBounds().top, closeTo(0.88, 0.01));
final Path reverseDifference =
Path.combine(PathOperation.reverseDifference, pathCircle1, pathCircle2);
expect(reverseDifference.getBounds().right,
closeTo(14.11, 0.01));
final Path union =
Path.combine(PathOperation.union, pathCircle1, pathCircle2);
expect(union.getBounds(), equals(c1UnionC2));
final Path intersect =
Path.combine(PathOperation.intersect, pathCircle1, pathCircle2);
expect(intersect.getBounds(), equals(c1IntersectC2));
// the bounds on this will be the same as union - but would draw a missing inside piece.
final Path xor = Path.combine(PathOperation.xor, pathCircle1, pathCircle2);
expect(xor.getBounds(), equals(c1UnionC2));
});
test('path clone', () {
final Path p1 = new Path()..lineTo(20.0, 20.0);
final Path p2 = new Path.from(p1);
expect(p1.getBounds(), equals(p2.getBounds()));
p1.lineTo(10.0, 30.0);
expect(p1.getBounds().bottom, equals(p2.getBounds().bottom + 10));
});
test('transformation tests', () {
final Rect bounds = new Rect.fromLTRB(0.0, 0.0, 10.0, 10.0);
final Path p = new Path()..addRect(bounds);
final Float64List scaleMatrix = new Float64List.fromList([
2.5, 0.0, 0.0, 0.0, // first col
0.0, 0.5, 0.0, 0.0, // second col
0.0, 0.0, 1.0, 0.0, // third col
0.0, 0.0, 0.0, 1.0, // fourth col
]);
expect(p.getBounds(), equals(bounds));
final Path pTransformed = p.transform(scaleMatrix);
expect(pTransformed.getBounds(),
equals(new Rect.fromLTRB(0.0, 0.0, 10 * 2.5, 10 * 0.5)));
final Path p2 = new Path()..lineTo(10.0, 10.0);
p.addPath(p2, const Offset(10.0, 10.0));
expect(p.getBounds(), equals(new Rect.fromLTRB(0.0, 0.0, 20.0, 20.0)));
p.addPath(p2, const Offset(20.0, 20.0), matrix4: scaleMatrix);
expect(p.getBounds(),
equals(new Rect.fromLTRB(0.0, 0.0, 20 + (10 * 2.5), 20 + (10 * .5))));
p.extendWithPath(p2, const Offset(0.0, 0.0));
expect(p.getBounds(), equals(new Rect.fromLTRB(0.0, 0.0, 45.0, 25.0)));
p.extendWithPath(p2, const Offset(45.0, 25.0), matrix4: scaleMatrix);
expect(p.getBounds(), equals(new Rect.fromLTRB(0.0, 0.0, 70.0, 30.0)));
});
test('path metrics tests', () {
final Path simpleHorizontalLine = new Path()..lineTo(10.0, 0.0);
// basic tests on horizontal line
final PathMetrics simpleHorizontalMetrics =
simpleHorizontalLine.computeMetrics();
expect(simpleHorizontalMetrics.iterator.current, isNull);
expect(simpleHorizontalMetrics.iterator.moveNext(), isTrue);
expect(simpleHorizontalMetrics.iterator.current, isNotNull);
expect(simpleHorizontalMetrics.iterator.current.length, equals(10.0));
expect(simpleHorizontalMetrics.iterator.current.isClosed, isFalse);
final Path simpleExtract =
simpleHorizontalMetrics.iterator.current.extractPath(1.0, 9.0);
expect(simpleExtract.getBounds(),
equals(new Rect.fromLTRB(1.0, 0.0, 9.0, 0.0)));
final Tangent posTan =
simpleHorizontalMetrics.iterator.current.getTangentForOffset(1.0);
expect(posTan, isNotNull);
expect(posTan.position, equals(const Offset(1.0, 0.0)));
expect(posTan.angle, equals(0.0));
expect(simpleHorizontalMetrics.iterator.moveNext(), isFalse);
expect(simpleHorizontalMetrics.iterator.current, isNull);
// test with forceClosed
final PathMetrics simpleMetricsClosed =
simpleHorizontalLine.computeMetrics(forceClosed: true);
expect(simpleMetricsClosed.iterator.current, isNull);
expect(simpleMetricsClosed.iterator.moveNext(), isTrue);
expect(simpleMetricsClosed.iterator.current, isNotNull);
expect(simpleMetricsClosed.iterator.current.length,
equals(20.0)); // because we forced close
expect(simpleMetricsClosed.iterator.current.isClosed, isTrue);
final Path simpleExtract2 =
simpleMetricsClosed.iterator.current.extractPath(1.0, 9.0);
expect(simpleExtract2.getBounds(),
equals(new Rect.fromLTRB(1.0, 0.0, 9.0, 0.0)));
expect(simpleMetricsClosed.iterator.moveNext(), isFalse);
// test getTangentForOffset with vertical line
final Path simpleVerticalLine = new Path()..lineTo(0.0, 10.0);
final PathMetrics simpleMetricsVertical =
simpleVerticalLine.computeMetrics()..iterator.moveNext();
final Tangent posTanVertical =
simpleMetricsVertical.iterator.current.getTangentForOffset(5.0);
expect(posTanVertical.position, equals(const Offset(0.0, 5.0)));
expect(posTanVertical.angle,
closeTo(-1.5708, .0001)); // 90 degrees
// test getTangentForOffset with diagonal line
final Path simpleDiagonalLine = new Path()..lineTo(10.0, 10.0);
final PathMetrics simpleMetricsDiagonal =
simpleDiagonalLine.computeMetrics()..iterator.moveNext();
final double midPoint = simpleMetricsDiagonal.iterator.current.length / 2;
final Tangent posTanDiagonal =
simpleMetricsDiagonal.iterator.current.getTangentForOffset(midPoint);
expect(posTanDiagonal.position, equals(new Offset(5.0, 5.0)));
expect(posTanDiagonal.angle,
closeTo(-0.7853981633974483, .00001)); // ~45 degrees
// test a multi-contour path
final Path multiContour = new Path()
..lineTo(0.0, 10.0)
..moveTo(10.0, 10.0)
..lineTo(10.0, 15.0);
final PathMetrics multiContourMetric = multiContour.computeMetrics();
expect(multiContourMetric.iterator.current, isNull);
expect(multiContourMetric.iterator.moveNext(), isTrue);
expect(multiContourMetric.iterator.current, isNotNull);
expect(multiContourMetric.iterator.current.length, equals(10.0));
expect(multiContourMetric.iterator.moveNext(), isTrue);
expect(multiContourMetric.iterator.current, isNotNull);
expect(multiContourMetric.iterator.current.length, equals(5.0));
expect(multiContourMetric.iterator.moveNext(), isFalse);
expect(multiContourMetric.iterator.current, isNull);
});
}
......@@ -9,6 +9,9 @@ import 'dart:developer' as developer;
import 'dart:math' as math;
import 'dart:nativewrappers';
// this needs to be imported because painting.dart expects it this way
import 'dart:collection' as collection;
import 'package:test/test.dart';
// HACK: these parts are to get access to private functions tested here.
......
......@@ -9947,6 +9947,8 @@ FILE: ../../../flutter/lib/ui/painting/paint.cc
FILE: ../../../flutter/lib/ui/painting/paint.h
FILE: ../../../flutter/lib/ui/painting/path.cc
FILE: ../../../flutter/lib/ui/painting/path.h
FILE: ../../../flutter/lib/ui/painting/path_measure.cc
FILE: ../../../flutter/lib/ui/painting/path_measure.h
FILE: ../../../flutter/lib/ui/painting/picture.cc
FILE: ../../../flutter/lib/ui/painting/picture.h
FILE: ../../../flutter/lib/ui/painting/picture_recorder.cc
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册