Skip to content
This repository has been archived by the owner on Feb 25, 2025. It is now read-only.

Commit

Permalink
Framework wide color linear gradients (#54748)
Browse files Browse the repository at this point in the history
This implements wide gamut color support for gradients in the engine.

issue: flutter/flutter#127855
integration test: flutter/flutter#153976

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
  • Loading branch information
gaaclarke authored Sep 4, 2024
1 parent 9838d3c commit 7418367
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 45 deletions.
3 changes: 2 additions & 1 deletion impeller/display_list/skia_conversions.cc
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,8 @@ std::optional<impeller::PixelFormat> ToPixelFormat(SkColorType type) {
void ConvertStops(const flutter::DlGradientColorSourceBase* gradient,
std::vector<Color>& colors,
std::vector<float>& stops) {
FML_DCHECK(gradient->stop_count() >= 2);
FML_DCHECK(gradient->stop_count() >= 2)
<< "stop_count:" << gradient->stop_count();

auto* dl_colors = gradient->colors();
auto* dl_stops = gradient->stops();
Expand Down
35 changes: 25 additions & 10 deletions lib/ui/painting.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4452,10 +4452,25 @@ enum TileMode {
decal,
}

Float32List _encodeWideColorList(List<Color> colors) {
final int colorCount = colors.length;
final Float32List result = Float32List(colorCount * 4);
for (int i = 0; i < colorCount; i++) {
final Color colorXr =
colors[i].withValues(colorSpace: ColorSpace.extendedSRGB);
result[i*4+0] = colorXr.a;
result[i*4+1] = colorXr.r;
result[i*4+2] = colorXr.g;
result[i*4+3] = colorXr.b;
}
return result;
}


Int32List _encodeColorList(List<Color> colors) {
final int colorCount = colors.length;
final Int32List result = Int32List(colorCount);
for (int i = 0; i < colorCount; ++i) {
for (int i = 0; i < colorCount; i++) {
result[i] = colors[i].value;
}
return result;
Expand All @@ -4464,7 +4479,7 @@ Int32List _encodeColorList(List<Color> colors) {
Float32List _encodePointList(List<Offset> points) {
final int pointCount = points.length;
final Float32List result = Float32List(pointCount * 2);
for (int i = 0; i < pointCount; ++i) {
for (int i = 0; i < pointCount; i++) {
final int xIndex = i * 2;
final int yIndex = xIndex + 1;
final Offset point = points[i];
Expand Down Expand Up @@ -4535,7 +4550,7 @@ base class Gradient extends Shader {
super._() {
_validateColorStops(colors, colorStops);
final Float32List endPointsBuffer = _encodeTwoPoints(from, to);
final Int32List colorsBuffer = _encodeColorList(colors);
final Float32List colorsBuffer = _encodeWideColorList(colors);
final Float32List? colorStopsBuffer = colorStops == null ? null : Float32List.fromList(colorStops);
_constructor();
_initLinear(endPointsBuffer, colorsBuffer, colorStopsBuffer, tileMode.index, matrix4);
Expand Down Expand Up @@ -4588,8 +4603,8 @@ base class Gradient extends Shader {
assert(matrix4 == null || _matrix4IsValid(matrix4)),
super._() {
_validateColorStops(colors, colorStops);
final Int32List colorsBuffer = _encodeColorList(colors);
final Float32List? colorStopsBuffer = colorStops == null ? null : Float32List.fromList(colorStops);
final Float32List colorsBuffer = _encodeWideColorList(colors);

// If focal is null or focal radius is null, this should be treated as a regular radial gradient
// If focal == center and the focal radius is 0.0, it's still a regular radial gradient
Expand Down Expand Up @@ -4647,7 +4662,7 @@ base class Gradient extends Shader {
assert(matrix4 == null || _matrix4IsValid(matrix4)),
super._() {
_validateColorStops(colors, colorStops);
final Int32List colorsBuffer = _encodeColorList(colors);
final Float32List colorsBuffer = _encodeWideColorList(colors);
final Float32List? colorStopsBuffer = colorStops == null ? null : Float32List.fromList(colorStops);
_constructor();
_initSweep(center.dx, center.dy, colorsBuffer, colorStopsBuffer, tileMode.index, startAngle, endAngle, matrix4);
Expand All @@ -4657,14 +4672,14 @@ base class Gradient extends Shader {
external void _constructor();

@Native<Void Function(Pointer<Void>, Handle, Handle, Handle, Int32, Handle)>(symbol: 'Gradient::initLinear')
external void _initLinear(Float32List endPoints, Int32List colors, Float32List? colorStops, int tileMode, Float64List? matrix4);
external void _initLinear(Float32List endPoints, Float32List colors, Float32List? colorStops, int tileMode, Float64List? matrix4);

@Native<Void Function(Pointer<Void>, Double, Double, Double, Handle, Handle, Int32, Handle)>(symbol: 'Gradient::initRadial')
external void _initRadial(
double centerX,
double centerY,
double radius,
Int32List colors,
Float32List colors,
Float32List? colorStops,
int tileMode,
Float64List? matrix4);
Expand All @@ -4677,7 +4692,7 @@ base class Gradient extends Shader {
double endX,
double endY,
double endRadius,
Int32List colors,
Float32List colors,
Float32List? colorStops,
int tileMode,
Float64List? matrix4);
Expand All @@ -4686,7 +4701,7 @@ base class Gradient extends Shader {
external void _initSweep(
double centerX,
double centerY,
Int32List colors,
Float32List colors,
Float32List? colorStops,
int tileMode,
double startAngle,
Expand Down Expand Up @@ -6503,7 +6518,7 @@ base class _NativeCanvas extends NativeFieldWrapperClass1 implements Canvas {
final Float32List rstTransformBuffer = Float32List(rectCount * 4);
final Float32List rectBuffer = Float32List(rectCount * 4);

for (int i = 0; i < rectCount; ++i) {
for (int i = 0; i < rectCount; i++) {
final int index0 = i * 4;
final int index1 = index0 + 1;
final int index2 = index0 + 2;
Expand Down
79 changes: 49 additions & 30 deletions lib/ui/painting/gradient.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@ void CanvasGradient::Create(Dart_Handle wrapper) {
}

void CanvasGradient::initLinear(const tonic::Float32List& end_points,
const tonic::Int32List& colors,
const tonic::Float32List& colors,
const tonic::Float32List& color_stops,
DlTileMode tile_mode,
const tonic::Float64List& matrix4) {
FML_DCHECK(end_points.num_elements() == 4);
FML_DCHECK(colors.num_elements() == color_stops.num_elements() ||
FML_DCHECK(colors.num_elements() == (color_stops.num_elements() * 4) ||
color_stops.data() == nullptr);
int num_colors = colors.num_elements() / 4;

static_assert(sizeof(SkPoint) == sizeof(float) * 2,
"SkPoint doesn't use floats.");
Expand All @@ -46,28 +47,32 @@ void CanvasGradient::initLinear(const tonic::Float32List& end_points,
SkPoint p0 = SkPoint::Make(end_points[0], end_points[1]);
SkPoint p1 = SkPoint::Make(end_points[2], end_points[3]);
std::vector<DlColor> dl_colors;
dl_colors.reserve(colors.num_elements());
for (int i = 0; i < colors.num_elements(); ++i) {
/// TODO(gaaclarke): Make this preserve wide gamut colors.
dl_colors.emplace_back(DlColor(colors[i]));
dl_colors.reserve(num_colors);
for (int i = 0; i < colors.num_elements(); i += 4) {
DlScalar a = colors[i + 0];
DlScalar r = colors[i + 1];
DlScalar g = colors[i + 2];
DlScalar b = colors[i + 3];
dl_colors.emplace_back(DlColor(a, r, g, b, DlColorSpace::kExtendedSRGB));
}

dl_shader_ = DlColorSource::MakeLinear(
p0, p1, colors.num_elements(), dl_colors.data(), color_stops.data(),
tile_mode, has_matrix ? &sk_matrix : nullptr);
dl_shader_ = DlColorSource::MakeLinear(p0, p1, num_colors, dl_colors.data(),
color_stops.data(), tile_mode,
has_matrix ? &sk_matrix : nullptr);
// Just a sanity check, all gradient shaders should be thread-safe
FML_DCHECK(dl_shader_->isUIThreadSafe());
}

void CanvasGradient::initRadial(double center_x,
double center_y,
double radius,
const tonic::Int32List& colors,
const tonic::Float32List& colors,
const tonic::Float32List& color_stops,
DlTileMode tile_mode,
const tonic::Float64List& matrix4) {
FML_DCHECK(colors.num_elements() == color_stops.num_elements() ||
FML_DCHECK(colors.num_elements() == (color_stops.num_elements() * 4) ||
color_stops.data() == nullptr);
int num_colors = colors.num_elements() / 4;

static_assert(sizeof(SkColor) == sizeof(int32_t),
"SkColor doesn't use int32_t.");
Expand All @@ -79,29 +84,34 @@ void CanvasGradient::initRadial(double center_x,
}

std::vector<DlColor> dl_colors;
dl_colors.reserve(colors.num_elements());
for (int i = 0; i < colors.num_elements(); ++i) {
dl_colors.emplace_back(DlColor(colors[i]));
dl_colors.reserve(num_colors);
for (int i = 0; i < colors.num_elements(); i += 4) {
DlScalar a = colors[i + 0];
DlScalar r = colors[i + 1];
DlScalar g = colors[i + 2];
DlScalar b = colors[i + 3];
dl_colors.emplace_back(DlColor(a, r, g, b, DlColorSpace::kExtendedSRGB));
}

dl_shader_ = DlColorSource::MakeRadial(
SkPoint::Make(SafeNarrow(center_x), SafeNarrow(center_y)),
SafeNarrow(radius), colors.num_elements(), dl_colors.data(),
color_stops.data(), tile_mode, has_matrix ? &sk_matrix : nullptr);
SafeNarrow(radius), num_colors, dl_colors.data(), color_stops.data(),
tile_mode, has_matrix ? &sk_matrix : nullptr);
// Just a sanity check, all gradient shaders should be thread-safe
FML_DCHECK(dl_shader_->isUIThreadSafe());
}

void CanvasGradient::initSweep(double center_x,
double center_y,
const tonic::Int32List& colors,
const tonic::Float32List& colors,
const tonic::Float32List& color_stops,
DlTileMode tile_mode,
double start_angle,
double end_angle,
const tonic::Float64List& matrix4) {
FML_DCHECK(colors.num_elements() == color_stops.num_elements() ||
FML_DCHECK(colors.num_elements() == (color_stops.num_elements() * 4) ||
color_stops.data() == nullptr);
int num_colors = colors.num_elements() / 4;

static_assert(sizeof(SkColor) == sizeof(int32_t),
"SkColor doesn't use int32_t.");
Expand All @@ -113,16 +123,20 @@ void CanvasGradient::initSweep(double center_x,
}

std::vector<DlColor> dl_colors;
dl_colors.reserve(colors.num_elements());
for (int i = 0; i < colors.num_elements(); ++i) {
dl_colors.emplace_back(DlColor(colors[i]));
dl_colors.reserve(num_colors);
for (int i = 0; i < colors.num_elements(); i += 4) {
DlScalar a = colors[i + 0];
DlScalar r = colors[i + 1];
DlScalar g = colors[i + 2];
DlScalar b = colors[i + 3];
dl_colors.emplace_back(DlColor(a, r, g, b, DlColorSpace::kExtendedSRGB));
}

dl_shader_ = DlColorSource::MakeSweep(
SkPoint::Make(SafeNarrow(center_x), SafeNarrow(center_y)),
SafeNarrow(start_angle) * 180.0f / static_cast<float>(M_PI),
SafeNarrow(end_angle) * 180.0f / static_cast<float>(M_PI),
colors.num_elements(), dl_colors.data(), color_stops.data(), tile_mode,
SafeNarrow(end_angle) * 180.0f / static_cast<float>(M_PI), num_colors,
dl_colors.data(), color_stops.data(), tile_mode,
has_matrix ? &sk_matrix : nullptr);
// Just a sanity check, all gradient shaders should be thread-safe
FML_DCHECK(dl_shader_->isUIThreadSafe());
Expand All @@ -134,12 +148,13 @@ void CanvasGradient::initTwoPointConical(double start_x,
double end_x,
double end_y,
double end_radius,
const tonic::Int32List& colors,
const tonic::Float32List& colors,
const tonic::Float32List& color_stops,
DlTileMode tile_mode,
const tonic::Float64List& matrix4) {
FML_DCHECK(colors.num_elements() == color_stops.num_elements() ||
FML_DCHECK(colors.num_elements() == (color_stops.num_elements() * 4) ||
color_stops.data() == nullptr);
int num_colors = colors.num_elements() / 4;

static_assert(sizeof(SkColor) == sizeof(int32_t),
"SkColor doesn't use int32_t.");
Expand All @@ -151,17 +166,21 @@ void CanvasGradient::initTwoPointConical(double start_x,
}

std::vector<DlColor> dl_colors;
dl_colors.reserve(colors.num_elements());
for (int i = 0; i < colors.num_elements(); ++i) {
dl_colors.emplace_back(DlColor(colors[i]));
dl_colors.reserve(num_colors);
for (int i = 0; i < colors.num_elements(); i += 4) {
DlScalar a = colors[i + 0];
DlScalar r = colors[i + 1];
DlScalar g = colors[i + 2];
DlScalar b = colors[i + 3];
dl_colors.emplace_back(DlColor(a, r, g, b, DlColorSpace::kExtendedSRGB));
}

dl_shader_ = DlColorSource::MakeConical(
SkPoint::Make(SafeNarrow(start_x), SafeNarrow(start_y)),
SafeNarrow(start_radius),
SkPoint::Make(SafeNarrow(end_x), SafeNarrow(end_y)),
SafeNarrow(end_radius), colors.num_elements(), dl_colors.data(),
color_stops.data(), tile_mode, has_matrix ? &sk_matrix : nullptr);
SafeNarrow(end_radius), num_colors, dl_colors.data(), color_stops.data(),
tile_mode, has_matrix ? &sk_matrix : nullptr);
// Just a sanity check, all gradient shaders should be thread-safe
FML_DCHECK(dl_shader_->isUIThreadSafe());
}
Expand Down
8 changes: 4 additions & 4 deletions lib/ui/painting/gradient.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,22 @@ class CanvasGradient : public Shader {
static void Create(Dart_Handle wrapper);

void initLinear(const tonic::Float32List& end_points,
const tonic::Int32List& colors,
const tonic::Float32List& colors,
const tonic::Float32List& color_stops,
DlTileMode tile_mode,
const tonic::Float64List& matrix4);

void initRadial(double center_x,
double center_y,
double radius,
const tonic::Int32List& colors,
const tonic::Float32List& colors,
const tonic::Float32List& color_stops,
DlTileMode tile_mode,
const tonic::Float64List& matrix4);

void initSweep(double center_x,
double center_y,
const tonic::Int32List& colors,
const tonic::Float32List& colors,
const tonic::Float32List& color_stops,
DlTileMode tile_mode,
double start_angle,
Expand All @@ -49,7 +49,7 @@ class CanvasGradient : public Shader {
double end_x,
double end_y,
double end_radius,
const tonic::Int32List& colors,
const tonic::Float32List& colors,
const tonic::Float32List& color_stops,
DlTileMode tile_mode,
const tonic::Float64List& matrix4);
Expand Down

0 comments on commit 7418367

Please sign in to comment.