[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