From 267bcd8c87cc28ab8aa0c347b9c74c14c1a8f765 Mon Sep 17 00:00:00 2001 From: Dan Field Date: Tue, 15 May 2018 00:01:52 -0400 Subject: [PATCH 1/4] update docs for getBounds --- lib/ui/painting.dart | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index f97b2d271be83..749425c0fae2c 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -1792,6 +1792,15 @@ class Path extends NativeFieldWrapperClass2 { Path _transform(Float64List matrix4) native 'Path_transform'; /// Computes the bounding rectangle for this path. + /// + /// The returned bounds width and height may be larger or smaller than area + /// affected when Path is drawn. For example, a path containing a straight + /// line on the horizontal axis may result in a [Rect] with a height of 0 and + /// a width the approximate length of the path (if drawn with a stroke width + /// of 1.0). Because of this, you should not rely on `Rect.isEmpty` to test + /// whether the bounds of this path contains any points, but instead test that + /// `Rect.width + Rect.height > 0.0` (. + // see https://skia.org/user/api/SkPath_Reference#SkPath_getBounds Rect getBounds() { final Float32List rect = _getBounds(); return new Rect.fromLTRB(rect[0], rect[1], rect[2], rect[3]); From f2a01d119073d66403d4f7bf77a1bb78cebf4c64 Mon Sep 17 00:00:00 2001 From: Dan Field Date: Tue, 15 May 2018 00:08:58 -0400 Subject: [PATCH 2/4] Add computeMetrics suggestion --- lib/ui/painting.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 749425c0fae2c..22d91518f314a 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -1799,7 +1799,8 @@ class Path extends NativeFieldWrapperClass2 { /// a width the approximate length of the path (if drawn with a stroke width /// of 1.0). Because of this, you should not rely on `Rect.isEmpty` to test /// whether the bounds of this path contains any points, but instead test that - /// `Rect.width + Rect.height > 0.0` (. + /// `Rect.width + Rect.height > 0.0` or use the `computeMetrics` API to check + /// the path length. // see https://skia.org/user/api/SkPath_Reference#SkPath_getBounds Rect getBounds() { final Float32List rect = _getBounds(); From 90572b50279255ba02342d086d70f4b5563462f9 Mon Sep 17 00:00:00 2001 From: Dan Field Date: Tue, 15 May 2018 07:43:52 -0400 Subject: [PATCH 3/4] better explanation --- lib/ui/painting.dart | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 22d91518f314a..f206f6d4adcc7 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -1793,14 +1793,18 @@ class Path extends NativeFieldWrapperClass2 { /// Computes the bounding rectangle for this path. /// - /// The returned bounds width and height may be larger or smaller than area - /// affected when Path is drawn. For example, a path containing a straight - /// line on the horizontal axis may result in a [Rect] with a height of 0 and - /// a width the approximate length of the path (if drawn with a stroke width - /// of 1.0). Because of this, you should not rely on `Rect.isEmpty` to test - /// whether the bounds of this path contains any points, but instead test that - /// `Rect.width + Rect.height > 0.0` or use the `computeMetrics` API to check - /// the path length. + /// A path containing only axis-aligned points on the same straight line will + /// have no area, and therefore `Rect.isEmpty` will return true for such a + /// path. Consider checking `rect.width + rect.height > 0.0` instead, or + /// using the [computeMetrics] API to check the path length. + /// + /// For many more elaborate paths, the bounds may be inaccurate. For example, + /// when a path contains a circle, the points used to compute the bounds are + /// the circle's implied control points, which form a square around the circle; + /// if the circle has a transformation applied using [transform] then that + /// square is rotated, and the (axis-aligned, non-rotated) bounding box + /// therefore ends up grossly overestimating the actual area covered by the + /// circle. // see https://skia.org/user/api/SkPath_Reference#SkPath_getBounds Rect getBounds() { final Float32List rect = _getBounds(); From af8f03fc850dad3de0d7fffa03a66016552e6ca2 Mon Sep 17 00:00:00 2001 From: Dan Field Date: Wed, 16 May 2018 01:01:50 -0400 Subject: [PATCH 4/4] Support for TwoPointConical gradients --- lib/ui/painting.dart | 25 ++++++++++++++++++++++--- lib/ui/painting/gradient.cc | 32 +++++++++++++++++++++++++++++++- lib/ui/painting/gradient.h | 11 +++++++++++ 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index f97b2d271be83..7ac5d238b61a8 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -2296,25 +2296,44 @@ class Gradient extends Shader { /// If `matrix4` is provided, the gradient fill will be transformed by the /// specified 4x4 matrix relative to the local coordinate system. `matrix4` must /// be a column-major matrix packed into a list of 16 values. + /// + /// If `focal` is provided and not equal to `center` or `focalRadius` is + /// provided and not equal to 0.0, the generated shader will be a two point + /// conical radial gradient, with `focal` being the center of the focal + /// circle and `focalRadius` being the radius of that circle. If `focal` is + /// provided and not equal to `center`, at least one of the two offsets must + /// not be equal to [Offset.zero]. Gradient.radial( Offset center, double radius, List colors, [ List colorStops, TileMode tileMode = TileMode.clamp, - Float64List matrix4 + Float64List matrix4, + Offset focal, + double focalRadius ]) : assert(_offsetIsValid(center)), assert(colors != null), assert(tileMode != null), assert(matrix4 == null || _matrix4IsValid(matrix4)), super._() { + focal ??= center; + focalRadius ??= 0.0; _validateColorStops(colors, colorStops); final Int32List colorsBuffer = _encodeColorList(colors); final Float32List colorStopsBuffer = colorStops == null ? null : new Float32List.fromList(colorStops); - _constructor(); - _initRadial(center.dx, center.dy, radius, colorsBuffer, colorStopsBuffer, tileMode.index, matrix4); + + if (center == focal && focalRadius != 0.0) { + _constructor(); + _initRadial(center.dx, center.dy, radius, colorsBuffer, colorStopsBuffer, tileMode.index, matrix4); + } else { + assert(center != Offset.zero || focal != Offset.zero); // will result in nullptr in Skia side + _constructor(); + _initConical(focal.dx, focal.dy, focalRadius, center.dx, center.dy, radius, colorsBuffer, colorStopsBuffer, tileMode.index, matrix4); + } } void _initRadial(double centerX, double centerY, double radius, Int32List colors, Float32List colorStops, int tileMode, Float64List matrix4) native 'Gradient_initRadial'; + void _initConical(double startX, double startY, double startRadius, double endX, double endY, double endRadius, Int32List colors, Float32List colorStops, int tileMode, Float64List matrix4) native 'Gradient_initTwoPointConical'; /// Creates a sweep gradient centered at `center` that starts at `startAngle` /// and ends at `endAngle`. diff --git a/lib/ui/painting/gradient.cc b/lib/ui/painting/gradient.cc index 9cbf5a19c5275..71b2669100fa1 100644 --- a/lib/ui/painting/gradient.cc +++ b/lib/ui/painting/gradient.cc @@ -25,7 +25,8 @@ IMPLEMENT_WRAPPERTYPEINFO(ui, Gradient); #define FOR_EACH_BINDING(V) \ V(Gradient, initLinear) \ V(Gradient, initRadial) \ - V(Gradient, initSweep) + V(Gradient, initSweep) \ + V(Gradient, initTwoPointConical) FOR_EACH_BINDING(DART_NATIVE_CALLBACK) @@ -109,6 +110,35 @@ void CanvasGradient::initSweep(double center_x, has_matrix ? &sk_matrix : nullptr))); } +void CanvasGradient::initTwoPointConical(double start_x, + double start_y, + double start_radius, + double end_x, + double end_y, + double end_radius, + const tonic::Int32List& colors, + const tonic::Float32List& color_stops, + SkShader::TileMode tile_mode, + const tonic::Float64List& matrix4) { + FXL_DCHECK(colors.num_elements() == color_stops.num_elements() || + color_stops.data() == nullptr); + + static_assert(sizeof(SkColor) == sizeof(int32_t), + "SkColor doesn't use int32_t."); + + SkMatrix sk_matrix; + bool has_matrix = matrix4.data() != nullptr; + if (has_matrix) { + sk_matrix = ToSkMatrix(matrix4); + } + + set_shader(UIDartState::CreateGPUObject(SkGradientShader::MakeTwoPointConical( + SkPoint::Make(start_x, start_y), start_radius, + SkPoint::Make(end_x, end_y), end_radius, + reinterpret_cast(colors.data()), color_stops.data(), + colors.num_elements(), tile_mode, 0, has_matrix ? &sk_matrix : nullptr))); +} + CanvasGradient::CanvasGradient() = default; CanvasGradient::~CanvasGradient() = default; diff --git a/lib/ui/painting/gradient.h b/lib/ui/painting/gradient.h index 178c17fe9b41c..4d8e8ec09191b 100644 --- a/lib/ui/painting/gradient.h +++ b/lib/ui/painting/gradient.h @@ -52,6 +52,17 @@ class CanvasGradient : public Shader { double end_angle, const tonic::Float64List& matrix4); + void initTwoPointConical(double start_x, + double start_y, + double start_radius, + double end_x, + double end_y, + double end_radius, + const tonic::Int32List& colors, + const tonic::Float32List& color_stops, + SkShader::TileMode tile_mode, + const tonic::Float64List& matrix4); + static void RegisterNatives(tonic::DartLibraryNatives* natives); private: