From 7418367f1ec71518451614c46c38815c00f9f121 Mon Sep 17 00:00:00 2001 From: gaaclarke <30870216+gaaclarke@users.noreply.github.com> Date: Wed, 4 Sep 2024 16:50:19 -0700 Subject: [PATCH] Framework wide color linear gradients (#54748) This implements wide gamut color support for gradients in the engine. issue: https://github.com/flutter/flutter/issues/127855 integration test: https://github.com/flutter/flutter/pull/153976 [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style --- impeller/display_list/skia_conversions.cc | 3 +- lib/ui/painting.dart | 35 +++++++--- lib/ui/painting/gradient.cc | 79 ++++++++++++++--------- lib/ui/painting/gradient.h | 8 +-- 4 files changed, 80 insertions(+), 45 deletions(-) diff --git a/impeller/display_list/skia_conversions.cc b/impeller/display_list/skia_conversions.cc index 4e3180a6e2074..57dc07cbc2157 100644 --- a/impeller/display_list/skia_conversions.cc +++ b/impeller/display_list/skia_conversions.cc @@ -218,7 +218,8 @@ std::optional ToPixelFormat(SkColorType type) { void ConvertStops(const flutter::DlGradientColorSourceBase* gradient, std::vector& colors, std::vector& stops) { - FML_DCHECK(gradient->stop_count() >= 2); + FML_DCHECK(gradient->stop_count() >= 2) + << "stop_count:" << gradient->stop_count(); auto* dl_colors = gradient->colors(); auto* dl_stops = gradient->stops(); diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 006235c0e5054..1a67e9a64102f 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -4452,10 +4452,25 @@ enum TileMode { decal, } +Float32List _encodeWideColorList(List colors) { + final int colorCount = colors.length; + final Float32List result = Float32List(colorCount * 4); + for (int i = 0; i < colorCount; i++) { + final Color colorXr = + colors[i].withValues(colorSpace: ColorSpace.extendedSRGB); + result[i*4+0] = colorXr.a; + result[i*4+1] = colorXr.r; + result[i*4+2] = colorXr.g; + result[i*4+3] = colorXr.b; + } + return result; +} + + Int32List _encodeColorList(List colors) { final int colorCount = colors.length; final Int32List result = Int32List(colorCount); - for (int i = 0; i < colorCount; ++i) { + for (int i = 0; i < colorCount; i++) { result[i] = colors[i].value; } return result; @@ -4464,7 +4479,7 @@ Int32List _encodeColorList(List colors) { Float32List _encodePointList(List points) { final int pointCount = points.length; final Float32List result = Float32List(pointCount * 2); - for (int i = 0; i < pointCount; ++i) { + for (int i = 0; i < pointCount; i++) { final int xIndex = i * 2; final int yIndex = xIndex + 1; final Offset point = points[i]; @@ -4535,7 +4550,7 @@ base class Gradient extends Shader { super._() { _validateColorStops(colors, colorStops); final Float32List endPointsBuffer = _encodeTwoPoints(from, to); - final Int32List colorsBuffer = _encodeColorList(colors); + final Float32List colorsBuffer = _encodeWideColorList(colors); final Float32List? colorStopsBuffer = colorStops == null ? null : Float32List.fromList(colorStops); _constructor(); _initLinear(endPointsBuffer, colorsBuffer, colorStopsBuffer, tileMode.index, matrix4); @@ -4588,8 +4603,8 @@ base class Gradient extends Shader { assert(matrix4 == null || _matrix4IsValid(matrix4)), super._() { _validateColorStops(colors, colorStops); - final Int32List colorsBuffer = _encodeColorList(colors); final Float32List? colorStopsBuffer = colorStops == null ? null : Float32List.fromList(colorStops); + final Float32List colorsBuffer = _encodeWideColorList(colors); // 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 @@ -4647,7 +4662,7 @@ base class Gradient extends Shader { assert(matrix4 == null || _matrix4IsValid(matrix4)), super._() { _validateColorStops(colors, colorStops); - final Int32List colorsBuffer = _encodeColorList(colors); + final Float32List colorsBuffer = _encodeWideColorList(colors); final Float32List? colorStopsBuffer = colorStops == null ? null : Float32List.fromList(colorStops); _constructor(); _initSweep(center.dx, center.dy, colorsBuffer, colorStopsBuffer, tileMode.index, startAngle, endAngle, matrix4); @@ -4657,14 +4672,14 @@ base class Gradient extends Shader { external void _constructor(); @Native, Handle, Handle, Handle, Int32, Handle)>(symbol: 'Gradient::initLinear') - external void _initLinear(Float32List endPoints, Int32List colors, Float32List? colorStops, int tileMode, Float64List? matrix4); + external void _initLinear(Float32List endPoints, Float32List colors, Float32List? colorStops, int tileMode, Float64List? matrix4); @Native, Double, Double, Double, Handle, Handle, Int32, Handle)>(symbol: 'Gradient::initRadial') external void _initRadial( double centerX, double centerY, double radius, - Int32List colors, + Float32List colors, Float32List? colorStops, int tileMode, Float64List? matrix4); @@ -4677,7 +4692,7 @@ base class Gradient extends Shader { double endX, double endY, double endRadius, - Int32List colors, + Float32List colors, Float32List? colorStops, int tileMode, Float64List? matrix4); @@ -4686,7 +4701,7 @@ base class Gradient extends Shader { external void _initSweep( double centerX, double centerY, - Int32List colors, + Float32List colors, Float32List? colorStops, int tileMode, double startAngle, @@ -6503,7 +6518,7 @@ base class _NativeCanvas extends NativeFieldWrapperClass1 implements Canvas { final Float32List rstTransformBuffer = Float32List(rectCount * 4); final Float32List rectBuffer = Float32List(rectCount * 4); - for (int i = 0; i < rectCount; ++i) { + for (int i = 0; i < rectCount; i++) { final int index0 = i * 4; final int index1 = index0 + 1; final int index2 = index0 + 2; diff --git a/lib/ui/painting/gradient.cc b/lib/ui/painting/gradient.cc index 4be8d056e9a82..6f3f8b5d60f3d 100644 --- a/lib/ui/painting/gradient.cc +++ b/lib/ui/painting/gradient.cc @@ -24,13 +24,14 @@ void CanvasGradient::Create(Dart_Handle wrapper) { } void CanvasGradient::initLinear(const tonic::Float32List& end_points, - const tonic::Int32List& colors, + const tonic::Float32List& colors, const tonic::Float32List& color_stops, DlTileMode tile_mode, const tonic::Float64List& matrix4) { FML_DCHECK(end_points.num_elements() == 4); - FML_DCHECK(colors.num_elements() == color_stops.num_elements() || + FML_DCHECK(colors.num_elements() == (color_stops.num_elements() * 4) || color_stops.data() == nullptr); + int num_colors = colors.num_elements() / 4; static_assert(sizeof(SkPoint) == sizeof(float) * 2, "SkPoint doesn't use floats."); @@ -46,15 +47,18 @@ void CanvasGradient::initLinear(const tonic::Float32List& end_points, SkPoint p0 = SkPoint::Make(end_points[0], end_points[1]); SkPoint p1 = SkPoint::Make(end_points[2], end_points[3]); std::vector dl_colors; - dl_colors.reserve(colors.num_elements()); - for (int i = 0; i < colors.num_elements(); ++i) { - /// TODO(gaaclarke): Make this preserve wide gamut colors. - dl_colors.emplace_back(DlColor(colors[i])); + dl_colors.reserve(num_colors); + for (int i = 0; i < colors.num_elements(); i += 4) { + DlScalar a = colors[i + 0]; + DlScalar r = colors[i + 1]; + DlScalar g = colors[i + 2]; + DlScalar b = colors[i + 3]; + dl_colors.emplace_back(DlColor(a, r, g, b, DlColorSpace::kExtendedSRGB)); } - dl_shader_ = DlColorSource::MakeLinear( - p0, p1, colors.num_elements(), dl_colors.data(), color_stops.data(), - tile_mode, has_matrix ? &sk_matrix : nullptr); + dl_shader_ = DlColorSource::MakeLinear(p0, p1, num_colors, dl_colors.data(), + color_stops.data(), tile_mode, + has_matrix ? &sk_matrix : nullptr); // Just a sanity check, all gradient shaders should be thread-safe FML_DCHECK(dl_shader_->isUIThreadSafe()); } @@ -62,12 +66,13 @@ void CanvasGradient::initLinear(const tonic::Float32List& end_points, void CanvasGradient::initRadial(double center_x, double center_y, double radius, - const tonic::Int32List& colors, + const tonic::Float32List& colors, const tonic::Float32List& color_stops, DlTileMode tile_mode, const tonic::Float64List& matrix4) { - FML_DCHECK(colors.num_elements() == color_stops.num_elements() || + FML_DCHECK(colors.num_elements() == (color_stops.num_elements() * 4) || color_stops.data() == nullptr); + int num_colors = colors.num_elements() / 4; static_assert(sizeof(SkColor) == sizeof(int32_t), "SkColor doesn't use int32_t."); @@ -79,29 +84,34 @@ void CanvasGradient::initRadial(double center_x, } std::vector dl_colors; - dl_colors.reserve(colors.num_elements()); - for (int i = 0; i < colors.num_elements(); ++i) { - dl_colors.emplace_back(DlColor(colors[i])); + dl_colors.reserve(num_colors); + for (int i = 0; i < colors.num_elements(); i += 4) { + DlScalar a = colors[i + 0]; + DlScalar r = colors[i + 1]; + DlScalar g = colors[i + 2]; + DlScalar b = colors[i + 3]; + dl_colors.emplace_back(DlColor(a, r, g, b, DlColorSpace::kExtendedSRGB)); } dl_shader_ = DlColorSource::MakeRadial( SkPoint::Make(SafeNarrow(center_x), SafeNarrow(center_y)), - SafeNarrow(radius), colors.num_elements(), dl_colors.data(), - color_stops.data(), tile_mode, has_matrix ? &sk_matrix : nullptr); + SafeNarrow(radius), num_colors, dl_colors.data(), color_stops.data(), + tile_mode, has_matrix ? &sk_matrix : nullptr); // Just a sanity check, all gradient shaders should be thread-safe FML_DCHECK(dl_shader_->isUIThreadSafe()); } void CanvasGradient::initSweep(double center_x, double center_y, - const tonic::Int32List& colors, + const tonic::Float32List& colors, const tonic::Float32List& color_stops, DlTileMode tile_mode, double start_angle, double end_angle, const tonic::Float64List& matrix4) { - FML_DCHECK(colors.num_elements() == color_stops.num_elements() || + FML_DCHECK(colors.num_elements() == (color_stops.num_elements() * 4) || color_stops.data() == nullptr); + int num_colors = colors.num_elements() / 4; static_assert(sizeof(SkColor) == sizeof(int32_t), "SkColor doesn't use int32_t."); @@ -113,16 +123,20 @@ void CanvasGradient::initSweep(double center_x, } std::vector dl_colors; - dl_colors.reserve(colors.num_elements()); - for (int i = 0; i < colors.num_elements(); ++i) { - dl_colors.emplace_back(DlColor(colors[i])); + dl_colors.reserve(num_colors); + for (int i = 0; i < colors.num_elements(); i += 4) { + DlScalar a = colors[i + 0]; + DlScalar r = colors[i + 1]; + DlScalar g = colors[i + 2]; + DlScalar b = colors[i + 3]; + dl_colors.emplace_back(DlColor(a, r, g, b, DlColorSpace::kExtendedSRGB)); } dl_shader_ = DlColorSource::MakeSweep( SkPoint::Make(SafeNarrow(center_x), SafeNarrow(center_y)), SafeNarrow(start_angle) * 180.0f / static_cast(M_PI), - SafeNarrow(end_angle) * 180.0f / static_cast(M_PI), - colors.num_elements(), dl_colors.data(), color_stops.data(), tile_mode, + SafeNarrow(end_angle) * 180.0f / static_cast(M_PI), num_colors, + dl_colors.data(), color_stops.data(), tile_mode, has_matrix ? &sk_matrix : nullptr); // Just a sanity check, all gradient shaders should be thread-safe FML_DCHECK(dl_shader_->isUIThreadSafe()); @@ -134,12 +148,13 @@ void CanvasGradient::initTwoPointConical(double start_x, double end_x, double end_y, double end_radius, - const tonic::Int32List& colors, + const tonic::Float32List& colors, const tonic::Float32List& color_stops, DlTileMode tile_mode, const tonic::Float64List& matrix4) { - FML_DCHECK(colors.num_elements() == color_stops.num_elements() || + FML_DCHECK(colors.num_elements() == (color_stops.num_elements() * 4) || color_stops.data() == nullptr); + int num_colors = colors.num_elements() / 4; static_assert(sizeof(SkColor) == sizeof(int32_t), "SkColor doesn't use int32_t."); @@ -151,17 +166,21 @@ void CanvasGradient::initTwoPointConical(double start_x, } std::vector dl_colors; - dl_colors.reserve(colors.num_elements()); - for (int i = 0; i < colors.num_elements(); ++i) { - dl_colors.emplace_back(DlColor(colors[i])); + dl_colors.reserve(num_colors); + for (int i = 0; i < colors.num_elements(); i += 4) { + DlScalar a = colors[i + 0]; + DlScalar r = colors[i + 1]; + DlScalar g = colors[i + 2]; + DlScalar b = colors[i + 3]; + dl_colors.emplace_back(DlColor(a, r, g, b, DlColorSpace::kExtendedSRGB)); } dl_shader_ = DlColorSource::MakeConical( SkPoint::Make(SafeNarrow(start_x), SafeNarrow(start_y)), SafeNarrow(start_radius), SkPoint::Make(SafeNarrow(end_x), SafeNarrow(end_y)), - SafeNarrow(end_radius), colors.num_elements(), dl_colors.data(), - color_stops.data(), tile_mode, has_matrix ? &sk_matrix : nullptr); + SafeNarrow(end_radius), num_colors, dl_colors.data(), color_stops.data(), + tile_mode, has_matrix ? &sk_matrix : nullptr); // Just a sanity check, all gradient shaders should be thread-safe FML_DCHECK(dl_shader_->isUIThreadSafe()); } diff --git a/lib/ui/painting/gradient.h b/lib/ui/painting/gradient.h index a0338d05fa5e7..c33843cc8fb2d 100644 --- a/lib/ui/painting/gradient.h +++ b/lib/ui/painting/gradient.h @@ -21,7 +21,7 @@ class CanvasGradient : public Shader { static void Create(Dart_Handle wrapper); void initLinear(const tonic::Float32List& end_points, - const tonic::Int32List& colors, + const tonic::Float32List& colors, const tonic::Float32List& color_stops, DlTileMode tile_mode, const tonic::Float64List& matrix4); @@ -29,14 +29,14 @@ class CanvasGradient : public Shader { void initRadial(double center_x, double center_y, double radius, - const tonic::Int32List& colors, + const tonic::Float32List& colors, const tonic::Float32List& color_stops, DlTileMode tile_mode, const tonic::Float64List& matrix4); void initSweep(double center_x, double center_y, - const tonic::Int32List& colors, + const tonic::Float32List& colors, const tonic::Float32List& color_stops, DlTileMode tile_mode, double start_angle, @@ -49,7 +49,7 @@ class CanvasGradient : public Shader { double end_x, double end_y, double end_radius, - const tonic::Int32List& colors, + const tonic::Float32List& colors, const tonic::Float32List& color_stops, DlTileMode tile_mode, const tonic::Float64List& matrix4);