diff --git a/impeller/display_list/dl_dispatcher.cc b/impeller/display_list/dl_dispatcher.cc index 8f18470b33302..0b19cefb5b79b 100644 --- a/impeller/display_list/dl_dispatcher.cc +++ b/impeller/display_list/dl_dispatcher.cc @@ -271,30 +271,6 @@ static std::vector ToColors(const flutter::DlColor colors[], int count) { return result; } -// Convert display list colors + stops into impeller colors and stops, taking -// care to ensure that the stops always start with 0.0 and end with 1.0. -template -static void ConvertStops(T* gradient, - std::vector* colors, - std::vector* stops) { - FML_DCHECK(gradient->stop_count() >= 2); - - auto* dl_colors = gradient->colors(); - auto* dl_stops = gradient->stops(); - if (dl_stops[0] != 0.0) { - colors->emplace_back(skia_conversions::ToColor(dl_colors[0])); - stops->emplace_back(0); - } - for (auto i = 0; i < gradient->stop_count(); i++) { - colors->emplace_back(skia_conversions::ToColor(dl_colors[i])); - stops->emplace_back(dl_stops[i]); - } - if (stops->back() != 1.0) { - colors->emplace_back(colors->back()); - stops->emplace_back(1.0); - } -} - static std::optional ToColorSourceType( flutter::DlColorSourceType type) { switch (type) { @@ -351,7 +327,7 @@ void DlDispatcher::setColorSource(const flutter::DlColorSource* source) { auto end_point = skia_conversions::ToPoint(linear->end_point()); std::vector colors; std::vector stops; - ConvertStops(linear, &colors, &stops); + skia_conversions::ConvertStops(linear, colors, stops); auto tile_mode = ToTileMode(linear->tile_mode()); auto matrix = ToMatrix(linear->matrix()); @@ -372,7 +348,7 @@ void DlDispatcher::setColorSource(const flutter::DlColorSource* source) { SkScalar focus_radius = conical_gradient->start_radius(); std::vector colors; std::vector stops; - ConvertStops(conical_gradient, &colors, &stops); + skia_conversions::ConvertStops(conical_gradient, colors, stops); auto tile_mode = ToTileMode(conical_gradient->tile_mode()); auto matrix = ToMatrix(conical_gradient->matrix()); @@ -390,7 +366,7 @@ void DlDispatcher::setColorSource(const flutter::DlColorSource* source) { auto radius = radialGradient->radius(); std::vector colors; std::vector stops; - ConvertStops(radialGradient, &colors, &stops); + skia_conversions::ConvertStops(radialGradient, colors, stops); auto tile_mode = ToTileMode(radialGradient->tile_mode()); auto matrix = ToMatrix(radialGradient->matrix()); @@ -409,7 +385,7 @@ void DlDispatcher::setColorSource(const flutter::DlColorSource* source) { auto end_angle = Degrees(sweepGradient->end()); std::vector colors; std::vector stops; - ConvertStops(sweepGradient, &colors, &stops); + skia_conversions::ConvertStops(sweepGradient, colors, stops); auto tile_mode = ToTileMode(sweepGradient->tile_mode()); auto matrix = ToMatrix(sweepGradient->matrix()); diff --git a/impeller/display_list/skia_conversions.cc b/impeller/display_list/skia_conversions.cc index 8b17ad0acf315..56e2f87825ccd 100644 --- a/impeller/display_list/skia_conversions.cc +++ b/impeller/display_list/skia_conversions.cc @@ -189,5 +189,29 @@ std::optional ToPixelFormat(SkColorType type) { return std::nullopt; } +void ConvertStops(const flutter::DlGradientColorSourceBase* gradient, + std::vector& colors, + std::vector& stops) { + FML_DCHECK(gradient->stop_count() >= 2); + + auto* dl_colors = gradient->colors(); + auto* dl_stops = gradient->stops(); + if (dl_stops[0] != 0.0) { + colors.emplace_back(skia_conversions::ToColor(dl_colors[0])); + stops.emplace_back(0); + } + for (auto i = 0; i < gradient->stop_count(); i++) { + colors.emplace_back(skia_conversions::ToColor(dl_colors[i])); + stops.emplace_back(std::clamp(dl_stops[i], 0.0f, 1.0f)); + } + if (dl_stops[gradient->stop_count() - 1] != 1.0) { + colors.emplace_back(colors.back()); + stops.emplace_back(1.0); + } + for (auto i = 1; i < gradient->stop_count(); i++) { + stops[i] = std::clamp(stops[i], stops[i - 1], stops[i]); + } +} + } // namespace skia_conversions } // namespace impeller diff --git a/impeller/display_list/skia_conversions.h b/impeller/display_list/skia_conversions.h index 4b535db6227c3..4eed60fb124d2 100644 --- a/impeller/display_list/skia_conversions.h +++ b/impeller/display_list/skia_conversions.h @@ -5,6 +5,7 @@ #pragma once #include "display_list/dl_color.h" +#include "display_list/effects/dl_color_source.h" #include "impeller/core/formats.h" #include "impeller/geometry/color.h" #include "impeller/geometry/path.h" @@ -47,5 +48,23 @@ Path PathDataFromTextBlob(const sk_sp& blob, std::optional ToPixelFormat(SkColorType type); +/// @brief Convert display list colors + stops into impeller colors and stops, +/// taking care to ensure that the stops monotonically increase from 0.0 to 1.0. +/// +/// The general process is: +/// * Ensure that the first gradient stop value is 0.0. If not, insert a new +/// stop with a value of 0.0 and use the first gradient color as this new +/// stops color. +/// * Ensure the last gradient stop value is 1.0. If not, insert a new stop +/// with a value of 1.0 and use the last gradient color as this stops color. +/// * Clamp all gradient values between the values of 0.0 and 1.0. +/// * For all stop values, ensure that the values are monotonically increasing +/// by clamping each value to a minimum of the previous stop value and itself. +/// For example, with stop values of 0.0, 0.5, 0.4, 1.0, we would clamp such +/// that the values were 0.0, 0.5, 0.5, 1.0. +void ConvertStops(const flutter::DlGradientColorSourceBase* gradient, + std::vector& colors, + std::vector& stops); + } // namespace skia_conversions } // namespace impeller diff --git a/impeller/display_list/skia_conversions_unittests.cc b/impeller/display_list/skia_conversions_unittests.cc index 220e5035f8226..eaf1f571ab7f5 100644 --- a/impeller/display_list/skia_conversions_unittests.cc +++ b/impeller/display_list/skia_conversions_unittests.cc @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "display_list/dl_color.h" +#include "display_list/dl_tile_mode.h" #include "flutter/testing/testing.h" #include "impeller/display_list/skia_conversions.h" #include "impeller/geometry/scalar.h" @@ -23,5 +25,136 @@ TEST(SkiaConversionsTest, ToColor) { ASSERT_TRUE(ScalarNearlyEqual(converted_color.blue, 0x20 * (1.0f / 255))); } +TEST(SkiaConversionsTest, GradientStopConversion) { + // Typical gradient. + std::vector colors = {flutter::DlColor::kBlue(), + flutter::DlColor::kRed(), + flutter::DlColor::kGreen()}; + std::vector stops = {0.0, 0.5, 1.0}; + const auto gradient = + flutter::DlColorSource::MakeLinear(SkPoint::Make(0, 0), // + SkPoint::Make(1.0, 1.0), // + 3, // + colors.data(), // + stops.data(), // + flutter::DlTileMode::kClamp, // + nullptr // + ); + + std::vector converted_colors; + std::vector converted_stops; + skia_conversions::ConvertStops(gradient.get(), converted_colors, + converted_stops); + + ASSERT_TRUE(ScalarNearlyEqual(converted_stops[0], 0.0f)); + ASSERT_TRUE(ScalarNearlyEqual(converted_stops[1], 0.5f)); + ASSERT_TRUE(ScalarNearlyEqual(converted_stops[2], 1.0f)); +} + +TEST(SkiaConversionsTest, GradientMissing0) { + std::vector colors = {flutter::DlColor::kBlue(), + flutter::DlColor::kRed()}; + std::vector stops = {0.5, 1.0}; + const auto gradient = + flutter::DlColorSource::MakeLinear(SkPoint::Make(0, 0), // + SkPoint::Make(1.0, 1.0), // + 2, // + colors.data(), // + stops.data(), // + flutter::DlTileMode::kClamp, // + nullptr // + ); + + std::vector converted_colors; + std::vector converted_stops; + skia_conversions::ConvertStops(gradient.get(), converted_colors, + converted_stops); + + // First color is inserted as blue. + ASSERT_TRUE(ScalarNearlyEqual(converted_colors[0].blue, 1.0f)); + ASSERT_TRUE(ScalarNearlyEqual(converted_stops[0], 0.0f)); + ASSERT_TRUE(ScalarNearlyEqual(converted_stops[1], 0.5f)); + ASSERT_TRUE(ScalarNearlyEqual(converted_stops[2], 1.0f)); +} + +TEST(SkiaConversionsTest, GradientMissingLastValue) { + std::vector colors = {flutter::DlColor::kBlue(), + flutter::DlColor::kRed()}; + std::vector stops = {0.0, .5}; + const auto gradient = + flutter::DlColorSource::MakeLinear(SkPoint::Make(0, 0), // + SkPoint::Make(1.0, 1.0), // + 2, // + colors.data(), // + stops.data(), // + flutter::DlTileMode::kClamp, // + nullptr // + ); + + std::vector converted_colors; + std::vector converted_stops; + skia_conversions::ConvertStops(gradient.get(), converted_colors, + converted_stops); + + // Last color is inserted as red. + ASSERT_TRUE(ScalarNearlyEqual(converted_colors[2].red, 1.0f)); + ASSERT_TRUE(ScalarNearlyEqual(converted_stops[0], 0.0f)); + ASSERT_TRUE(ScalarNearlyEqual(converted_stops[1], 0.5f)); + ASSERT_TRUE(ScalarNearlyEqual(converted_stops[2], 1.0f)); +} + +TEST(SkiaConversionsTest, GradientStopGreaterThan1) { + std::vector colors = {flutter::DlColor::kBlue(), + flutter::DlColor::kGreen(), + flutter::DlColor::kRed()}; + std::vector stops = {0.0, 100, 1.0}; + const auto gradient = + flutter::DlColorSource::MakeLinear(SkPoint::Make(0, 0), // + SkPoint::Make(1.0, 1.0), // + 3, // + colors.data(), // + stops.data(), // + flutter::DlTileMode::kClamp, // + nullptr // + ); + + std::vector converted_colors; + std::vector converted_stops; + skia_conversions::ConvertStops(gradient.get(), converted_colors, + converted_stops); + + // Value is clamped to 1.0 + ASSERT_TRUE(ScalarNearlyEqual(converted_stops[0], 0.0f)); + ASSERT_TRUE(ScalarNearlyEqual(converted_stops[1], 1.0f)); + ASSERT_TRUE(ScalarNearlyEqual(converted_stops[2], 1.0f)); +} + +TEST(SkiaConversionsTest, GradientConversionNonMonotonic) { + std::vector colors = { + flutter::DlColor::kBlue(), flutter::DlColor::kGreen(), + flutter::DlColor::kGreen(), flutter::DlColor::kRed()}; + std::vector stops = {0.0, 0.5, 0.4, 1.0}; + const auto gradient = + flutter::DlColorSource::MakeLinear(SkPoint::Make(0, 0), // + SkPoint::Make(1.0, 1.0), // + 4, // + colors.data(), // + stops.data(), // + flutter::DlTileMode::kClamp, // + nullptr // + ); + + std::vector converted_colors; + std::vector converted_stops; + skia_conversions::ConvertStops(gradient.get(), converted_colors, + converted_stops); + + // Value is clamped to 0.5 + ASSERT_TRUE(ScalarNearlyEqual(converted_stops[0], 0.0f)); + ASSERT_TRUE(ScalarNearlyEqual(converted_stops[1], 0.5f)); + ASSERT_TRUE(ScalarNearlyEqual(converted_stops[2], 0.5f)); + ASSERT_TRUE(ScalarNearlyEqual(converted_stops[3], 1.0f)); +} + } // namespace testing } // namespace impeller diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 6784474e88a31..638a257301d3b 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -4191,7 +4191,11 @@ base class Gradient extends Shader { /// If `colorStops` is provided, `colorStops[i]` is a number from 0.0 to 1.0 /// that specifies where `color[i]` begins in the gradient. If `colorStops` is /// not provided, then only two stops, at 0.0 and 1.0, are implied (and - /// `color` must therefore only have two entries). + /// `color` must therefore only have two entries). Stop values less than 0.0 + /// will be rounded up to 0.0 and stop values greater than 1.0 will be rounded + /// down to 1.0. Each stop value must be greater than or equal to the previous + /// stop value. Stop values that do not meet this criteria will be rounded up + /// to the previous stop value. /// /// The behavior before `from` and after `to` is described by the `tileMode` /// argument. For details, see the [TileMode] enum. @@ -4233,7 +4237,11 @@ base class Gradient extends Shader { /// If `colorStops` is provided, `colorStops[i]` is a number from 0.0 to 1.0 /// that specifies where `color[i]` begins in the gradient. If `colorStops` is /// not provided, then only two stops, at 0.0 and 1.0, are implied (and - /// `color` must therefore only have two entries). + /// `color` must therefore only have two entries). Stop values less than 0.0 + /// will be rounded up to 0.0 and stop values greater than 1.0 will be rounded + /// down to 1.0. Each stop value must be greater than or equal to the previous + /// stop value. Stop values that do not meet this criteria will be rounded up + /// to the previous stop value. /// /// The behavior before and after the radius is described by the `tileMode` /// argument. For details, see the [TileMode] enum. @@ -4295,7 +4303,11 @@ base class Gradient extends Shader { /// If `colorStops` is provided, `colorStops[i]` is a number from 0.0 to 1.0 /// that specifies where `color[i]` begins in the gradient. If `colorStops` is /// not provided, then only two stops, at 0.0 and 1.0, are implied (and - /// `color` must therefore only have two entries). + /// `color` must therefore only have two entries). Stop values less than 0.0 + /// will be rounded up to 0.0 and stop values greater than 1.0 will be rounded + /// down to 1.0. Each stop value must be greater than or equal to the previous + /// stop value. Stop values that do not meet this criteria will be rounded up + /// to the previous stop value. /// /// The behavior before `startAngle` and after `endAngle` is described by the /// `tileMode` argument. For details, see the [TileMode] enum.