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

Commit cff3880

Browse files
committed
Update Color to do all calculations with floating point components (#54981)
This transforms the rest of Color to use the floating point parameters. This will likely break existing tests very subtly. For example, colors will be slightly different in golden tests if `lerp` was ever used. issue: flutter/flutter#127855 [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
1 parent 1d889dd commit cff3880

File tree

12 files changed

+164
-138
lines changed

12 files changed

+164
-138
lines changed

flutter_frontend_server/test/to_string_test.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ void main() {
6060
]));
6161
final runResult = io.Process.runSync(dart, <String>[regularDill]);
6262
checkProcessResult(runResult);
63-
var paintString = '"Paint.toString":"Paint(Color(0xffffffff))"';
63+
var paintString =
64+
'"Paint.toString":"Paint(Color(alpha: 1.0000, red: 1.0000, green: 1.0000, blue: 1.0000, colorSpace: ColorSpace.sRGB))"';
6465
if (buildDir.contains('release')) {
6566
paintString = '"Paint.toString":"Instance of \'Paint\'"';
6667
}

lib/ui/lerp.dart

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,3 @@ double _lerpDouble(double a, double b, double t) {
3737
double _lerpInt(int a, int b, double t) {
3838
return a + (b - a) * t;
3939
}
40-
41-
/// Same as [num.clamp] but specialized for non-null [int].
42-
int _clampInt(int value, int min, int max) {
43-
assert(min <= max);
44-
if (value < min) {
45-
return min;
46-
} else if (value > max) {
47-
return max;
48-
} else {
49-
return value;
50-
}
51-
}

lib/ui/painting.dart

Lines changed: 48 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ bool _radiusIsValid(Radius radius) {
4848
return true;
4949
}
5050

51-
Color _scaleAlpha(Color a, double factor) {
52-
return a.withAlpha((a.alpha * factor).round().clamp(0, 255));
51+
Color _scaleAlpha(Color x, double factor) {
52+
return x.withValues(alpha: clampDouble(x.a * factor, 0, 1));
5353
}
5454

5555
/// An immutable 32 bit color value in ARGB format.
@@ -310,10 +310,11 @@ class Color {
310310
///
311311
/// See <https://en.wikipedia.org/wiki/Relative_luminance>.
312312
double computeLuminance() {
313+
assert(colorSpace != ColorSpace.extendedSRGB);
313314
// See <https://www.w3.org/TR/WCAG20/#relativeluminancedef>
314-
final double R = _linearizeColorComponent(red / 0xFF);
315-
final double G = _linearizeColorComponent(green / 0xFF);
316-
final double B = _linearizeColorComponent(blue / 0xFF);
315+
final double R = _linearizeColorComponent(r);
316+
final double G = _linearizeColorComponent(g);
317+
final double B = _linearizeColorComponent(b);
317318
return 0.2126 * R + 0.7152 * G + 0.0722 * B;
318319
}
319320

@@ -339,28 +340,26 @@ class Color {
339340
///
340341
/// Values for `t` are usually obtained from an [Animation<double>], such as
341342
/// an [AnimationController].
342-
static Color? lerp(Color? a, Color? b, double t) {
343-
// TODO(gaaclarke): Update math to use floats. This was already attempted
344-
// but it leads to subtle changes that change test results.
345-
assert(a?.colorSpace != ColorSpace.extendedSRGB);
346-
assert(b?.colorSpace != ColorSpace.extendedSRGB);
347-
if (b == null) {
348-
if (a == null) {
343+
static Color? lerp(Color? x, Color? y, double t) {
344+
assert(x?.colorSpace != ColorSpace.extendedSRGB);
345+
assert(y?.colorSpace != ColorSpace.extendedSRGB);
346+
if (y == null) {
347+
if (x == null) {
349348
return null;
350349
} else {
351-
return _scaleAlpha(a, 1.0 - t);
350+
return _scaleAlpha(x, 1.0 - t);
352351
}
353352
} else {
354-
if (a == null) {
355-
return _scaleAlpha(b, t);
353+
if (x == null) {
354+
return _scaleAlpha(y, t);
356355
} else {
357-
assert(a.colorSpace == b.colorSpace);
358-
return Color._fromARGBC(
359-
_clampInt(_lerpInt(a.alpha, b.alpha, t).toInt(), 0, 255),
360-
_clampInt(_lerpInt(a.red, b.red, t).toInt(), 0, 255),
361-
_clampInt(_lerpInt(a.green, b.green, t).toInt(), 0, 255),
362-
_clampInt(_lerpInt(a.blue, b.blue, t).toInt(), 0, 255),
363-
a.colorSpace,
356+
assert(x.colorSpace == y.colorSpace);
357+
return Color.from(
358+
alpha: clampDouble(_lerpDouble(x.a, y.a, t), 0, 1),
359+
red: clampDouble(_lerpDouble(x.r, y.r, t), 0, 1),
360+
green: clampDouble(_lerpDouble(x.g, y.g, t), 0, 1),
361+
blue: clampDouble(_lerpDouble(x.b, y.b, t), 0, 1),
362+
colorSpace: x.colorSpace,
364363
);
365364
}
366365
}
@@ -377,32 +376,30 @@ class Color {
377376
static Color alphaBlend(Color foreground, Color background) {
378377
assert(foreground.colorSpace == background.colorSpace);
379378
assert(foreground.colorSpace != ColorSpace.extendedSRGB);
380-
// TODO(gaaclarke): Update math to use floats. This was already attempted
381-
// but it leads to subtle changes that change test results.
382-
final int alpha = foreground.alpha;
383-
if (alpha == 0x00) { // Foreground completely transparent.
379+
final double alpha = foreground.a;
380+
if (alpha == 0) { // Foreground completely transparent.
384381
return background;
385382
}
386-
final int invAlpha = 0xff - alpha;
387-
int backAlpha = background.alpha;
388-
if (backAlpha == 0xff) { // Opaque background case
389-
return Color._fromARGBC(
390-
0xff,
391-
(alpha * foreground.red + invAlpha * background.red) ~/ 0xff,
392-
(alpha * foreground.green + invAlpha * background.green) ~/ 0xff,
393-
(alpha * foreground.blue + invAlpha * background.blue) ~/ 0xff,
394-
foreground.colorSpace,
383+
final double invAlpha = 1 - alpha;
384+
double backAlpha = background.a;
385+
if (backAlpha == 1) { // Opaque background case
386+
return Color.from(
387+
alpha: 1,
388+
red: alpha * foreground.r + invAlpha * background.r,
389+
green: alpha * foreground.g + invAlpha * background.g,
390+
blue: alpha * foreground.b + invAlpha * background.b,
391+
colorSpace: foreground.colorSpace,
395392
);
396393
} else { // General case
397-
backAlpha = (backAlpha * invAlpha) ~/ 0xff;
398-
final int outAlpha = alpha + backAlpha;
399-
assert(outAlpha != 0x00);
400-
return Color._fromARGBC(
401-
outAlpha,
402-
(foreground.red * alpha + background.red * backAlpha) ~/ outAlpha,
403-
(foreground.green * alpha + background.green * backAlpha) ~/ outAlpha,
404-
(foreground.blue * alpha + background.blue * backAlpha) ~/ outAlpha,
405-
foreground.colorSpace,
394+
backAlpha = backAlpha * invAlpha;
395+
final double outAlpha = alpha + backAlpha;
396+
assert(outAlpha != 0);
397+
return Color.from(
398+
alpha: outAlpha,
399+
red: (foreground.r * alpha + background.r * backAlpha) / outAlpha,
400+
green: (foreground.g * alpha + background.g * backAlpha) / outAlpha,
401+
blue: (foreground.b * alpha + background.b * backAlpha) / outAlpha,
402+
colorSpace: foreground.colorSpace,
406403
);
407404
}
408405
}
@@ -423,16 +420,19 @@ class Color {
423420
return false;
424421
}
425422
return other is Color &&
426-
other.value == value &&
423+
other.a == a &&
424+
other.r == r &&
425+
other.g == g &&
426+
other.b == b &&
427427
other.colorSpace == colorSpace;
428428
}
429429

430430
@override
431-
int get hashCode => Object.hash(value, colorSpace);
431+
int get hashCode => Object.hash(a, r, g, b, colorSpace);
432432

433-
// TODO(gaaclarke): Make toString() print out float values.
434433
@override
435-
String toString() => 'Color(0x${value.toRadixString(16).padLeft(8, '0')})';
434+
String toString() =>
435+
'Color(alpha: ${a.toStringAsFixed(4)}, red: ${r.toStringAsFixed(4)}, green: ${g.toStringAsFixed(4)}, blue: ${b.toStringAsFixed(4)}, colorSpace: $colorSpace)';
436436
}
437437

438438
/// Algorithms to use when painting on the canvas.

lib/web_ui/lib/lerp.dart

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,3 @@ double? lerpDouble(num? a, num? b, double t) {
2828
double _lerpDouble(double a, double b, double t) {
2929
return a * (1.0 - t) + b * t;
3030
}
31-
32-
/// Linearly interpolate between two integers.
33-
///
34-
/// Same as [lerpDouble] but specialized for non-null `int` type.
35-
double _lerpInt(int a, int b, double t) {
36-
return a * (1.0 - t) + b * t;
37-
}

lib/web_ui/lib/painting.dart

Lines changed: 48 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ void _validateColorStops(List<Color> colors, List<double>? colorStops) {
1717
}
1818
}
1919

20-
Color _scaleAlpha(Color a, double factor) {
21-
return a.withAlpha(engine.clampInt((a.alpha * factor).round(), 0, 255));
20+
Color _scaleAlpha(Color x, double factor) {
21+
return x.withValues(alpha: (x.a * factor).clamp(0, 1));
2222
}
2323

2424
class Color {
@@ -141,32 +141,32 @@ class Color {
141141

142142
double computeLuminance() {
143143
// See <https://www.w3.org/TR/WCAG20/#relativeluminancedef>
144-
final double R = _linearizeColorComponent(red / 0xFF);
145-
final double G = _linearizeColorComponent(green / 0xFF);
146-
final double B = _linearizeColorComponent(blue / 0xFF);
144+
final double R = _linearizeColorComponent(r);
145+
final double G = _linearizeColorComponent(g);
146+
final double B = _linearizeColorComponent(b);
147147
return 0.2126 * R + 0.7152 * G + 0.0722 * B;
148148
}
149149

150-
static Color? lerp(Color? a, Color? b, double t) {
151-
assert(a?.colorSpace != ColorSpace.extendedSRGB);
152-
assert(b?.colorSpace != ColorSpace.extendedSRGB);
153-
if (b == null) {
154-
if (a == null) {
150+
static Color? lerp(Color? x, Color? y, double t) {
151+
assert(x?.colorSpace != ColorSpace.extendedSRGB);
152+
assert(y?.colorSpace != ColorSpace.extendedSRGB);
153+
if (y == null) {
154+
if (x == null) {
155155
return null;
156156
} else {
157-
return _scaleAlpha(a, 1.0 - t);
157+
return _scaleAlpha(x, 1.0 - t);
158158
}
159159
} else {
160-
if (a == null) {
161-
return _scaleAlpha(b, t);
160+
if (x == null) {
161+
return _scaleAlpha(y, t);
162162
} else {
163-
assert(a.colorSpace == b.colorSpace);
164-
return Color._fromARGBC(
165-
engine.clampInt(_lerpInt(a.alpha, b.alpha, t).toInt(), 0, 255),
166-
engine.clampInt(_lerpInt(a.red, b.red, t).toInt(), 0, 255),
167-
engine.clampInt(_lerpInt(a.green, b.green, t).toInt(), 0, 255),
168-
engine.clampInt(_lerpInt(a.blue, b.blue, t).toInt(), 0, 255),
169-
a.colorSpace,
163+
assert(x.colorSpace == y.colorSpace);
164+
return Color.from(
165+
alpha: _lerpDouble(x.a, y.a, t).clamp(0, 1),
166+
red: _lerpDouble(x.r, y.r, t).clamp(0, 1),
167+
green: _lerpDouble(x.g, y.g, t).clamp(0, 1),
168+
blue: _lerpDouble(x.b, y.b, t).clamp(0, 1),
169+
colorSpace: x.colorSpace,
170170
);
171171
}
172172
}
@@ -175,30 +175,30 @@ class Color {
175175
static Color alphaBlend(Color foreground, Color background) {
176176
assert(foreground.colorSpace == background.colorSpace);
177177
assert(foreground.colorSpace != ColorSpace.extendedSRGB);
178-
final int alpha = foreground.alpha;
179-
if (alpha == 0x00) {
178+
final double alpha = foreground.a;
179+
if (alpha == 0) { // Foreground completely transparent.
180180
return background;
181181
}
182-
final int invAlpha = 0xff - alpha;
183-
int backAlpha = background.alpha;
184-
if (backAlpha == 0xff) {
185-
return Color._fromARGBC(
186-
0xff,
187-
(alpha * foreground.red + invAlpha * background.red) ~/ 0xff,
188-
(alpha * foreground.green + invAlpha * background.green) ~/ 0xff,
189-
(alpha * foreground.blue + invAlpha * background.blue) ~/ 0xff,
190-
foreground.colorSpace,
182+
final double invAlpha = 1 - alpha;
183+
double backAlpha = background.a;
184+
if (backAlpha == 1) { // Opaque background case
185+
return Color.from(
186+
alpha: 1,
187+
red: alpha * foreground.r + invAlpha * background.r,
188+
green: alpha * foreground.g + invAlpha * background.g,
189+
blue: alpha * foreground.b + invAlpha * background.b,
190+
colorSpace: foreground.colorSpace,
191191
);
192-
} else {
193-
backAlpha = (backAlpha * invAlpha) ~/ 0xff;
194-
final int outAlpha = alpha + backAlpha;
195-
assert(outAlpha != 0x00);
196-
return Color._fromARGBC(
197-
outAlpha,
198-
(foreground.red * alpha + background.red * backAlpha) ~/ outAlpha,
199-
(foreground.green * alpha + background.green * backAlpha) ~/ outAlpha,
200-
(foreground.blue * alpha + background.blue * backAlpha) ~/ outAlpha,
201-
foreground.colorSpace,
192+
} else { // General case
193+
backAlpha = backAlpha * invAlpha;
194+
final double outAlpha = alpha + backAlpha;
195+
assert(outAlpha != 0);
196+
return Color.from(
197+
alpha: outAlpha,
198+
red: (foreground.r * alpha + background.r * backAlpha) / outAlpha,
199+
green: (foreground.g * alpha + background.g * backAlpha) / outAlpha,
200+
blue: (foreground.b * alpha + background.b * backAlpha) / outAlpha,
201+
colorSpace: foreground.colorSpace,
202202
);
203203
}
204204
}
@@ -216,15 +216,19 @@ class Color {
216216
return false;
217217
}
218218
return other is Color &&
219-
other.value == value &&
219+
other.a == a &&
220+
other.r == r &&
221+
other.g == g &&
222+
other.b == b &&
220223
other.colorSpace == colorSpace;
221224
}
222225

223226
@override
224-
int get hashCode => Object.hash(value, colorSpace);
227+
int get hashCode => Object.hash(a, r, g, b, colorSpace);
225228

226229
@override
227-
String toString() => 'Color(0x${value.toRadixString(16).padLeft(8, '0')})';
230+
String toString() =>
231+
'Color(alpha: ${a.toStringAsFixed(4)}, red: ${r.toStringAsFixed(4)}, green: ${g.toStringAsFixed(4)}, blue: ${b.toStringAsFixed(4)}, colorSpace: $colorSpace)';
228232
}
229233

230234
enum StrokeCap {

0 commit comments

Comments
 (0)