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

Reland: Update Color to do all calculations with floating point components #55231

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion flutter_frontend_server/test/to_string_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ void main() {
]));
final runResult = io.Process.runSync(dart, <String>[regularDill]);
checkProcessResult(runResult);
var paintString = '"Paint.toString":"Paint(Color(0xffffffff))"';
var paintString =
'"Paint.toString":"Paint(Color(alpha: 1.0000, red: 1.0000, green: 1.0000, blue: 1.0000, colorSpace: ColorSpace.sRGB))"';
if (buildDir.contains('release')) {
paintString = '"Paint.toString":"Instance of \'Paint\'"';
}
Expand Down
12 changes: 0 additions & 12 deletions lib/ui/lerp.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,3 @@ double _lerpDouble(double a, double b, double t) {
double _lerpInt(int a, int b, double t) {
return a + (b - a) * t;
}

/// Same as [num.clamp] but specialized for non-null [int].
int _clampInt(int value, int min, int max) {
assert(min <= max);
if (value < min) {
return min;
} else if (value > max) {
return max;
} else {
return value;
}
}
96 changes: 48 additions & 48 deletions lib/ui/painting.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ bool _radiusIsValid(Radius radius) {
return true;
}

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

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

Expand All @@ -339,28 +340,26 @@ class Color {
///
/// Values for `t` are usually obtained from an [Animation<double>], such as
/// an [AnimationController].
static Color? lerp(Color? a, Color? b, double t) {
// TODO(gaaclarke): Update math to use floats. This was already attempted
// but it leads to subtle changes that change test results.
assert(a?.colorSpace != ColorSpace.extendedSRGB);
assert(b?.colorSpace != ColorSpace.extendedSRGB);
if (b == null) {
if (a == null) {
static Color? lerp(Color? x, Color? y, double t) {
assert(x?.colorSpace != ColorSpace.extendedSRGB);
assert(y?.colorSpace != ColorSpace.extendedSRGB);
if (y == null) {
if (x == null) {
return null;
} else {
return _scaleAlpha(a, 1.0 - t);
return _scaleAlpha(x, 1.0 - t);
}
} else {
if (a == null) {
return _scaleAlpha(b, t);
if (x == null) {
return _scaleAlpha(y, t);
} else {
assert(a.colorSpace == b.colorSpace);
return Color._fromARGBC(
_clampInt(_lerpInt(a.alpha, b.alpha, t).toInt(), 0, 255),
_clampInt(_lerpInt(a.red, b.red, t).toInt(), 0, 255),
_clampInt(_lerpInt(a.green, b.green, t).toInt(), 0, 255),
_clampInt(_lerpInt(a.blue, b.blue, t).toInt(), 0, 255),
a.colorSpace,
assert(x.colorSpace == y.colorSpace);
return Color.from(
alpha: clampDouble(_lerpDouble(x.a, y.a, t), 0, 1),
red: clampDouble(_lerpDouble(x.r, y.r, t), 0, 1),
green: clampDouble(_lerpDouble(x.g, y.g, t), 0, 1),
blue: clampDouble(_lerpDouble(x.b, y.b, t), 0, 1),
colorSpace: x.colorSpace,
);
}
}
Expand All @@ -377,32 +376,30 @@ class Color {
static Color alphaBlend(Color foreground, Color background) {
assert(foreground.colorSpace == background.colorSpace);
assert(foreground.colorSpace != ColorSpace.extendedSRGB);
// TODO(gaaclarke): Update math to use floats. This was already attempted
// but it leads to subtle changes that change test results.
final int alpha = foreground.alpha;
if (alpha == 0x00) { // Foreground completely transparent.
final double alpha = foreground.a;
if (alpha == 0) { // Foreground completely transparent.
return background;
}
final int invAlpha = 0xff - alpha;
int backAlpha = background.alpha;
if (backAlpha == 0xff) { // Opaque background case
return Color._fromARGBC(
0xff,
(alpha * foreground.red + invAlpha * background.red) ~/ 0xff,
(alpha * foreground.green + invAlpha * background.green) ~/ 0xff,
(alpha * foreground.blue + invAlpha * background.blue) ~/ 0xff,
foreground.colorSpace,
final double invAlpha = 1 - alpha;
double backAlpha = background.a;
if (backAlpha == 1) { // Opaque background case
return Color.from(
alpha: 1,
red: alpha * foreground.r + invAlpha * background.r,
green: alpha * foreground.g + invAlpha * background.g,
blue: alpha * foreground.b + invAlpha * background.b,
colorSpace: foreground.colorSpace,
);
} else { // General case
backAlpha = (backAlpha * invAlpha) ~/ 0xff;
final int outAlpha = alpha + backAlpha;
assert(outAlpha != 0x00);
return Color._fromARGBC(
outAlpha,
(foreground.red * alpha + background.red * backAlpha) ~/ outAlpha,
(foreground.green * alpha + background.green * backAlpha) ~/ outAlpha,
(foreground.blue * alpha + background.blue * backAlpha) ~/ outAlpha,
foreground.colorSpace,
backAlpha = backAlpha * invAlpha;
final double outAlpha = alpha + backAlpha;
assert(outAlpha != 0);
return Color.from(
alpha: outAlpha,
red: (foreground.r * alpha + background.r * backAlpha) / outAlpha,
green: (foreground.g * alpha + background.g * backAlpha) / outAlpha,
blue: (foreground.b * alpha + background.b * backAlpha) / outAlpha,
colorSpace: foreground.colorSpace,
);
}
}
Expand All @@ -423,16 +420,19 @@ class Color {
return false;
}
return other is Color &&
other.value == value &&
other.a == a &&
other.r == r &&
other.g == g &&
other.b == b &&
other.colorSpace == colorSpace;
}

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

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

/// Algorithms to use when painting on the canvas.
Expand Down
7 changes: 0 additions & 7 deletions lib/web_ui/lib/lerp.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,3 @@ double? lerpDouble(num? a, num? b, double t) {
double _lerpDouble(double a, double b, double t) {
return a * (1.0 - t) + b * t;
}

/// Linearly interpolate between two integers.
///
/// Same as [lerpDouble] but specialized for non-null `int` type.
double _lerpInt(int a, int b, double t) {
return a * (1.0 - t) + b * t;
}
92 changes: 48 additions & 44 deletions lib/web_ui/lib/painting.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ void _validateColorStops(List<Color> colors, List<double>? colorStops) {
}
}

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

class Color {
Expand Down Expand Up @@ -141,32 +141,32 @@ class Color {

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

static Color? lerp(Color? a, Color? b, double t) {
assert(a?.colorSpace != ColorSpace.extendedSRGB);
assert(b?.colorSpace != ColorSpace.extendedSRGB);
if (b == null) {
if (a == null) {
static Color? lerp(Color? x, Color? y, double t) {
assert(x?.colorSpace != ColorSpace.extendedSRGB);
assert(y?.colorSpace != ColorSpace.extendedSRGB);
if (y == null) {
if (x == null) {
return null;
} else {
return _scaleAlpha(a, 1.0 - t);
return _scaleAlpha(x, 1.0 - t);
}
} else {
if (a == null) {
return _scaleAlpha(b, t);
if (x == null) {
return _scaleAlpha(y, t);
} else {
assert(a.colorSpace == b.colorSpace);
return Color._fromARGBC(
engine.clampInt(_lerpInt(a.alpha, b.alpha, t).toInt(), 0, 255),
engine.clampInt(_lerpInt(a.red, b.red, t).toInt(), 0, 255),
engine.clampInt(_lerpInt(a.green, b.green, t).toInt(), 0, 255),
engine.clampInt(_lerpInt(a.blue, b.blue, t).toInt(), 0, 255),
a.colorSpace,
assert(x.colorSpace == y.colorSpace);
return Color.from(
alpha: _lerpDouble(x.a, y.a, t).clamp(0, 1),
red: _lerpDouble(x.r, y.r, t).clamp(0, 1),
green: _lerpDouble(x.g, y.g, t).clamp(0, 1),
blue: _lerpDouble(x.b, y.b, t).clamp(0, 1),
colorSpace: x.colorSpace,
);
}
}
Expand All @@ -175,30 +175,30 @@ class Color {
static Color alphaBlend(Color foreground, Color background) {
assert(foreground.colorSpace == background.colorSpace);
assert(foreground.colorSpace != ColorSpace.extendedSRGB);
final int alpha = foreground.alpha;
if (alpha == 0x00) {
final double alpha = foreground.a;
if (alpha == 0) { // Foreground completely transparent.
return background;
}
final int invAlpha = 0xff - alpha;
int backAlpha = background.alpha;
if (backAlpha == 0xff) {
return Color._fromARGBC(
0xff,
(alpha * foreground.red + invAlpha * background.red) ~/ 0xff,
(alpha * foreground.green + invAlpha * background.green) ~/ 0xff,
(alpha * foreground.blue + invAlpha * background.blue) ~/ 0xff,
foreground.colorSpace,
final double invAlpha = 1 - alpha;
double backAlpha = background.a;
if (backAlpha == 1) { // Opaque background case
return Color.from(
alpha: 1,
red: alpha * foreground.r + invAlpha * background.r,
green: alpha * foreground.g + invAlpha * background.g,
blue: alpha * foreground.b + invAlpha * background.b,
colorSpace: foreground.colorSpace,
);
} else {
backAlpha = (backAlpha * invAlpha) ~/ 0xff;
final int outAlpha = alpha + backAlpha;
assert(outAlpha != 0x00);
return Color._fromARGBC(
outAlpha,
(foreground.red * alpha + background.red * backAlpha) ~/ outAlpha,
(foreground.green * alpha + background.green * backAlpha) ~/ outAlpha,
(foreground.blue * alpha + background.blue * backAlpha) ~/ outAlpha,
foreground.colorSpace,
} else { // General case
backAlpha = backAlpha * invAlpha;
final double outAlpha = alpha + backAlpha;
assert(outAlpha != 0);
return Color.from(
alpha: outAlpha,
red: (foreground.r * alpha + background.r * backAlpha) / outAlpha,
green: (foreground.g * alpha + background.g * backAlpha) / outAlpha,
blue: (foreground.b * alpha + background.b * backAlpha) / outAlpha,
colorSpace: foreground.colorSpace,
);
}
}
Expand All @@ -216,15 +216,19 @@ class Color {
return false;
}
return other is Color &&
other.value == value &&
other.a == a &&
other.r == r &&
other.g == g &&
other.b == b &&
other.colorSpace == colorSpace;
}

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

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

enum StrokeCap {
Expand Down
Loading