| // 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 "flutter/fml/logging.h" | |
| #include "impeller/geometry/gradient.h" | |
| namespace impeller { | |
| static void AppendColor(const Color& color, GradientData* data) { | |
| auto converted = color.ToR8G8B8A8(); | |
| data->color_bytes.push_back(converted[0]); | |
| data->color_bytes.push_back(converted[1]); | |
| data->color_bytes.push_back(converted[2]); | |
| data->color_bytes.push_back(converted[3]); | |
| } | |
| GradientData CreateGradientBuffer(const std::vector<Color>& colors, | |
| const std::vector<Scalar>& stops) { | |
| FML_DCHECK(stops.size() == colors.size()); | |
| uint32_t texture_size; | |
| if (stops.size() == 2) { | |
| texture_size = colors.size(); | |
| } else { | |
| auto minimum_delta = 1.0; | |
| for (size_t i = 1; i < stops.size(); i++) { | |
| auto value = stops[i] - stops[i - 1]; | |
| // Smaller than kEhCloseEnough | |
| if (value < 0.0001) { | |
| continue; | |
| } | |
| 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( | |
| static_cast<uint32_t>(std::round(1.0 / minimum_delta)) + 1, 1024u); | |
| } | |
| GradientData data = { | |
| .color_bytes = {}, | |
| .texture_size = texture_size, | |
| }; | |
| data.color_bytes.reserve(texture_size * 4); | |
| if (texture_size == colors.size() && colors.size() <= 1024) { | |
| for (auto i = 0u; i < colors.size(); i++) { | |
| AppendColor(colors[i], &data); | |
| } | |
| } 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, &data); | |
| for (auto i = 1u; i < texture_size - 1; i++) { | |
| auto scaled_i = i / (texture_size - 1.0); | |
| 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, &data); | |
| 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, &data); | |
| } else { | |
| // We've slightly overshot the previous stop. | |
| previous_color = next_color; | |
| previous_stop = next_stop; | |
| previous_color_index += 1; | |
| next_color = colors[previous_color_index + 1]; | |
| auto next_stop = stops[previous_color_index + 1]; | |
| auto t = (scaled_i - previous_stop) / (next_stop - previous_stop); | |
| auto mixed_color = Color::lerp(previous_color, next_color, t); | |
| AppendColor(mixed_color, &data); | |
| } | |
| } | |
| // The last index is always equal to the last color, exactly. | |
| AppendColor(colors.back(), &data); | |
| } | |
| return data; | |
| } | |
| std::optional<std::vector<Color>> CreateGradientColors( | |
| const std::vector<Color>& colors, | |
| const std::vector<Scalar>& stops) { | |
| FML_DCHECK(stops.size() == colors.size()); | |
| if (stops.size() == 2) { | |
| // Use original buffer. | |
| return std::nullopt; | |
| } | |
| auto minimum_delta = 1.0; | |
| for (size_t i = 1; i < stops.size(); i++) { | |
| auto value = stops[i] - stops[i - 1]; | |
| // Smaller than kEhCloseEnough | |
| if (value < 0.0001) { | |
| continue; | |
| } | |
| if (value < minimum_delta) { | |
| minimum_delta = value; | |
| } | |
| } | |
| // Avoid creating buffers that are absurdly large due to stops that are | |
| // very close together. | |
| uint32_t color_count = std::min( | |
| static_cast<uint32_t>(std::round(1.0 / minimum_delta)) + 1, 1024u); | |
| if (color_count == colors.size()) { | |
| // Use original buffer. | |
| return std::nullopt; | |
| } | |
| std::vector<Color> data; | |
| data.reserve(color_count); | |
| 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. | |
| data.push_back(colors[0]); | |
| for (auto i = 1u; i < color_count - 1; i++) { | |
| auto scaled_i = i / (color_count - 1.0); | |
| 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)) { | |
| data.push_back(next_color); | |
| 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); | |
| data.push_back(mixed_color); | |
| } else { | |
| // We've slightly overshot the previous stop. | |
| previous_color = next_color; | |
| previous_stop = next_stop; | |
| previous_color_index += 1; | |
| next_color = colors[previous_color_index + 1]; | |
| auto next_stop = stops[previous_color_index + 1]; | |
| auto t = (scaled_i - previous_stop) / (next_stop - previous_stop); | |
| auto mixed_color = Color::lerp(previous_color, next_color, t); | |
| data.push_back(mixed_color); | |
| } | |
| } | |
| // The last index is always equal to the last color, exactly. | |
| data.push_back(colors.back()); | |
| return data; | |
| } | |
| } // namespace impeller |