From 9884f4f9ce1835130dc29bc656b0343541c4e8e8 Mon Sep 17 00:00:00 2001 From: gaaclarke <30870216+gaaclarke@users.noreply.github.com> Date: Thu, 12 Sep 2024 09:30:06 -0700 Subject: [PATCH 1/2] 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: https://github.com/flutter/flutter/issues/127855 [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style --- .../test/to_string_test.dart | 3 +- lib/ui/lerp.dart | 12 --- lib/ui/painting.dart | 96 +++++++++---------- lib/web_ui/lib/lerp.dart | 7 -- lib/web_ui/lib/painting.dart | 92 +++++++++--------- lib/web_ui/test/ui/color_test.dart | 41 ++++++-- lib/web_ui/test/ui/filters_test.dart | 8 +- lib/web_ui/test/ui/paint_test.dart | 2 +- lib/web_ui/test/ui/text_style_test.dart | 10 +- testing/dart/color_test.dart | 27 ++++-- testing/dart/image_filter_test.dart | 2 +- testing/dart/text_test.dart | 2 +- 12 files changed, 164 insertions(+), 138 deletions(-) diff --git a/flutter_frontend_server/test/to_string_test.dart b/flutter_frontend_server/test/to_string_test.dart index ca1f14c90adf9..770f5866ac669 100644 --- a/flutter_frontend_server/test/to_string_test.dart +++ b/flutter_frontend_server/test/to_string_test.dart @@ -60,7 +60,8 @@ void main() { ])); final runResult = io.Process.runSync(dart, [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\'"'; } diff --git a/lib/ui/lerp.dart b/lib/ui/lerp.dart index 5b17fe86756e3..b90e8a7738c51 100644 --- a/lib/ui/lerp.dart +++ b/lib/ui/lerp.dart @@ -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; - } -} diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index f15737cd07198..09ef1c5e8a4c3 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -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. @@ -310,10 +310,11 @@ class Color { /// /// See . double computeLuminance() { + assert(colorSpace != ColorSpace.extendedSRGB); // See - 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; } @@ -339,28 +340,26 @@ class Color { /// /// Values for `t` are usually obtained from an [Animation], 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, ); } } @@ -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, ); } } @@ -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. diff --git a/lib/web_ui/lib/lerp.dart b/lib/web_ui/lib/lerp.dart index ff70c354e308f..80a941f7890e8 100644 --- a/lib/web_ui/lib/lerp.dart +++ b/lib/web_ui/lib/lerp.dart @@ -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; -} diff --git a/lib/web_ui/lib/painting.dart b/lib/web_ui/lib/painting.dart index 113cabadca1e9..7bdd746633328 100644 --- a/lib/web_ui/lib/painting.dart +++ b/lib/web_ui/lib/painting.dart @@ -17,8 +17,8 @@ void _validateColorStops(List colors, List? 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 { @@ -141,32 +141,32 @@ class Color { double computeLuminance() { // See - 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, ); } } @@ -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, ); } } @@ -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 { diff --git a/lib/web_ui/test/ui/color_test.dart b/lib/web_ui/test/ui/color_test.dart index 2ac2df7595d02..6ee06e7f8a734 100644 --- a/lib/web_ui/test/ui/color_test.dart +++ b/lib/web_ui/test/ui/color_test.dart @@ -8,6 +8,33 @@ import 'package:ui/ui.dart'; import '../common/test_initialization.dart'; + +class _ColorMatcher extends Matcher { + _ColorMatcher(this._target, this._threshold); + + final Color _target; + final double _threshold; + + @override + Description describe(Description description) { + return description.add('matches color "$_target" with threshold "$_threshold".'); + } + + @override + bool matches(dynamic item, Map matchState) { + return item is Color && + item.colorSpace == _target.colorSpace && + (item.a - _target.a).abs() <= _threshold && + (item.r - _target.r).abs() <= _threshold && + (item.g - _target.g).abs() <= _threshold && + (item.b - _target.b).abs() <= _threshold; + } +} + +Matcher isSameColorAs(Color color, {double threshold = 0.004}) { + return _ColorMatcher(color, threshold); +} + void main() { internalBootstrapBrowserTest(() => testMain); } @@ -31,7 +58,7 @@ Future testMain() async { const Color c = Color(0x00000000); final Paint p = Paint(); p.color = c; - expect(c.toString(), equals('Color(0x00000000)')); + expect(c.toString(), equals('${const Color(0x00000000)}')); }); test('color created with out of bounds value', () { @@ -63,7 +90,7 @@ Future testMain() async { ); expect( Color.lerp(const Color(0x00000000), const Color(0xFFFFFFFF), 0.5), - const Color(0x7F7F7F7F), + isSameColorAs(const Color(0x7F7F7F7F)), ); expect( Color.lerp(const Color(0x00000000), const Color(0xFFFFFFFF), 1.0), @@ -102,23 +129,23 @@ Future testMain() async { ); expect( Color.alphaBlend(const Color(0x80808080), const Color(0xFFFFFFFF)), - const Color(0xFFBFBFBF), + isSameColorAs(const Color(0xFFBFBFBF)), ); expect( Color.alphaBlend(const Color(0x80808080), const Color(0xFF000000)), - const Color(0xFF404040), + isSameColorAs(const Color(0xFF404040)), ); expect( Color.alphaBlend(const Color(0x01020304), const Color(0xFF000000)), - const Color(0xFF000000), + isSameColorAs(const Color(0xFF000000)), ); expect( Color.alphaBlend(const Color(0x11223344), const Color(0xFF000000)), - const Color(0xFF020304), + isSameColorAs(const Color(0xFF020304)), ); expect( Color.alphaBlend(const Color(0x11223344), const Color(0x80000000)), - const Color(0x88040608), + isSameColorAs(const Color(0x88040608)), ); }); diff --git a/lib/web_ui/test/ui/filters_test.dart b/lib/web_ui/test/ui/filters_test.dart index d2ca97291548e..fc7ce4aa604aa 100644 --- a/lib/web_ui/test/ui/filters_test.dart +++ b/lib/web_ui/test/ui/filters_test.dart @@ -121,22 +121,22 @@ Future testMain() async { test('color filter as image filter', () async { const ui.ColorFilter colorFilter = ui.ColorFilter.mode( - ui.Color.fromRGBO(0, 0, 255, 128), + ui.Color.fromARGB(128, 0, 0, 255), ui.BlendMode.srcOver, ); await drawTestImageWithPaint(ui.Paint()..imageFilter = colorFilter); await matchGoldenFile('ui_filter_colorfilter_as_imagefilter.png', region: region); - expect(colorFilter.toString(), 'ColorFilter.mode(Color(0x800000ff), BlendMode.srcOver)'); + expect(colorFilter.toString(), 'ColorFilter.mode(${const ui.Color(0x800000ff)}, BlendMode.srcOver)'); }); test('mode color filter', () async { const ui.ColorFilter colorFilter = ui.ColorFilter.mode( - ui.Color.fromRGBO(0, 0, 255, 128), + ui.Color.fromARGB(128, 0, 0, 255), ui.BlendMode.srcOver, ); await drawTestImageWithPaint(ui.Paint()..colorFilter = colorFilter); await matchGoldenFile('ui_filter_mode_colorfilter.png', region: region); - expect(colorFilter.toString(), 'ColorFilter.mode(Color(0x800000ff), BlendMode.srcOver)'); + expect(colorFilter.toString(), 'ColorFilter.mode(${const ui.Color(0x800000ff)}, BlendMode.srcOver)'); }); test('linearToSRGBGamma color filter', () async { diff --git a/lib/web_ui/test/ui/paint_test.dart b/lib/web_ui/test/ui/paint_test.dart index 759aa636321d7..253ba3d17d38e 100644 --- a/lib/web_ui/test/ui/paint_test.dart +++ b/lib/web_ui/test/ui/paint_test.dart @@ -69,7 +69,7 @@ Future testMain() async { expect( paint.toString(), 'Paint(' - 'Color(0xaabbccdd); ' + '${const ui.Color(0xaabbccdd)}; ' 'BlendMode.darken; ' 'colorFilter: ColorFilter.linearToSrgbGamma(); ' 'maskFilter: MaskFilter.blur(BlurStyle.normal, 1.7); ' diff --git a/lib/web_ui/test/ui/text_style_test.dart b/lib/web_ui/test/ui/text_style_test.dart index 67eda1dedf007..98b5241946703 100644 --- a/lib/web_ui/test/ui/text_style_test.dart +++ b/lib/web_ui/test/ui/text_style_test.dart @@ -122,9 +122,9 @@ Future testMain() async { expect( style.toString(), 'TextStyle(' - 'color: Color(0xff000000), ' + 'color: ${const ui.Color(0xff000000)}, ' 'decoration: TextDecoration.none, ' - 'decorationColor: Color(0xffaa0000), ' + 'decorationColor: ${const ui.Color(0xffaa0000)}, ' 'decorationStyle: TextDecorationStyle.solid, ' 'decorationThickness: ${1.0}, ' 'fontWeight: FontWeight.w400, ' @@ -140,7 +140,7 @@ Future testMain() async { 'locale: en_US, ' 'background: Paint(), ' 'foreground: unspecified, ' - 'shadows: [TextShadow(Color(0xff000000), Offset(0.0, 0.0), ${0.0})], ' + 'shadows: [TextShadow(${const ui.Color(0xff000000)}, Offset(0.0, 0.0), ${0.0})], ' "fontFeatures: [FontFeature('case', 1)], " "fontVariations: [FontVariation('ital', 0.1)]" ')', @@ -165,7 +165,7 @@ Future testMain() async { 'TextStyle(' 'color: unspecified, ' 'decoration: TextDecoration.none, ' - 'decorationColor: Color(0xffaa0000), ' + 'decorationColor: ${const ui.Color(0xffaa0000)}, ' 'decorationStyle: TextDecorationStyle.solid, ' 'decorationThickness: ${1.0}, ' 'fontWeight: FontWeight.w400, ' @@ -181,7 +181,7 @@ Future testMain() async { 'locale: en_US, ' 'background: Paint(), ' 'foreground: Paint(), ' - 'shadows: [TextShadow(Color(0xff000000), Offset(0.0, 0.0), ${0.0})], ' + 'shadows: [TextShadow(${const ui.Color(0xff000000)}, Offset(0.0, 0.0), ${0.0})], ' "fontFeatures: [FontFeature('case', 1)], " "fontVariations: [FontVariation('ital', 0.1)]" ')', diff --git a/testing/dart/color_test.dart b/testing/dart/color_test.dart index 763339ae00b90..dc9888c66e8b1 100644 --- a/testing/dart/color_test.dart +++ b/testing/dart/color_test.dart @@ -6,6 +6,19 @@ import 'dart:ui'; import 'package:test/test.dart'; +/// Positive result when the Colors will map to the same argb8888 color. +Matcher colorMatches(dynamic o) => (v) { + Expect.isTrue(o is Color); + Expect.isTrue(v is Color); + if (o is Color && v is Color) { + Expect.equals(o.colorSpace, v.colorSpace); + Expect.approxEquals(o.a, v.a, 1 / 255); + Expect.approxEquals(o.r, v.r, 1 / 255); + Expect.approxEquals(o.g, v.g, 1 / 255); + Expect.approxEquals(o.b, v.b, 1 / 255); + } +}; + class NotAColor extends Color { const NotAColor(super.value); } @@ -23,7 +36,7 @@ void main() { const Color c = Color(0x00000000); final Paint p = Paint(); p.color = c; - expect(c.toString(), equals('Color(0x00000000)')); + expect(c, equals(const Color(0x00000000))); }); test('color created with out of bounds value', () { @@ -54,7 +67,7 @@ void main() { ); expect( Color.lerp(const Color(0x00000000), const Color(0xFFFFFFFF), 0.5), - const Color(0x7F7F7F7F), + colorMatches(const Color(0x7F7F7F7F)), ); expect( Color.lerp(const Color(0x00000000), const Color(0xFFFFFFFF), 1.0), @@ -144,23 +157,23 @@ void main() { ); expect( Color.alphaBlend(const Color(0x80808080), const Color(0xFFFFFFFF)), - const Color(0xFFBFBFBF), + colorMatches(const Color(0xFFBFBFBF)), ); expect( Color.alphaBlend(const Color(0x80808080), const Color(0xFF000000)), - const Color(0xFF404040), + colorMatches(const Color(0xFF404040)), ); expect( Color.alphaBlend(const Color(0x01020304), const Color(0xFF000000)), - const Color(0xFF000000), + colorMatches(const Color(0xFF000000)), ); expect( Color.alphaBlend(const Color(0x11223344), const Color(0xFF000000)), - const Color(0xFF020304), + colorMatches(const Color(0xFF020304)), ); expect( Color.alphaBlend(const Color(0x11223344), const Color(0x80000000)), - const Color(0x88040608), + colorMatches(const Color(0x88040608)), ); }); diff --git a/testing/dart/image_filter_test.dart b/testing/dart/image_filter_test.dart index 4ad33c0aa70f1..dab241627158d 100644 --- a/testing/dart/image_filter_test.dart +++ b/testing/dart/image_filter_test.dart @@ -293,7 +293,7 @@ void main() async { ).toString(), contains( 'matrix([10.0, 0.0, 0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, -0.0, -0.0, 0.0, 1.0], FilterQuality.low) -> ' - 'ColorFilter.mode(Color(0xffabcdef), BlendMode.color) -> ' + 'ColorFilter.mode(${const Color(0xFFABCDEF)}, BlendMode.color) -> ' 'blur(20.0, 20.0, repeated) -> ' 'blur(30.0, 30.0, mirror)' ), diff --git a/testing/dart/text_test.dart b/testing/dart/text_test.dart index 108aefb7a53ed..46f176bca0d67 100644 --- a/testing/dart/text_test.dart +++ b/testing/dart/text_test.dart @@ -80,7 +80,7 @@ void testTextStyle() { ); expect( ts1.toString(), - equals('TextStyle(color: Color(0xff00ff00), decoration: unspecified, decorationColor: unspecified, decorationStyle: unspecified, decorationThickness: unspecified, fontWeight: FontWeight.w800, fontStyle: unspecified, textBaseline: unspecified, fontFamily: unspecified, fontFamilyFallback: unspecified, fontSize: 10.0, letterSpacing: unspecified, wordSpacing: unspecified, height: 100.0x, leadingDistribution: unspecified, locale: unspecified, background: unspecified, foreground: unspecified, shadows: unspecified, fontFeatures: unspecified, fontVariations: unspecified)'), + equals('TextStyle(color: ${const Color(0xFF00FF00)}, decoration: unspecified, decorationColor: unspecified, decorationStyle: unspecified, decorationThickness: unspecified, fontWeight: FontWeight.w800, fontStyle: unspecified, textBaseline: unspecified, fontFamily: unspecified, fontFamilyFallback: unspecified, fontSize: 10.0, letterSpacing: unspecified, wordSpacing: unspecified, height: 100.0x, leadingDistribution: unspecified, locale: unspecified, background: unspecified, foreground: unspecified, shadows: unspecified, fontFeatures: unspecified, fontVariations: unspecified)'), ); expect( ts2.toString(), From c0cce1aa7a52ee5c877bad819216d917a3a33924 Mon Sep 17 00:00:00 2001 From: Aaron Clarke Date: Tue, 17 Sep 2024 10:14:37 -0700 Subject: [PATCH 2/2] migrated to flutter:test --- testing/dart/color_test.dart | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/testing/dart/color_test.dart b/testing/dart/color_test.dart index dc9888c66e8b1..2b45535369e42 100644 --- a/testing/dart/color_test.dart +++ b/testing/dart/color_test.dart @@ -6,18 +6,31 @@ import 'dart:ui'; import 'package:test/test.dart'; -/// Positive result when the Colors will map to the same argb8888 color. -Matcher colorMatches(dynamic o) => (v) { - Expect.isTrue(o is Color); - Expect.isTrue(v is Color); - if (o is Color && v is Color) { - Expect.equals(o.colorSpace, v.colorSpace); - Expect.approxEquals(o.a, v.a, 1 / 255); - Expect.approxEquals(o.r, v.r, 1 / 255); - Expect.approxEquals(o.g, v.g, 1 / 255); - Expect.approxEquals(o.b, v.b, 1 / 255); +class _ColorMatcher extends Matcher { + _ColorMatcher(this._target, this._threshold); + + final Color _target; + final double _threshold; + + @override + Description describe(Description description) { + return description.add('matches color "$_target" with threshold "$_threshold".'); } -}; + + @override + bool matches(dynamic item, Map matchState) { + return item is Color && + item.colorSpace == _target.colorSpace && + (item.a - _target.a).abs() <= _threshold && + (item.r - _target.r).abs() <= _threshold && + (item.g - _target.g).abs() <= _threshold && + (item.b - _target.b).abs() <= _threshold; + } +} + +Matcher colorMatches(Color color, {double threshold = 1/255}) { + return _ColorMatcher(color, threshold); +} class NotAColor extends Color { const NotAColor(super.value);