[Impeller] support many colors/stops on Linear/Radial/SweepGradient (#35782)
diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter
index d4332c4..21c4d8f 100644
--- a/ci/licenses_golden/licenses_flutter
+++ b/ci/licenses_golden/licenses_flutter
@@ -590,6 +590,8 @@
FILE: ../../../flutter/impeller/entity/contents/filters/morphology_filter_contents.h
FILE: ../../../flutter/impeller/entity/contents/filters/srgb_to_linear_filter_contents.cc
FILE: ../../../flutter/impeller/entity/contents/filters/srgb_to_linear_filter_contents.h
+FILE: ../../../flutter/impeller/entity/contents/gradient_generator.cc
+FILE: ../../../flutter/impeller/entity/contents/gradient_generator.h
FILE: ../../../flutter/impeller/entity/contents/linear_gradient_contents.cc
FILE: ../../../flutter/impeller/entity/contents/linear_gradient_contents.h
FILE: ../../../flutter/impeller/entity/contents/radial_gradient_contents.cc
@@ -678,6 +680,8 @@
FILE: ../../../flutter/impeller/geometry/constants.h
FILE: ../../../flutter/impeller/geometry/geometry_unittests.cc
FILE: ../../../flutter/impeller/geometry/geometry_unittests.h
+FILE: ../../../flutter/impeller/geometry/gradient.cc
+FILE: ../../../flutter/impeller/geometry/gradient.h
FILE: ../../../flutter/impeller/geometry/matrix.cc
FILE: ../../../flutter/impeller/geometry/matrix.h
FILE: ../../../flutter/impeller/geometry/matrix_decomposition.cc
diff --git a/impeller/aiks/aiks_unittests.cc b/impeller/aiks/aiks_unittests.cc
index 53d7544..d66cbe4 100644
--- a/impeller/aiks/aiks_unittests.cc
+++ b/impeller/aiks/aiks_unittests.cc
@@ -4,6 +4,7 @@
#include <array>
#include <cmath>
+#include <iostream>
#include <tuple>
#include "flutter/testing/testing.h"
@@ -334,11 +335,212 @@
canvas.Translate({100.0, 100.0, 0});
auto tile_mode = tile_modes[selected_tile_mode];
paint.color_source = [tile_mode]() {
- auto contents = std::make_shared<LinearGradientContents>();
- contents->SetEndPoints({0, 0}, {200, 200});
std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
Color{0.1294, 0.5882, 0.9529, 1.0}};
+ std::vector<Scalar> stops = {0.0, 1.0};
+
+ auto contents = std::make_shared<LinearGradientContents>();
+ contents->SetEndPoints({0, 0}, {200, 200});
contents->SetColors(std::move(colors));
+ contents->SetStops(std::move(stops));
+ contents->SetTileMode(tile_mode);
+ contents->SetMatrix(matrix);
+ return contents;
+ };
+ canvas.DrawRect({0, 0, 600, 600}, paint);
+ return renderer.Render(canvas.EndRecordingAsPicture(), render_target);
+ };
+ ASSERT_TRUE(OpenPlaygroundHere(callback));
+}
+
+TEST_P(AiksTest, CanRenderLinearGradientManyColors) {
+ bool first_frame = true;
+ auto callback = [&](AiksContext& renderer, RenderTarget& render_target) {
+ if (first_frame) {
+ first_frame = false;
+ ImGui::SetNextWindowSize({480, 100});
+ ImGui::SetNextWindowPos({100, 550});
+ }
+
+ const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
+ const Entity::TileMode tile_modes[] = {
+ Entity::TileMode::kClamp, Entity::TileMode::kRepeat,
+ Entity::TileMode::kMirror, Entity::TileMode::kDecal};
+
+ static int selected_tile_mode = 0;
+ ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
+ ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names,
+ sizeof(tile_mode_names) / sizeof(char*));
+ static Matrix matrix = {
+ 1, 0, 0, 0, //
+ 0, 1, 0, 0, //
+ 0, 0, 1, 0, //
+ 0, 0, 0, 1 //
+ };
+ std::string label = "##1";
+ label.c_str();
+ for (int i = 0; i < 4; i++) {
+ ImGui::InputScalarN(label.c_str(), ImGuiDataType_Float, &(matrix.vec[i]),
+ 4, NULL, NULL, "%.2f", 0);
+ label[2]++;
+ }
+ ImGui::End();
+
+ Canvas canvas;
+ Paint paint;
+ canvas.Translate({100.0, 100.0, 0});
+ auto tile_mode = tile_modes[selected_tile_mode];
+ paint.color_source = [tile_mode]() {
+ std::vector<Color> colors = {
+ Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0},
+ Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0},
+ Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0},
+ Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0},
+ Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0},
+ Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0},
+ Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}};
+ std::vector<Scalar> stops = {
+ 0.0,
+ (1.0 / 6.0) * 1,
+ (1.0 / 6.0) * 2,
+ (1.0 / 6.0) * 3,
+ (1.0 / 6.0) * 4,
+ (1.0 / 6.0) * 5,
+ 1.0,
+ };
+
+ auto contents = std::make_shared<LinearGradientContents>();
+ contents->SetEndPoints({0, 0}, {200, 200});
+ contents->SetColors(std::move(colors));
+ contents->SetStops(std::move(stops));
+ contents->SetTileMode(tile_mode);
+ contents->SetMatrix(matrix);
+ return contents;
+ };
+ canvas.DrawRect({0, 0, 600, 600}, paint);
+ return renderer.Render(canvas.EndRecordingAsPicture(), render_target);
+ };
+ ASSERT_TRUE(OpenPlaygroundHere(callback));
+}
+
+TEST_P(AiksTest, CanRenderLinearGradientWayManyColors) {
+ bool first_frame = true;
+ auto callback = [&](AiksContext& renderer, RenderTarget& render_target) {
+ if (first_frame) {
+ first_frame = false;
+ ImGui::SetNextWindowSize({480, 100});
+ ImGui::SetNextWindowPos({100, 550});
+ }
+
+ const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
+ const Entity::TileMode tile_modes[] = {
+ Entity::TileMode::kClamp, Entity::TileMode::kRepeat,
+ Entity::TileMode::kMirror, Entity::TileMode::kDecal};
+
+ static int selected_tile_mode = 0;
+ ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
+ ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names,
+ sizeof(tile_mode_names) / sizeof(char*));
+ static Matrix matrix = {
+ 1, 0, 0, 0, //
+ 0, 1, 0, 0, //
+ 0, 0, 1, 0, //
+ 0, 0, 0, 1 //
+ };
+ std::string label = "##1";
+ label.c_str();
+ for (int i = 0; i < 4; i++) {
+ ImGui::InputScalarN(label.c_str(), ImGuiDataType_Float, &(matrix.vec[i]),
+ 4, NULL, NULL, "%.2f", 0);
+ label[2]++;
+ }
+ ImGui::End();
+
+ Canvas canvas;
+ Paint paint;
+ canvas.Translate({100.0, 100.0, 0});
+ auto tile_mode = tile_modes[selected_tile_mode];
+ auto color = Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0};
+ std::vector<Color> colors;
+ std::vector<Scalar> stops;
+ auto current_stop = 0.0;
+ for (int i = 0; i < 2000; i++) {
+ colors.push_back(color);
+ stops.push_back(current_stop);
+ current_stop += 1 / 2000.0;
+ }
+ stops[2000 - 1] = 1.0;
+ paint.color_source = [tile_mode, stops = std::move(stops),
+ colors = std::move(colors)]() {
+ auto contents = std::make_shared<LinearGradientContents>();
+ contents->SetEndPoints({0, 0}, {200, 200});
+ contents->SetColors(std::move(colors));
+ contents->SetStops(std::move(stops));
+ contents->SetTileMode(tile_mode);
+ contents->SetMatrix(matrix);
+ return contents;
+ };
+ canvas.DrawRect({0, 0, 600, 600}, paint);
+ return renderer.Render(canvas.EndRecordingAsPicture(), render_target);
+ };
+ ASSERT_TRUE(OpenPlaygroundHere(callback));
+}
+
+TEST_P(AiksTest, CanRenderLinearGradientManyColorsUnevenStops) {
+ bool first_frame = true;
+ auto callback = [&](AiksContext& renderer, RenderTarget& render_target) {
+ if (first_frame) {
+ first_frame = false;
+ ImGui::SetNextWindowSize({480, 100});
+ ImGui::SetNextWindowPos({100, 550});
+ }
+
+ const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
+ const Entity::TileMode tile_modes[] = {
+ Entity::TileMode::kClamp, Entity::TileMode::kRepeat,
+ Entity::TileMode::kMirror, Entity::TileMode::kDecal};
+
+ static int selected_tile_mode = 0;
+ ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
+ ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names,
+ sizeof(tile_mode_names) / sizeof(char*));
+ static Matrix matrix = {
+ 1, 0, 0, 0, //
+ 0, 1, 0, 0, //
+ 0, 0, 1, 0, //
+ 0, 0, 0, 1 //
+ };
+ std::string label = "##1";
+ label.c_str();
+ for (int i = 0; i < 4; i++) {
+ ImGui::InputScalarN(label.c_str(), ImGuiDataType_Float, &(matrix.vec[i]),
+ 4, NULL, NULL, "%.2f", 0);
+ label[2]++;
+ }
+ ImGui::End();
+
+ Canvas canvas;
+ Paint paint;
+ canvas.Translate({100.0, 100.0, 0});
+ auto tile_mode = tile_modes[selected_tile_mode];
+ paint.color_source = [tile_mode]() {
+ std::vector<Color> colors = {
+ Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0},
+ Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0},
+ Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0},
+ Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0},
+ Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0},
+ Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0},
+ Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}};
+ std::vector<Scalar> stops = {
+ 0.0 / 126.0, 2.0 / 126.0, 4.0 / 126.0, 8.0 / 126.0,
+ 16.0 / 126.0, 32.0 / 126.0, 1.0,
+ };
+
+ auto contents = std::make_shared<LinearGradientContents>();
+ contents->SetEndPoints({0, 0}, {200, 200});
+ contents->SetColors(std::move(colors));
+ contents->SetStops(std::move(stops));
contents->SetTileMode(tile_mode);
contents->SetMatrix(matrix);
return contents;
@@ -386,11 +588,84 @@
canvas.Translate({100.0, 100.0, 0});
auto tile_mode = tile_modes[selected_tile_mode];
paint.color_source = [tile_mode]() {
- auto contents = std::make_shared<RadialGradientContents>();
- contents->SetCenterAndRadius({100, 100}, 100);
std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
Color{0.1294, 0.5882, 0.9529, 1.0}};
+ std::vector<Scalar> stops = {0.0, 1.0};
+
+ auto contents = std::make_shared<RadialGradientContents>();
+ contents->SetCenterAndRadius({100, 100}, 100);
contents->SetColors(std::move(colors));
+ contents->SetStops(std::move(stops));
+ contents->SetTileMode(tile_mode);
+ contents->SetMatrix(matrix);
+ return contents;
+ };
+ canvas.DrawRect({0, 0, 600, 600}, paint);
+ return renderer.Render(canvas.EndRecordingAsPicture(), render_target);
+ };
+ ASSERT_TRUE(OpenPlaygroundHere(callback));
+}
+
+TEST_P(AiksTest, CanRenderRadialGradientManyColors) {
+ bool first_frame = true;
+ auto callback = [&](AiksContext& renderer, RenderTarget& render_target) {
+ if (first_frame) {
+ first_frame = false;
+ ImGui::SetNextWindowSize({480, 100});
+ ImGui::SetNextWindowPos({100, 550});
+ }
+
+ const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
+ const Entity::TileMode tile_modes[] = {
+ Entity::TileMode::kClamp, Entity::TileMode::kRepeat,
+ Entity::TileMode::kMirror, Entity::TileMode::kDecal};
+
+ static int selected_tile_mode = 0;
+ ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
+ ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names,
+ sizeof(tile_mode_names) / sizeof(char*));
+ static Matrix matrix = {
+ 1, 0, 0, 0, //
+ 0, 1, 0, 0, //
+ 0, 0, 1, 0, //
+ 0, 0, 0, 1 //
+ };
+ std::string label = "##1";
+ label.c_str();
+ for (int i = 0; i < 4; i++) {
+ ImGui::InputScalarN(label.c_str(), ImGuiDataType_Float, &(matrix.vec[i]),
+ 4, NULL, NULL, "%.2f", 0);
+ label[2]++;
+ }
+ ImGui::End();
+
+ Canvas canvas;
+ Paint paint;
+ canvas.Translate({100.0, 100.0, 0});
+ auto tile_mode = tile_modes[selected_tile_mode];
+ paint.color_source = [tile_mode]() {
+ std::vector<Color> colors = {
+ Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0},
+ Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0},
+ Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0},
+ Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0},
+ Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0},
+ Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0},
+ Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}};
+ std::vector<Scalar> stops = {
+ 0.0,
+ (1.0 / 6.0) * 1,
+ (1.0 / 6.0) * 2,
+ (1.0 / 6.0) * 3,
+ (1.0 / 6.0) * 4,
+ (1.0 / 6.0) * 5,
+ 1.0,
+ };
+
+ auto contents = std::make_shared<RadialGradientContents>();
+ contents->SetCenterAndRadius({100, 100}, 100);
+ contents->SetColors(std::move(colors));
+ contents->SetStops(std::move(stops));
contents->SetTileMode(tile_mode);
contents->SetMatrix(matrix);
return contents;
@@ -442,6 +717,78 @@
contents->SetCenterAndAngles({100, 100}, Degrees(45), Degrees(135));
std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
Color{0.1294, 0.5882, 0.9529, 1.0}};
+ std::vector<Scalar> stops = {0.0, 1.0};
+ contents->SetColors(std::move(colors));
+ contents->SetStops(std::move(stops));
+ contents->SetTileMode(tile_mode);
+ contents->SetMatrix(matrix);
+ return contents;
+ };
+ canvas.DrawRect({0, 0, 600, 600}, paint);
+ return renderer.Render(canvas.EndRecordingAsPicture(), render_target);
+ };
+ ASSERT_TRUE(OpenPlaygroundHere(callback));
+}
+
+TEST_P(AiksTest, CanRenderSweepGradientManyColors) {
+ bool first_frame = true;
+ auto callback = [&](AiksContext& renderer, RenderTarget& render_target) {
+ if (first_frame) {
+ first_frame = false;
+ ImGui::SetNextWindowSize({480, 100});
+ ImGui::SetNextWindowPos({100, 550});
+ }
+
+ const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
+ const Entity::TileMode tile_modes[] = {
+ Entity::TileMode::kClamp, Entity::TileMode::kRepeat,
+ Entity::TileMode::kMirror, Entity::TileMode::kDecal};
+
+ static int selected_tile_mode = 0;
+ ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
+ ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names,
+ sizeof(tile_mode_names) / sizeof(char*));
+ static Matrix matrix = {
+ 1, 0, 0, 0, //
+ 0, 1, 0, 0, //
+ 0, 0, 1, 0, //
+ 0, 0, 0, 1 //
+ };
+ std::string label = "##1";
+ label.c_str();
+ for (int i = 0; i < 4; i++) {
+ ImGui::InputScalarN(label.c_str(), ImGuiDataType_Float, &(matrix.vec[i]),
+ 4, NULL, NULL, "%.2f", 0);
+ label[2]++;
+ }
+ ImGui::End();
+
+ Canvas canvas;
+ Paint paint;
+ canvas.Translate({100.0, 100.0, 0});
+ auto tile_mode = tile_modes[selected_tile_mode];
+ paint.color_source = [tile_mode]() {
+ auto contents = std::make_shared<SweepGradientContents>();
+ contents->SetCenterAndAngles({100, 100}, Degrees(45), Degrees(135));
+ std::vector<Color> colors = {
+ Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0},
+ Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0},
+ Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0},
+ Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0},
+ Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0},
+ Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0},
+ Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}};
+ std::vector<Scalar> stops = {
+ 0.0,
+ (1.0 / 6.0) * 1,
+ (1.0 / 6.0) * 2,
+ (1.0 / 6.0) * 3,
+ (1.0 / 6.0) * 4,
+ (1.0 / 6.0) * 5,
+ 1.0,
+ };
+
+ contents->SetStops(std::move(stops));
contents->SetColors(std::move(colors));
contents->SetTileMode(tile_mode);
contents->SetMatrix(matrix);
@@ -461,7 +808,12 @@
contents->SetEndPoints({0, 0}, {100, 100});
std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
Color{0.1294, 0.5882, 0.9529, 1.0}};
+ std::vector<Scalar> stops = {
+ 0.0,
+ 1.0,
+ };
contents->SetColors(std::move(colors));
+ contents->SetStops(std::move(stops));
contents->SetTileMode(Entity::TileMode::kRepeat);
return contents;
};
diff --git a/impeller/display_list/display_list_dispatcher.cc b/impeller/display_list/display_list_dispatcher.cc
index e28a4c0..9f118a4 100644
--- a/impeller/display_list/display_list_dispatcher.cc
+++ b/impeller/display_list/display_list_dispatcher.cc
@@ -313,16 +313,19 @@
auto start_point = ToPoint(linear->start_point());
auto end_point = ToPoint(linear->end_point());
std::vector<Color> colors;
+ std::vector<float> stops;
for (auto i = 0; i < linear->stop_count(); i++) {
colors.emplace_back(ToColor(linear->colors()[i]));
+ stops.emplace_back(linear->stops()[i]);
}
auto tile_mode = ToTileMode(linear->tile_mode());
auto matrix = ToMatrix(linear->matrix());
paint_.color_source = [start_point, end_point, colors = std::move(colors),
- tile_mode, matrix]() {
+ stops = std::move(stops), tile_mode, matrix]() {
auto contents = std::make_shared<LinearGradientContents>();
- contents->SetEndPoints(start_point, end_point);
contents->SetColors(std::move(colors));
+ contents->SetStops(std::move(stops));
+ contents->SetEndPoints(start_point, end_point);
contents->SetTileMode(tile_mode);
contents->SetMatrix(matrix);
return contents;
@@ -336,16 +339,19 @@
auto center = ToPoint(radialGradient->center());
auto radius = radialGradient->radius();
std::vector<Color> colors;
+ std::vector<float> stops;
for (auto i = 0; i < radialGradient->stop_count(); i++) {
colors.emplace_back(ToColor(radialGradient->colors()[i]));
+ stops.emplace_back(radialGradient->stops()[i]);
}
auto tile_mode = ToTileMode(radialGradient->tile_mode());
auto matrix = ToMatrix(radialGradient->matrix());
paint_.color_source = [center, radius, colors = std::move(colors),
- tile_mode, matrix]() {
+ stops = std::move(stops), tile_mode, matrix]() {
auto contents = std::make_shared<RadialGradientContents>();
- contents->SetCenterAndRadius(center, radius);
contents->SetColors(std::move(colors));
+ contents->SetStops(std::move(stops));
+ contents->SetCenterAndRadius(center, radius);
contents->SetTileMode(tile_mode);
contents->SetMatrix(matrix);
return contents;
@@ -361,16 +367,20 @@
auto start_angle = Degrees(sweepGradient->start());
auto end_angle = Degrees(sweepGradient->end());
std::vector<Color> colors;
+ std::vector<Scalar> stops;
for (auto i = 0; i < sweepGradient->stop_count(); i++) {
colors.emplace_back(ToColor(sweepGradient->colors()[i]));
+ stops.emplace_back(sweepGradient->stops()[i]);
}
auto tile_mode = ToTileMode(sweepGradient->tile_mode());
auto matrix = ToMatrix(sweepGradient->matrix());
paint_.color_source = [center, start_angle, end_angle,
- colors = std::move(colors), tile_mode, matrix]() {
+ colors = std::move(colors),
+ stops = std::move(stops), tile_mode, matrix]() {
auto contents = std::make_shared<SweepGradientContents>();
contents->SetCenterAndAngles(center, start_angle, end_angle);
contents->SetColors(std::move(colors));
+ contents->SetStops(std::move(stops));
contents->SetTileMode(tile_mode);
contents->SetMatrix(matrix);
return contents;
diff --git a/impeller/entity/BUILD.gn b/impeller/entity/BUILD.gn
index 362e264..9d3fb34 100644
--- a/impeller/entity/BUILD.gn
+++ b/impeller/entity/BUILD.gn
@@ -97,6 +97,8 @@
"contents/filters/morphology_filter_contents.h",
"contents/filters/srgb_to_linear_filter_contents.cc",
"contents/filters/srgb_to_linear_filter_contents.h",
+ "contents/gradient_generator.cc",
+ "contents/gradient_generator.h",
"contents/linear_gradient_contents.cc",
"contents/linear_gradient_contents.h",
"contents/radial_gradient_contents.cc",
diff --git a/impeller/entity/contents/content_context.h b/impeller/entity/contents/content_context.h
index 2597353..3b671f1 100644
--- a/impeller/entity/contents/content_context.h
+++ b/impeller/entity/contents/content_context.h
@@ -196,6 +196,7 @@
ContentContextOptions opts) const {
return GetPipeline(radial_gradient_fill_pipelines_, opts);
}
+
std::shared_ptr<Pipeline<PipelineDescriptor>> GetRRectBlurPipeline(
ContentContextOptions opts) const {
return GetPipeline(rrect_blur_pipelines_, opts);
diff --git a/impeller/entity/contents/gradient_generator.cc b/impeller/entity/contents/gradient_generator.cc
new file mode 100644
index 0000000..bfb0aff
--- /dev/null
+++ b/impeller/entity/contents/gradient_generator.cc
@@ -0,0 +1,50 @@
+// Copyright 2013 The Flutter 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 <algorithm>
+
+#include "impeller/entity/contents/gradient_generator.h"
+
+#include "flutter/fml/logging.h"
+#include "impeller/entity/contents/content_context.h"
+#include "impeller/geometry/gradient.h"
+#include "impeller/renderer/context.h"
+#include "impeller/renderer/render_pass.h"
+#include "impeller/renderer/texture.h"
+
+namespace impeller {
+
+std::shared_ptr<Texture> CreateGradientTexture(
+ const std::vector<Color>& colors,
+ const std::vector<Scalar>& stops,
+ std::shared_ptr<impeller::Context> context) {
+ // If the computed scale is nearly the same as the color length, then the
+ // stops are evenly spaced and we can lerp entirely in the gradient shader.
+ // Thus we only need to populate a texture with all of the colors in order.
+ // For other cases, we may have more colors than we can fit in the texture,
+ // or we may have very small stop values. For these gradients the lerped
+ // values are computed here and then populated in a texture.
+ uint32_t texture_size;
+ auto color_stop_channels = CreateGradientBuffer(colors, stops, &texture_size);
+ impeller::TextureDescriptor texture_descriptor;
+ texture_descriptor.storage_mode = impeller::StorageMode::kHostVisible;
+ texture_descriptor.format = PixelFormat::kR8G8B8A8UNormInt;
+ texture_descriptor.size = {texture_size, 1};
+ auto texture =
+ context->GetResourceAllocator()->CreateTexture(texture_descriptor);
+ if (!texture) {
+ FML_DLOG(ERROR) << "Could not create Impeller texture.";
+ return nullptr;
+ }
+
+ auto mapping = std::make_shared<fml::DataMapping>(color_stop_channels);
+ if (!texture->SetContents(mapping)) {
+ FML_DLOG(ERROR) << "Could not copy contents into Impeller texture.";
+ return nullptr;
+ }
+ texture->SetLabel(impeller::SPrintF("Gradient(%p)", texture.get()).c_str());
+ return texture;
+}
+
+} // namespace impeller
diff --git a/impeller/entity/contents/gradient_generator.h b/impeller/entity/contents/gradient_generator.h
new file mode 100644
index 0000000..ac58948
--- /dev/null
+++ b/impeller/entity/contents/gradient_generator.h
@@ -0,0 +1,30 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#pragma once
+
+#include <functional>
+#include <memory>
+#include <vector>
+
+#include "flutter/fml/macros.h"
+#include "flutter/impeller/renderer/texture.h"
+#include "impeller/geometry/color.h"
+#include "impeller/geometry/path.h"
+#include "impeller/geometry/point.h"
+
+namespace impeller {
+
+class Context;
+
+/**
+ * @brief Create a host visible texture that contains the gradient defined
+ * by the provided colors and stops.
+ */
+std::shared_ptr<Texture> CreateGradientTexture(
+ const std::vector<Color>& colors,
+ const std::vector<Scalar>& stops,
+ std::shared_ptr<impeller::Context> context);
+
+} // namespace impeller
diff --git a/impeller/entity/contents/linear_gradient_contents.cc b/impeller/entity/contents/linear_gradient_contents.cc
index 7eb0962..9144410 100644
--- a/impeller/entity/contents/linear_gradient_contents.cc
+++ b/impeller/entity/contents/linear_gradient_contents.cc
@@ -6,8 +6,11 @@
#include "flutter/fml/logging.h"
#include "impeller/entity/contents/content_context.h"
+#include "impeller/entity/contents/gradient_generator.h"
#include "impeller/entity/entity.h"
+#include "impeller/renderer/formats.h"
#include "impeller/renderer/render_pass.h"
+#include "impeller/renderer/sampler_library.h"
#include "impeller/tessellator/tessellator.h"
namespace impeller {
@@ -23,22 +26,24 @@
void LinearGradientContents::SetColors(std::vector<Color> colors) {
colors_ = std::move(colors);
- if (colors_.empty()) {
- colors_.push_back(Color::Black());
- colors_.push_back(Color::Black());
- } else if (colors_.size() < 2u) {
- colors_.push_back(colors_.back());
- }
}
-void LinearGradientContents::SetTileMode(Entity::TileMode tile_mode) {
- tile_mode_ = tile_mode;
+void LinearGradientContents::SetStops(std::vector<Scalar> stops) {
+ stops_ = std::move(stops);
}
const std::vector<Color>& LinearGradientContents::GetColors() const {
return colors_;
}
+const std::vector<Scalar>& LinearGradientContents::GetStops() const {
+ return stops_;
+}
+
+void LinearGradientContents::SetTileMode(Entity::TileMode tile_mode) {
+ tile_mode_ = tile_mode;
+}
+
bool LinearGradientContents::Render(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
@@ -63,17 +68,23 @@
}
}
- VS::FrameInfo frame_info;
- frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
- entity.GetTransformation();
- frame_info.matrix = GetInverseMatrix();
+ auto gradient_texture =
+ CreateGradientTexture(colors_, stops_, renderer.GetContext());
+ if (gradient_texture == nullptr) {
+ return false;
+ }
FS::GradientInfo gradient_info;
gradient_info.start_point = start_point_;
gradient_info.end_point = end_point_;
- gradient_info.start_color = colors_[0].Premultiply();
- gradient_info.end_color = colors_[1].Premultiply();
gradient_info.tile_mode = static_cast<Scalar>(tile_mode_);
+ gradient_info.texture_sampler_y_coord_scale =
+ gradient_texture->GetYCoordScale();
+
+ VS::FrameInfo frame_info;
+ frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
+ entity.GetTransformation();
+ frame_info.matrix = GetInverseMatrix();
Command cmd;
cmd.label = "LinearGradientFill";
@@ -85,6 +96,12 @@
cmd.primitive_type = PrimitiveType::kTriangle;
FS::BindGradientInfo(
cmd, pass.GetTransientsBuffer().EmplaceUniform(gradient_info));
+ SamplerDescriptor sampler_desc;
+ sampler_desc.min_filter = MinMagFilter::kLinear;
+ sampler_desc.mag_filter = MinMagFilter::kLinear;
+ FS::BindTextureSampler(
+ cmd, std::move(gradient_texture),
+ renderer.GetContext()->GetSamplerLibrary()->GetSampler(sampler_desc));
VS::BindFrameInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(frame_info));
return pass.AddCommand(std::move(cmd));
}
diff --git a/impeller/entity/contents/linear_gradient_contents.h b/impeller/entity/contents/linear_gradient_contents.h
index 4ba2c82..fbc9ec1 100644
--- a/impeller/entity/contents/linear_gradient_contents.h
+++ b/impeller/entity/contents/linear_gradient_contents.h
@@ -9,6 +9,7 @@
#include <vector>
#include "flutter/fml/macros.h"
+#include "flutter/impeller/renderer/texture.h"
#include "impeller/entity/contents/color_source_contents.h"
#include "impeller/entity/entity.h"
#include "impeller/geometry/color.h"
@@ -32,14 +33,19 @@
void SetColors(std::vector<Color> colors);
- void SetTileMode(Entity::TileMode tile_mode);
+ void SetStops(std::vector<Scalar> stops);
const std::vector<Color>& GetColors() const;
+ const std::vector<Scalar>& GetStops() const;
+
+ void SetTileMode(Entity::TileMode tile_mode);
+
private:
Point start_point_;
Point end_point_;
std::vector<Color> colors_;
+ std::vector<Scalar> stops_;
Entity::TileMode tile_mode_;
FML_DISALLOW_COPY_AND_ASSIGN(LinearGradientContents);
diff --git a/impeller/entity/contents/radial_gradient_contents.cc b/impeller/entity/contents/radial_gradient_contents.cc
index 605a5fe..f8bffbd 100644
--- a/impeller/entity/contents/radial_gradient_contents.cc
+++ b/impeller/entity/contents/radial_gradient_contents.cc
@@ -6,8 +6,10 @@
#include "flutter/fml/logging.h"
#include "impeller/entity/contents/content_context.h"
+#include "impeller/entity/contents/gradient_generator.h"
#include "impeller/entity/entity.h"
#include "impeller/renderer/render_pass.h"
+#include "impeller/renderer/sampler_library.h"
#include "impeller/tessellator/tessellator.h"
namespace impeller {
@@ -21,30 +23,31 @@
radius_ = radius;
}
-void RadialGradientContents::SetColors(std::vector<Color> colors) {
- colors_ = std::move(colors);
- if (colors_.empty()) {
- colors_.push_back(Color::Black());
- colors_.push_back(Color::Black());
- } else if (colors_.size() < 2u) {
- colors_.push_back(colors_.back());
- }
-}
-
void RadialGradientContents::SetTileMode(Entity::TileMode tile_mode) {
tile_mode_ = tile_mode;
}
+void RadialGradientContents::SetColors(std::vector<Color> colors) {
+ colors_ = std::move(colors);
+}
+
+void RadialGradientContents::SetStops(std::vector<Scalar> stops) {
+ stops_ = std::move(stops);
+}
+
const std::vector<Color>& RadialGradientContents::GetColors() const {
return colors_;
}
+const std::vector<Scalar>& RadialGradientContents::GetStops() const {
+ return stops_;
+}
+
bool RadialGradientContents::Render(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
using VS = RadialGradientFillPipeline::VertexShader;
using FS = RadialGradientFillPipeline::FragmentShader;
-
auto vertices_builder = VertexBufferBuilder<VS::PerVertexData>();
{
auto result = Tessellator{}.Tessellate(GetPath().GetFillType(),
@@ -63,17 +66,23 @@
}
}
- VS::FrameInfo frame_info;
- frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
- entity.GetTransformation();
- frame_info.matrix = GetInverseMatrix();
+ auto gradient_texture =
+ CreateGradientTexture(colors_, stops_, renderer.GetContext());
+ if (gradient_texture == nullptr) {
+ return false;
+ }
FS::GradientInfo gradient_info;
gradient_info.center = center_;
gradient_info.radius = radius_;
- gradient_info.center_color = colors_[0].Premultiply();
- gradient_info.edge_color = colors_[1].Premultiply();
gradient_info.tile_mode = static_cast<Scalar>(tile_mode_);
+ gradient_info.texture_sampler_y_coord_scale =
+ gradient_texture->GetYCoordScale();
+
+ VS::FrameInfo frame_info;
+ frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
+ entity.GetTransformation();
+ frame_info.matrix = GetInverseMatrix();
Command cmd;
cmd.label = "RadialGradientFill";
@@ -85,6 +94,12 @@
cmd.primitive_type = PrimitiveType::kTriangle;
FS::BindGradientInfo(
cmd, pass.GetTransientsBuffer().EmplaceUniform(gradient_info));
+ SamplerDescriptor sampler_desc;
+ sampler_desc.min_filter = MinMagFilter::kLinear;
+ sampler_desc.mag_filter = MinMagFilter::kLinear;
+ FS::BindTextureSampler(
+ cmd, gradient_texture,
+ renderer.GetContext()->GetSamplerLibrary()->GetSampler(sampler_desc));
VS::BindFrameInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(frame_info));
return pass.AddCommand(std::move(cmd));
}
diff --git a/impeller/entity/contents/radial_gradient_contents.h b/impeller/entity/contents/radial_gradient_contents.h
index 226e9d8..af659e6 100644
--- a/impeller/entity/contents/radial_gradient_contents.h
+++ b/impeller/entity/contents/radial_gradient_contents.h
@@ -32,14 +32,19 @@
void SetColors(std::vector<Color> colors);
- void SetTileMode(Entity::TileMode tile_mode);
+ void SetStops(std::vector<Scalar> stops);
const std::vector<Color>& GetColors() const;
+ const std::vector<Scalar>& GetStops() const;
+
+ void SetTileMode(Entity::TileMode tile_mode);
+
private:
Point center_;
Scalar radius_;
std::vector<Color> colors_;
+ std::vector<Scalar> stops_;
Entity::TileMode tile_mode_;
FML_DISALLOW_COPY_AND_ASSIGN(RadialGradientContents);
diff --git a/impeller/entity/contents/sweep_gradient_contents.cc b/impeller/entity/contents/sweep_gradient_contents.cc
index a5b332d..5e4e9f1 100644
--- a/impeller/entity/contents/sweep_gradient_contents.cc
+++ b/impeller/entity/contents/sweep_gradient_contents.cc
@@ -6,8 +6,10 @@
#include "flutter/fml/logging.h"
#include "impeller/entity/contents/content_context.h"
+#include "impeller/entity/contents/gradient_generator.h"
#include "impeller/entity/entity.h"
#include "impeller/renderer/render_pass.h"
+#include "impeller/renderer/sampler_library.h"
#include "impeller/tessellator/tessellator.h"
namespace impeller {
@@ -29,12 +31,10 @@
void SweepGradientContents::SetColors(std::vector<Color> colors) {
colors_ = std::move(colors);
- if (colors_.empty()) {
- colors_.push_back(Color::Black());
- colors_.push_back(Color::Black());
- } else if (colors_.size() < 2u) {
- colors_.push_back(colors_.back());
- }
+}
+
+void SweepGradientContents::SetStops(std::vector<Scalar> stops) {
+ stops_ = std::move(stops);
}
void SweepGradientContents::SetTileMode(Entity::TileMode tile_mode) {
@@ -45,6 +45,10 @@
return colors_;
}
+const std::vector<Scalar>& SweepGradientContents::GetStops() const {
+ return stops_;
+}
+
bool SweepGradientContents::Render(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
@@ -69,19 +73,25 @@
}
}
- VS::FrameInfo frame_info;
- frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
- entity.GetTransformation();
- frame_info.matrix = GetInverseMatrix();
+ auto gradient_texture =
+ CreateGradientTexture(colors_, stops_, renderer.GetContext());
+ if (gradient_texture == nullptr) {
+ return false;
+ }
FS::GradientInfo gradient_info;
gradient_info.center = center_;
gradient_info.bias = bias_;
gradient_info.scale = scale_;
- gradient_info.start_color = colors_[0].Premultiply();
- gradient_info.end_color = colors_[1].Premultiply();
+ gradient_info.texture_sampler_y_coord_scale =
+ gradient_texture->GetYCoordScale();
gradient_info.tile_mode = static_cast<Scalar>(tile_mode_);
+ VS::FrameInfo frame_info;
+ frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
+ entity.GetTransformation();
+ frame_info.matrix = GetInverseMatrix();
+
Command cmd;
cmd.label = "SweepGradientFill";
cmd.pipeline = renderer.GetSweepGradientFillPipeline(
@@ -93,6 +103,12 @@
FS::BindGradientInfo(
cmd, pass.GetTransientsBuffer().EmplaceUniform(gradient_info));
VS::BindFrameInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(frame_info));
+ SamplerDescriptor sampler_desc;
+ sampler_desc.min_filter = MinMagFilter::kLinear;
+ sampler_desc.mag_filter = MinMagFilter::kLinear;
+ FS::BindTextureSampler(
+ cmd, gradient_texture,
+ renderer.GetContext()->GetSamplerLibrary()->GetSampler(sampler_desc));
return pass.AddCommand(std::move(cmd));
}
diff --git a/impeller/entity/contents/sweep_gradient_contents.h b/impeller/entity/contents/sweep_gradient_contents.h
index 2a2b345..a25c2f0 100644
--- a/impeller/entity/contents/sweep_gradient_contents.h
+++ b/impeller/entity/contents/sweep_gradient_contents.h
@@ -33,15 +33,20 @@
void SetColors(std::vector<Color> colors);
+ void SetStops(std::vector<Scalar> stops);
+
void SetTileMode(Entity::TileMode tile_mode);
const std::vector<Color>& GetColors() const;
+ const std::vector<Scalar>& GetStops() const;
+
private:
Point center_;
Scalar bias_;
Scalar scale_;
std::vector<Color> colors_;
+ std::vector<Scalar> stops_;
Entity::TileMode tile_mode_;
FML_DISALLOW_COPY_AND_ASSIGN(SweepGradientContents);
diff --git a/impeller/entity/shaders/linear_gradient_fill.frag b/impeller/entity/shaders/linear_gradient_fill.frag
index 5282d4d..467c422 100644
--- a/impeller/entity/shaders/linear_gradient_fill.frag
+++ b/impeller/entity/shaders/linear_gradient_fill.frag
@@ -4,12 +4,13 @@
#include <impeller/texture.glsl>
+uniform sampler2D texture_sampler;
+
uniform GradientInfo {
vec2 start_point;
vec2 end_point;
- vec4 start_color;
- vec4 end_color;
float tile_mode;
+ float texture_sampler_y_coord_scale;
} gradient_info;
in vec2 v_position;
@@ -23,11 +24,10 @@
gradient_info.end_point - gradient_info.start_point
);
float t = dot / (len * len);
- if ((t < 0.0 || t > 1.0) && gradient_info.tile_mode == kTileModeDecal) {
- frag_color = vec4(0);
- return;
- }
-
- t = IPFloatTile(t, gradient_info.tile_mode);
- frag_color = mix(gradient_info.start_color, gradient_info.end_color, t);
+ frag_color = IPSampleWithTileMode(
+ texture_sampler,
+ vec2(t, 0.5),
+ gradient_info.texture_sampler_y_coord_scale,
+ gradient_info.tile_mode,
+ gradient_info.tile_mode);
}
diff --git a/impeller/entity/shaders/radial_gradient_fill.frag b/impeller/entity/shaders/radial_gradient_fill.frag
index 3804d2f9..4029694 100644
--- a/impeller/entity/shaders/radial_gradient_fill.frag
+++ b/impeller/entity/shaders/radial_gradient_fill.frag
@@ -4,12 +4,13 @@
#include <impeller/texture.glsl>
+uniform sampler2D texture_sampler;
+
uniform GradientInfo {
vec2 center;
float radius;
- vec4 center_color;
- vec4 edge_color;
float tile_mode;
+ float texture_sampler_y_coord_scale;
} gradient_info;
in vec2 v_position;
@@ -19,11 +20,10 @@
void main() {
float len = length(v_position - gradient_info.center);
float t = len / gradient_info.radius;
- if ((t < 0.0 || t > 1.0) && gradient_info.tile_mode == kTileModeDecal) {
- frag_color = vec4(0);
- return;
- }
-
- t = IPFloatTile(t, gradient_info.tile_mode);
- frag_color = mix(gradient_info.center_color, gradient_info.edge_color, t);
+ frag_color = IPSampleWithTileMode(
+ texture_sampler,
+ vec2(t, 0.5),
+ gradient_info.texture_sampler_y_coord_scale,
+ gradient_info.tile_mode,
+ gradient_info.tile_mode);
}
diff --git a/impeller/entity/shaders/sweep_gradient_fill.frag b/impeller/entity/shaders/sweep_gradient_fill.frag
index f7ab313..674791f 100644
--- a/impeller/entity/shaders/sweep_gradient_fill.frag
+++ b/impeller/entity/shaders/sweep_gradient_fill.frag
@@ -5,13 +5,14 @@
#include <impeller/constants.glsl>
#include <impeller/texture.glsl>
+uniform sampler2D texture_sampler;
+
uniform GradientInfo {
vec2 center;
float bias;
float scale;
- vec4 start_color;
- vec4 end_color;
float tile_mode;
+ float texture_sampler_y_coord_scale;
} gradient_info;
in vec2 v_position;
@@ -23,11 +24,10 @@
float angle = atan(-coord.y, -coord.x);
float t = (angle * k1Over2Pi + 0.5 + gradient_info.bias) * gradient_info.scale;
- if ((t < 0.0 || t > 1.0) && gradient_info.tile_mode == kTileModeDecal) {
- frag_color = vec4(0);
- return;
- }
-
- t = IPFloatTile(t, gradient_info.tile_mode);
- frag_color = mix(gradient_info.start_color, gradient_info.end_color, t);
+ frag_color = IPSampleWithTileMode(
+ texture_sampler,
+ vec2(t, 0.5),
+ gradient_info.texture_sampler_y_coord_scale,
+ gradient_info.tile_mode,
+ gradient_info.tile_mode);
}
diff --git a/impeller/geometry/BUILD.gn b/impeller/geometry/BUILD.gn
index 0afe7a6..0386575 100644
--- a/impeller/geometry/BUILD.gn
+++ b/impeller/geometry/BUILD.gn
@@ -10,6 +10,8 @@
"color.h",
"constants.cc",
"constants.h",
+ "gradient.cc",
+ "gradient.h",
"matrix.cc",
"matrix.h",
"matrix_decomposition.cc",
diff --git a/impeller/geometry/color.h b/impeller/geometry/color.h
index ad324e7..e2a32fb 100644
--- a/impeller/geometry/color.h
+++ b/impeller/geometry/color.h
@@ -4,6 +4,8 @@
#pragma once
+#include <stdint.h>
+#include <array>
#include <cstdlib>
#include <ostream>
@@ -60,6 +62,34 @@
return {red / alpha, green / alpha, blue / alpha, alpha};
}
+ /**
+ * @brief Return a color that is linearly interpolated between colors a
+ * and b, according to the value of t.
+ *
+ * @param a The lower color.
+ * @param b The upper color.
+ * @param t A value between 0.0 and 1.0, inclusive.
+ * @return constexpr Color
+ */
+ constexpr static Color lerp(Color a, Color b, Scalar t) {
+ Scalar tt = 1.0 - t;
+ return {a.red * tt + b.red * t, a.green * tt + b.green * t,
+ a.blue * tt + b.blue * t, a.alpha * tt + b.alpha * t};
+ }
+
+ /**
+ * @brief Convert to R8G8B8A8 representation.
+ *
+ * @return constexpr std::array<u_int8, 4>
+ */
+ constexpr std::array<uint8_t, 4> ToR8G8B8A8() const {
+ uint8_t r = std::round(red * 255);
+ uint8_t g = std::round(green * 255);
+ uint8_t b = std::round(blue * 255);
+ uint8_t a = std::round(alpha * 255);
+ return {r, g, b, a};
+ }
+
static constexpr Color White() { return {1.0, 1.0, 1.0, 1.0}; }
static constexpr Color Black() { return {0.0, 0.0, 0.0, 1.0}; }
diff --git a/impeller/geometry/geometry_unittests.cc b/impeller/geometry/geometry_unittests.cc
index c1c92f3..db027c3 100644
--- a/impeller/geometry/geometry_unittests.cc
+++ b/impeller/geometry/geometry_unittests.cc
@@ -9,6 +9,7 @@
#include "flutter/testing/testing.h"
#include "impeller/geometry/constants.h"
+#include "impeller/geometry/gradient.h"
#include "impeller/geometry/path.h"
#include "impeller/geometry/path_builder.h"
#include "impeller/geometry/path_component.h"
@@ -907,6 +908,48 @@
}
}
+TEST(GeometryTest, ColorR8G8B8A8) {
+ {
+ Color a(1.0, 0.5, 0.2, 0.5);
+ std::array<uint8_t, 4> expected = {255, 128, 51, 128};
+ ASSERT_ARRAY_4_NEAR(a.ToR8G8B8A8(), expected);
+ }
+
+ {
+ Color a(0.0, 0.0, 0.0, 0.0);
+ std::array<uint8_t, 4> expected = {0, 0, 0, 0};
+ ASSERT_ARRAY_4_NEAR(a.ToR8G8B8A8(), expected);
+ }
+
+ {
+ Color a(1.0, 1.0, 1.0, 1.0);
+ std::array<uint8_t, 4> expected = {255, 255, 255, 255};
+ ASSERT_ARRAY_4_NEAR(a.ToR8G8B8A8(), expected);
+ }
+}
+
+TEST(GeometryTest, ColorLerp) {
+ {
+ Color a(0.0, 0.0, 0.0, 0.0);
+ Color b(1.0, 1.0, 1.0, 1.0);
+
+ ASSERT_COLOR_NEAR(Color::lerp(a, b, 0.5), Color(0.5, 0.5, 0.5, 0.5));
+ ASSERT_COLOR_NEAR(Color::lerp(a, b, 0.0), a);
+ ASSERT_COLOR_NEAR(Color::lerp(a, b, 1.0), b);
+ ASSERT_COLOR_NEAR(Color::lerp(a, b, 0.2), Color(0.2, 0.2, 0.2, 0.2));
+ }
+
+ {
+ Color a(0.2, 0.4, 1.0, 0.5);
+ Color b(0.4, 1.0, 0.2, 0.3);
+
+ ASSERT_COLOR_NEAR(Color::lerp(a, b, 0.5), Color(0.3, 0.7, 0.6, 0.4));
+ ASSERT_COLOR_NEAR(Color::lerp(a, b, 0.0), a);
+ ASSERT_COLOR_NEAR(Color::lerp(a, b, 1.0), b);
+ ASSERT_COLOR_NEAR(Color::lerp(a, b, 0.2), Color(0.24, 0.52, 0.84, 0.46));
+ }
+}
+
TEST(GeometryTest, CanConvertBetweenDegressAndRadians) {
{
auto deg = Degrees{90.0};
@@ -1360,5 +1403,69 @@
))");
}
+TEST(GeometryTest, Gradient) {
+ {
+ // Simple 2 color gradient produces color buffer containing exactly those
+ // values.
+ std::vector<Color> colors = {Color::Red(), Color::Blue()};
+ std::vector<Scalar> stops = {0.0, 1.0};
+ uint32_t texture_size;
+
+ auto gradient = CreateGradientBuffer(colors, stops, &texture_size);
+
+ ASSERT_COLOR_BUFFER_NEAR(gradient, colors);
+ ASSERT_EQ(texture_size, 2u);
+ }
+
+ {
+ // Simple N color gradient produces color buffer containing exactly those
+ // values.
+ std::vector<Color> colors = {Color::Red(), Color::Blue(), Color::Green(),
+ Color::White()};
+ std::vector<Scalar> stops = {0.0, 0.33, 0.66, 1.0};
+ uint32_t texture_size;
+
+ auto gradient = CreateGradientBuffer(colors, stops, &texture_size);
+
+ ASSERT_COLOR_BUFFER_NEAR(gradient, colors);
+ ASSERT_EQ(texture_size, 4u);
+ }
+
+ {
+ // Gradient with color stops will lerp and scale buffer.
+ std::vector<Color> colors = {Color::Red(), Color::Blue(), Color::Green()};
+ std::vector<Scalar> stops = {0.0, 0.25, 1.0};
+ uint32_t texture_size;
+
+ auto gradient = CreateGradientBuffer(colors, stops, &texture_size);
+
+ std::vector<Color> lerped_colors = {
+ Color::Red(),
+ Color::Blue(),
+ Color::lerp(Color::Blue(), Color::Green(), 0.3333),
+ Color::lerp(Color::Blue(), Color::Green(), 0.6666),
+ Color::Green(),
+ };
+ ASSERT_COLOR_BUFFER_NEAR(gradient, lerped_colors);
+ ASSERT_EQ(texture_size, 5u);
+ }
+
+ {
+ // Gradient size is capped at 1024.
+ std::vector<Color> colors = {};
+ std::vector<Scalar> stops = {};
+ for (auto i = 0u; i < 2000; i++) {
+ colors.push_back(Color::Blue());
+ stops.push_back(i / 2000.0);
+ }
+ stops[1999] = 1.0;
+
+ uint32_t texture_size;
+ auto gradient = CreateGradientBuffer(colors, stops, &texture_size);
+
+ ASSERT_EQ(texture_size, 1024u);
+ }
+}
+
} // namespace testing
} // namespace impeller
diff --git a/impeller/geometry/geometry_unittests.h b/impeller/geometry/geometry_unittests.h
index c2e4e28..4407ad8 100644
--- a/impeller/geometry/geometry_unittests.h
+++ b/impeller/geometry/geometry_unittests.h
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include <array>
#include <iostream>
#include "gtest/gtest.h"
@@ -101,6 +102,35 @@
: ::testing::AssertionFailure() << "Sizes are not equal.";
}
+inline ::testing::AssertionResult Array4Near(std::array<uint8_t, 4> a,
+ std::array<uint8_t, 4> b) {
+ auto equal = NumberNear(a[0], b[0]) && NumberNear(a[1], b[1]) &&
+ NumberNear(a[2], b[2]) && NumberNear(a[3], b[3]);
+
+ return equal ? ::testing::AssertionSuccess()
+ : ::testing::AssertionFailure() << "Arrays are not equal.";
+}
+
+inline ::testing::AssertionResult ColorBufferNear(
+ std::vector<uint8_t> a,
+ std::vector<impeller::Color> b) {
+ if (a.size() != b.size() * 4) {
+ return ::testing::AssertionFailure()
+ << "Color buffer length does not match";
+ }
+ for (auto i = 0u; i < b.size(); i++) {
+ auto right = b[i].Premultiply().ToR8G8B8A8();
+ auto j = i * 4;
+ auto equal = NumberNear(a[j], right[0]) && NumberNear(a[j + 1], right[1]) &&
+ NumberNear(a[j + 2], right[2]) &&
+ NumberNear(a[j + 3], right[3]);
+ if (!equal) {
+ ::testing::AssertionFailure() << "Color buffers are not equal.";
+ }
+ }
+ return ::testing::AssertionSuccess();
+}
+
#define ASSERT_MATRIX_NEAR(a, b) ASSERT_PRED2(&::MatrixNear, a, b)
#define ASSERT_QUATERNION_NEAR(a, b) ASSERT_PRED2(&::QuaternionNear, a, b)
#define ASSERT_RECT_NEAR(a, b) ASSERT_PRED2(&::RectNear, a, b)
@@ -109,3 +139,5 @@
#define ASSERT_VECTOR3_NEAR(a, b) ASSERT_PRED2(&::Vector3Near, a, b)
#define ASSERT_VECTOR4_NEAR(a, b) ASSERT_PRED2(&::Vector4Near, a, b)
#define ASSERT_SIZE_NEAR(a, b) ASSERT_PRED2(&::SizeNear, a, b)
+#define ASSERT_ARRAY_4_NEAR(a, b) ASSERT_PRED2(&::Array4Near, a, b)
+#define ASSERT_COLOR_BUFFER_NEAR(a, b) ASSERT_PRED2(&::ColorBufferNear, a, b)
diff --git a/impeller/geometry/gradient.cc b/impeller/geometry/gradient.cc
new file mode 100644
index 0000000..150aa92
--- /dev/null
+++ b/impeller/geometry/gradient.cc
@@ -0,0 +1,98 @@
+// Copyright 2013 The Flutter 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 <algorithm>
+
+#include "impeller/geometry/gradient.h"
+
+namespace impeller {
+
+static void AppendColor(const Color& color, std::vector<uint8_t>* colors) {
+ auto converted = color.Premultiply().ToR8G8B8A8();
+ colors->push_back(converted[0]);
+ colors->push_back(converted[1]);
+ colors->push_back(converted[2]);
+ colors->push_back(converted[3]);
+}
+
+std::vector<uint8_t> CreateGradientBuffer(const std::vector<Color>& colors,
+ const std::vector<Scalar>& stops,
+ uint32_t* out_texture_size) {
+ uint32_t texture_size;
+ // TODO(jonahwilliams): we should add a display list flag to check if the
+ // stops were provided or not, then we can skip this step.
+ // TODO(jonahwilliams): Skia has a check for stop sizes below a certain
+ // threshold, we should make sure that we behave reasonably with them.
+ if (stops.size() == 2) {
+ texture_size = 2;
+ } else {
+ auto minimum_delta = 1.0;
+ for (size_t i = 1; i < stops.size(); i++) {
+ auto value = stops[i] - stops[i - 1];
+ if (value < minimum_delta) {
+ minimum_delta = value;
+ }
+ }
+ // Avoid creating textures that are absurdly large due to stops that are
+ // very close together.
+ // TODO(jonahwilliams): this should use a platform specific max texture
+ // size.
+ texture_size =
+ std::min((uint32_t)std::round(1.0 / minimum_delta) + 1, 1024u);
+ }
+
+ *out_texture_size = texture_size;
+ std::vector<uint8_t> color_stop_channels;
+ color_stop_channels.reserve(texture_size * 4);
+
+ if (texture_size == colors.size() && colors.size() <= 1024) {
+ for (auto i = 0u; i < colors.size(); i++) {
+ AppendColor(colors[i], &color_stop_channels);
+ }
+ } else {
+ Color previous_color = colors[0];
+ auto previous_stop = 0.0;
+ auto previous_color_index = 0;
+
+ // The first index is always equal to the first color, exactly.
+ AppendColor(previous_color, &color_stop_channels);
+
+ for (auto i = 1u; i < texture_size - 1; i++) {
+ auto scaled_i = i / texture_size;
+ Color next_color = colors[previous_color_index + 1];
+ auto next_stop = stops[previous_color_index + 1];
+
+ // We're almost exactly equal to the next stop.
+ if (ScalarNearlyEqual(scaled_i, next_stop)) {
+ AppendColor(next_color, &color_stop_channels);
+
+ previous_color = next_color;
+ previous_stop = next_stop;
+ previous_color_index += 1;
+ } else if (scaled_i < next_stop) {
+ // We're still between the current stop and the next stop.
+ auto t = (scaled_i - previous_stop) / (next_stop - previous_stop);
+ auto mixed_color = Color::lerp(previous_color, next_color, t);
+
+ AppendColor(mixed_color, &color_stop_channels);
+ } else {
+ // We've slightly overshot the next stop. In theory this only happens if
+ // we have scaled our texture such that not every stop gets their own
+ // index. For now I am simply ignoring the inbetween colors. Currently
+ // this requires a gradient with either an absurd number of textures
+ // or very small stops.
+ AppendColor(next_color, &color_stop_channels);
+
+ previous_color = next_color;
+ previous_stop = next_stop;
+ previous_color_index += 1;
+ }
+ }
+ // The last index is always equal to the last color, exactly.
+ AppendColor(colors.back(), &color_stop_channels);
+ }
+ return color_stop_channels;
+}
+
+} // namespace impeller
diff --git a/impeller/geometry/gradient.h b/impeller/geometry/gradient.h
new file mode 100644
index 0000000..2e99e89
--- /dev/null
+++ b/impeller/geometry/gradient.h
@@ -0,0 +1,28 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#pragma once
+
+#include <memory>
+#include <vector>
+
+#include "impeller/geometry/color.h"
+#include "impeller/geometry/path.h"
+#include "impeller/geometry/point.h"
+
+namespace impeller {
+
+/**
+ * @brief Populate a vector with the interpolated colors for the linear gradient
+ * described colors and stops.
+ *
+ * @param colors
+ * @param stops
+ * @return std::vector<u_int8_t>
+ */
+std::vector<uint8_t> CreateGradientBuffer(const std::vector<Color>& colors,
+ const std::vector<Scalar>& stops,
+ uint32_t* out_texture_size);
+
+} // namespace impeller