diff --git a/engine/src/flutter/lib/ui/painting.dart b/engine/src/flutter/lib/ui/painting.dart index 0adb6f1cae..562eb56e1e 100644 --- a/engine/src/flutter/lib/ui/painting.dart +++ b/engine/src/flutter/lib/ui/painting.dart @@ -2310,25 +2310,45 @@ 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` and `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 = 0.0 ]) : assert(_offsetIsValid(center)), assert(colors != null), assert(tileMode != null), assert(matrix4 == null || _matrix4IsValid(matrix4)), super._() { + 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 focal is null or focal radius is null, this should be treated as a regular radial gradient + // If focal == center and the focal radius is 0.0, it's still a regular radial gradient + if (focal == null || (focal == center && 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 exception(s) 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/engine/src/flutter/lib/ui/painting/gradient.cc b/engine/src/flutter/lib/ui/painting/gradient.cc index 9cbf5a19c5..71b2669100 100644 --- a/engine/src/flutter/lib/ui/painting/gradient.cc +++ b/engine/src/flutter/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/engine/src/flutter/lib/ui/painting/gradient.h b/engine/src/flutter/lib/ui/painting/gradient.h index 178c17fe9b..4d8e8ec091 100644 --- a/engine/src/flutter/lib/ui/painting/gradient.h +++ b/engine/src/flutter/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: diff --git a/engine/src/flutter/testing/dart/gradient_test.dart b/engine/src/flutter/testing/dart/gradient_test.dart new file mode 100644 index 0000000000..881b70fec5 --- /dev/null +++ b/engine/src/flutter/testing/dart/gradient_test.dart @@ -0,0 +1,72 @@ +// 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('Gradient.radial with no focal point', () { + expect( + new Gradient.radial( + Offset.zero, + null, + [const Color(0xFFFFFFFF), const Color(0xFFFFFFFF)], + [0.0, 1.0], + TileMode.mirror), + isNotNull, + ); + }); + + // this is just a radial gradient, focal point is discarded. + test('radial center and focal == Offset.zero and focalRadius == 0.0 is ok', + () { + expect( + () => new Gradient.radial( + Offset.zero, + 0.0, + [const Color(0xFFFFFFFF), const Color(0xFFFFFFFF)], + [0.0, 1.0], + TileMode.mirror, + null, + Offset.zero, + 0.0, + ), + isNotNull); + }); + + test('radial center != focal and focalRadius == 0.0 is ok', () { + expect( + () => new Gradient.radial( + Offset.zero, + 0.0, + [const Color(0xFFFFFFFF), const Color(0xFFFFFFFF)], + [0.0, 1.0], + TileMode.mirror, + null, + const Offset(2.0, 2.0), + 0.0, + ), + isNotNull); + }); + + // this would result in div/0 on skia side. + test('radial center and focal == Offset.zero and focalRadius != 0.0 assert', + () { + expect( + () => new Gradient.radial( + Offset.zero, + 0.0, + [const Color(0xFFFFFFFF), const Color(0xFFFFFFFF)], + [0.0, 1.0], + TileMode.mirror, + null, + Offset.zero, + 1.0, + ), + throwsA(const isInstanceOf()), + ); + }); +}