diff --git a/lib/gpu/lib/src/render_pass.dart b/lib/gpu/lib/src/render_pass.dart index c2f712c001b6a..0b2cae00b8ba9 100644 --- a/lib/gpu/lib/src/render_pass.dart +++ b/lib/gpu/lib/src/render_pass.dart @@ -95,15 +95,6 @@ base class RenderTarget { final DepthStencilAttachment? depthStencilAttachment; } -// TODO(gaaclarke): Refactor this to support wide gamut colors. -int _colorToInt(ui.Color color) { - assert(color.colorSpace == ui.ColorSpace.sRGB); - return ((color.a * 255.0).round() << 24) | - ((color.r * 255.0).round() << 16) | - ((color.g * 255.0).round() << 8) | - ((color.b * 255.0).round() << 0); -} - base class RenderPass extends NativeFieldWrapperClass1 { /// Creates a new RenderPass. RenderPass._(CommandBuffer commandBuffer, RenderTarget renderTarget) { @@ -114,7 +105,7 @@ base class RenderPass extends NativeFieldWrapperClass1 { index, color.loadAction.index, color.storeAction.index, - _colorToInt(color.clearValue), + color.clearValue.value, color.texture, color.resolveTexture); if (error != null) { diff --git a/lib/ui/lerp.dart b/lib/ui/lerp.dart index e29a8322e298e..eeb0625a83f42 100644 --- a/lib/ui/lerp.dart +++ b/lib/ui/lerp.dart @@ -26,7 +26,7 @@ double? lerpDouble(num? a, num? b, double t) { /// /// Same as [lerpDouble] but specialized for non-null `double` type. double _lerpDouble(double a, double b, double t) { - return a + (b - a) * t; + return a * (1.0 - t) + b * t; } /// Linearly interpolate between two integers. diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 006235c0e5054..cba1b0a33b86f 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -83,7 +83,7 @@ Color _scaleAlpha(Color a, double factor) { /// * [Colors](https://api.flutter.dev/flutter/material/Colors-class.html), which /// defines the colors found in the Material Design specification. class Color { - /// Construct an sRGB color from the lower 32 bits of an [int]. + /// Construct a color from the lower 32 bits of an [int]. /// /// The bits are interpreted as follows: /// @@ -99,32 +99,9 @@ class Color { /// For example, to get a fully opaque orange, you would use `const /// Color(0xFFFF9000)` (`FF` for the alpha, `FF` for the red, `90` for the /// green, and `00` for the blue). - const Color(int value) - : _value = value & 0xFFFFFFFF, - colorSpace = ColorSpace.sRGB, - _a = null, - _r = null, - _g = null, - _b = null; - - /// Construct a color with normalized color components. - /// - /// Normalized color components allows arbitrary bit depths for color - /// components to be be supported. The values will be normalized relative to - /// the [ColorSpace] argument. - const Color.from( - {required double alpha, - required double red, - required double green, - required double blue, - this.colorSpace = ColorSpace.sRGB}) - : _value = 0, - _a = alpha, - _r = red, - _g = green, - _b = blue; - - /// Construct an sRGB color from the lower 8 bits of four integers. + const Color(int value) : value = value & 0xFFFFFFFF; + + /// Construct a color from the lower 8 bits of four integers. /// /// * `a` is the alpha value, with 0 being transparent and 255 being fully /// opaque. @@ -136,32 +113,13 @@ class Color { /// /// See also [fromRGBO], which takes the alpha value as a floating point /// value. - const Color.fromARGB(int a, int r, int g, int b) - : _value = (((a & 0xff) << 24) | - ((r & 0xff) << 16) | - ((g & 0xff) << 8) | - ((b & 0xff) << 0)) & - 0xFFFFFFFF, - colorSpace = ColorSpace.sRGB, - _a = null, - _r = null, - _g = null, - _b = null; - - const Color._fromARGBC( - int alpha, int red, int green, int blue, this.colorSpace) - : _value = (((alpha & 0xff) << 24) | - ((red & 0xff) << 16) | - ((green & 0xff) << 8) | - ((blue & 0xff) << 0)) & - 0xFFFFFFFF, - _a = null, - _r = null, - _g = null, - _b = null; - - /// Create an sRGB color from red, green, blue, and opacity, similar to - /// `rgba()` in CSS. + const Color.fromARGB(int a, int r, int g, int b) : + value = (((a & 0xff) << 24) | + ((r & 0xff) << 16) | + ((g & 0xff) << 8) | + ((b & 0xff) << 0)) & 0xFFFFFFFF; + + /// Create a color from red, green, blue, and opacity, similar to `rgba()` in CSS. /// /// * `r` is [red], from 0 to 255. /// * `g` is [green], from 0 to 255. @@ -172,43 +130,11 @@ class Color { /// Out of range values are brought into range using modulo 255. /// /// See also [fromARGB], which takes the opacity as an integer value. - const Color.fromRGBO(int r, int g, int b, double opacity) - : _value = ((((opacity * 0xff ~/ 1) & 0xff) << 24) | - ((r & 0xff) << 16) | - ((g & 0xff) << 8) | - ((b & 0xff) << 0)) & - 0xFFFFFFFF, - colorSpace = ColorSpace.sRGB, - _a = null, - _r = null, - _g = null, - _b = null; - - /// The alpha channel of this color. - /// - /// A value of 0.0 means this color is fully transparent. A value of 1.0 means - /// this color is fully opaque. - double get a => _a ?? (alpha / 255); - final double? _a; - - /// The red channel of this color. - double get r => _r ?? (red / 255); - final double? _r; - - /// The green channel of this color. - double get g => _g ?? (green / 255); - final double? _g; - - /// The blue channel of this color. - double get b => _b ?? (blue / 255); - final double? _b; - - /// The color space of this color. - final ColorSpace colorSpace; - - static int _floatToInt8(double x) { - return ((x * 255.0).round()) & 0xff; - } + const Color.fromRGBO(int r, int g, int b, double opacity) : + value = ((((opacity * 0xff ~/ 1) & 0xff) << 24) | + ((r & 0xff) << 16) | + ((g & 0xff) << 8) | + ((b & 0xff) << 0)) & 0xFFFFFFFF; /// A 32 bit value representing this color. /// @@ -218,74 +144,29 @@ class Color { /// * Bits 16-23 are the red value. /// * Bits 8-15 are the green value. /// * Bits 0-7 are the blue value. - @Deprecated('Use component accessors like .r or .g.') - int get value { - if (_a != null && _r != null && _g != null && _b != null) { - return _floatToInt8(_a) << 24 | - _floatToInt8(_r) << 16 | - _floatToInt8(_g) << 8 | - _floatToInt8(_b) << 0; - } else { - return _value; - } - } - final int _value; + final int value; /// The alpha channel of this color in an 8 bit value. /// /// A value of 0 means this color is fully transparent. A value of 255 means /// this color is fully opaque. - @Deprecated('Use .a.') int get alpha => (0xff000000 & value) >> 24; /// The alpha channel of this color as a double. /// /// A value of 0.0 means this color is fully transparent. A value of 1.0 means /// this color is fully opaque. - @Deprecated('Use .a.') double get opacity => alpha / 0xFF; /// The red channel of this color in an 8 bit value. - @Deprecated('Use .r.') int get red => (0x00ff0000 & value) >> 16; /// The green channel of this color in an 8 bit value. - @Deprecated('Use .g.') int get green => (0x0000ff00 & value) >> 8; /// The blue channel of this color in an 8 bit value. - @Deprecated('Use .b.') int get blue => (0x000000ff & value) >> 0; - /// Returns a new color that matches this color with the passed in components - /// changed. - /// - /// Changes to color components will be applied before applying changes to the - /// color space. - Color withValues( - {double? alpha, - double? red, - double? green, - double? blue, - ColorSpace? colorSpace}) { - Color? updatedComponents; - if (alpha != null || red != null || green != null || blue != null) { - updatedComponents = Color.from( - alpha: alpha ?? a, - red: red ?? r, - green: green ?? g, - blue: blue ?? b, - colorSpace: this.colorSpace); - } - if (colorSpace != null && colorSpace != this.colorSpace) { - final _ColorTransform transform = - _getColorTransform(this.colorSpace, colorSpace); - return transform.transform(updatedComponents ?? this, colorSpace); - } else { - return updatedComponents ?? this; - } - } - /// Returns a new color that matches this color with the alpha channel /// replaced with `a` (which ranges from 0 to 255). /// @@ -298,7 +179,6 @@ class Color { /// replaced with the given `opacity` (which ranges from 0.0 to 1.0). /// /// Out of range values will have unexpected effects. - @Deprecated('Use .withValues() to avoid precision loss.') Color withOpacity(double opacity) { assert(opacity >= 0.0 && opacity <= 1.0); return withAlpha((255.0 * opacity).round()); @@ -373,10 +253,6 @@ 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) { return null; @@ -387,13 +263,11 @@ class Color { if (a == null) { return _scaleAlpha(b, t); } else { - assert(a.colorSpace == b.colorSpace); - return Color._fromARGBC( + return Color.fromARGB( _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, ); } } @@ -408,10 +282,6 @@ class Color { /// operations for two things that are solid colors with the same shape, but /// overlay each other: instead, just paint one with the combined 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. return background; @@ -419,23 +289,21 @@ class Color { final int invAlpha = 0xff - alpha; int backAlpha = background.alpha; if (backAlpha == 0xff) { // Opaque background case - return Color._fromARGBC( + return Color.fromARGB( 0xff, (alpha * foreground.red + invAlpha * background.red) ~/ 0xff, (alpha * foreground.green + invAlpha * background.green) ~/ 0xff, (alpha * foreground.blue + invAlpha * background.blue) ~/ 0xff, - foreground.colorSpace, ); } else { // General case backAlpha = (backAlpha * invAlpha) ~/ 0xff; final int outAlpha = alpha + backAlpha; assert(outAlpha != 0x00); - return Color._fromARGBC( + return Color.fromARGB( outAlpha, (foreground.red * alpha + background.red * backAlpha) ~/ outAlpha, (foreground.green * alpha + background.green * backAlpha) ~/ outAlpha, (foreground.blue * alpha + background.blue * backAlpha) ~/ outAlpha, - foreground.colorSpace, ); } } @@ -455,15 +323,13 @@ class Color { if (other.runtimeType != runtimeType) { return false; } - return other is Color && - other.value == value && - other.colorSpace == colorSpace; + return other is Color + && other.value == value; } @override - int get hashCode => Object.hash(value, colorSpace); + int get hashCode => value.hashCode; - // TODO(gaaclarke): Make toString() print out float values. @override String toString() => 'Color(0x${value.toRadixString(16).padLeft(8, '0')})'; } @@ -1260,31 +1126,22 @@ final class Paint { @pragma('vm:entry-point') final ByteData _data = ByteData(_kDataByteCount); - // Must match //lib/ui/painting/paint.cc. static const int _kIsAntiAliasIndex = 0; - static const int _kColorRedIndex = 1; - static const int _kColorGreenIndex = 2; - static const int _kColorBlueIndex = 3; - static const int _kColorAlphaIndex = 4; - static const int _kColorSpaceIndex = 5; - static const int _kBlendModeIndex = 6; - static const int _kStyleIndex = 7; - static const int _kStrokeWidthIndex = 8; - static const int _kStrokeCapIndex = 9; - static const int _kStrokeJoinIndex = 10; - static const int _kStrokeMiterLimitIndex = 11; - static const int _kFilterQualityIndex = 12; - static const int _kMaskFilterIndex = 13; - static const int _kMaskFilterBlurStyleIndex = 14; - static const int _kMaskFilterSigmaIndex = 15; - static const int _kInvertColorIndex = 16; + static const int _kColorIndex = 1; + static const int _kBlendModeIndex = 2; + static const int _kStyleIndex = 3; + static const int _kStrokeWidthIndex = 4; + static const int _kStrokeCapIndex = 5; + static const int _kStrokeJoinIndex = 6; + static const int _kStrokeMiterLimitIndex = 7; + static const int _kFilterQualityIndex = 8; + static const int _kMaskFilterIndex = 9; + static const int _kMaskFilterBlurStyleIndex = 10; + static const int _kMaskFilterSigmaIndex = 11; + static const int _kInvertColorIndex = 12; static const int _kIsAntiAliasOffset = _kIsAntiAliasIndex << 2; - static const int _kColorRedOffset = _kColorRedIndex << 2; - static const int _kColorGreenOffset = _kColorGreenIndex << 2; - static const int _kColorBlueOffset = _kColorBlueIndex << 2; - static const int _kColorAlphaOffset = _kColorAlphaIndex << 2; - static const int _kColorSpaceOffset = _kColorSpaceIndex << 2; + static const int _kColorOffset = _kColorIndex << 2; static const int _kBlendModeOffset = _kBlendModeIndex << 2; static const int _kStyleOffset = _kStyleIndex << 2; static const int _kStrokeWidthOffset = _kStrokeWidthIndex << 2; @@ -1298,7 +1155,7 @@ final class Paint { static const int _kInvertColorOffset = _kInvertColorIndex << 2; // If you add more fields, remember to update _kDataByteCount. - static const int _kDataByteCount = 68; // 4 * (last index + 1). + static const int _kDataByteCount = 52; // 4 * (last index + 1). // Binary format must match the deserialization code in paint.cc. // C++ unit tests access this. @@ -1344,28 +1201,12 @@ final class Paint { /// This color is not used when compositing. To colorize a layer, use /// [colorFilter]. Color get color { - final double red = _data.getFloat32(_kColorRedOffset, _kFakeHostEndian); - final double green = _data.getFloat32(_kColorGreenOffset, _kFakeHostEndian); - final double blue = _data.getFloat32(_kColorBlueOffset, _kFakeHostEndian); - final double alpha = - 1.0 - _data.getFloat32(_kColorAlphaOffset, _kFakeHostEndian); - final ColorSpace colorSpace = _indexToColorSpace( - _data.getInt32(_kColorSpaceOffset, _kFakeHostEndian)); - return Color.from( - alpha: alpha, - red: red, - green: green, - blue: blue, - colorSpace: colorSpace); + final int encoded = _data.getInt32(_kColorOffset, _kFakeHostEndian); + return Color(encoded ^ _kColorDefault); } - set color(Color value) { - _data.setFloat32(_kColorRedOffset, value.r, _kFakeHostEndian); - _data.setFloat32(_kColorGreenOffset, value.g, _kFakeHostEndian); - _data.setFloat32(_kColorBlueOffset, value.b, _kFakeHostEndian); - _data.setFloat32(_kColorAlphaOffset, 1.0 - value.a, _kFakeHostEndian); - _data.setInt32(_kColorSpaceOffset, _colorSpaceToIndex(value.colorSpace), - _kFakeHostEndian); + final int encoded = value.value ^ _kColorDefault; + _data.setInt32(_kColorOffset, encoded, _kFakeHostEndian); } // Must be kept in sync with the default in paint.cc. @@ -1739,38 +1580,6 @@ enum ColorSpace { /// see the extended values an [ImageByteFormat] like /// [ImageByteFormat.rawExtendedRgba128] must be used. extendedSRGB, - /// The Display P3 color space. - /// - /// This is a wide gamut color space that has broad hardware support. It's - /// supported in cases like using Impeller on iOS. When used on a platform - /// that doesn't support Display P3, the colors will be clamped to sRGB. - /// - /// See also: https://en.wikipedia.org/wiki/DCI-P3 - displayP3, -} - -int _colorSpaceToIndex(ColorSpace colorSpace) { - switch (colorSpace) { - case ColorSpace.sRGB: - return 0; - case ColorSpace.extendedSRGB: - return 1; - case ColorSpace.displayP3: - return 2; - } -} - -ColorSpace _indexToColorSpace(int index) { - switch(index) { - case 0: - return ColorSpace.sRGB; - case 1: - return ColorSpace.extendedSRGB; - case 2: - return ColorSpace.displayP3; - default: - throw ArgumentError('Unknown color space: $index'); - } } /// The format in which image bytes should be returned when using @@ -3655,121 +3464,6 @@ class MaskFilter { String toString() => 'MaskFilter.blur($_style, ${_sigma.toStringAsFixed(1)})'; } -abstract class _ColorTransform { - Color transform(Color color, ColorSpace resultColorSpace); -} - -class _IdentityColorTransform implements _ColorTransform { - const _IdentityColorTransform(); - @override - Color transform(Color color, ColorSpace resultColorSpace) => color; -} - -class _ClampTransform implements _ColorTransform { - const _ClampTransform(this.child); - final _ColorTransform child; - @override - Color transform(Color color, ColorSpace resultColorSpace) { - return Color.from( - alpha: clampDouble(color.a, 0, 1), - red: clampDouble(color.r, 0, 1), - green: clampDouble(color.g, 0, 1), - blue: clampDouble(color.b, 0, 1), - colorSpace: resultColorSpace); - } -} - -class _MatrixColorTransform implements _ColorTransform { - /// Row-major. - const _MatrixColorTransform(this.values); - - final List values; - - @override - Color transform(Color color, ColorSpace resultColorSpace) { - return Color.from( - alpha: color.a, - red: values[0] * color.r + - values[1] * color.g + - values[2] * color.b + - values[3], - green: values[4] * color.r + - values[5] * color.g + - values[6] * color.b + - values[7], - blue: values[8] * color.r + - values[9] * color.g + - values[10] * color.b + - values[11], - colorSpace: resultColorSpace); - } -} - -_ColorTransform _getColorTransform(ColorSpace source, ColorSpace destination) { - // The transforms were calculated with the following octave script from known - // conversions. These transforms have a white point that matches Apple's. - // - // p3Colors = [ - // 1, 0, 0, 0.25; - // 0, 1, 0, 0.5; - // 0, 0, 1, 0.75; - // 1, 1, 1, 1; - // ]; - // srgbColors = [ - // 1.0930908918380737, -0.5116420984268188, -0.0003518527664709836, 0.12397786229848862; - // -0.22684034705162048, 1.0182716846466064, 0.00027732315356843174, 0.5073589086532593; - // -0.15007957816123962, -0.31062406301498413, 1.0420056581497192, 0.771118700504303; - // 1, 1, 1, 1; - // ]; - // - // format long - // p3ToSrgb = srgbColors * inv(p3Colors) - // srgbToP3 = inv(p3ToSrgb) - const _MatrixColorTransform srgbToP3 = _MatrixColorTransform([ - 0.808052267214446, 0.220292047628890, -0.139648846160100, - 0.145738111193222, // - 0.096480880462996, 0.916386732581291, -0.086093928394828, - 0.089490172325882, // - -0.127099563510240, -0.068983484963878, 0.735426667591299, 0.233655661600230 - ]); - const _ColorTransform p3ToSrgb = _MatrixColorTransform([ - 1.306671048092539, -0.298061942172353, 0.213228303487995, - -0.213580156254466, // - -0.117390025596251, 1.127722006101976, 0.109727644608938, - -0.109450321455370, // - 0.214813187718391, 0.054268702864647, 1.406898424029350, -0.364892765879631 - ]); - switch (source) { - case ColorSpace.sRGB: - switch (destination) { - case ColorSpace.sRGB: - return const _IdentityColorTransform(); - case ColorSpace.extendedSRGB: - return const _IdentityColorTransform(); - case ColorSpace.displayP3: - return srgbToP3; - } - case ColorSpace.extendedSRGB: - switch (destination) { - case ColorSpace.sRGB: - return const _ClampTransform(_IdentityColorTransform()); - case ColorSpace.extendedSRGB: - return const _IdentityColorTransform(); - case ColorSpace.displayP3: - return const _ClampTransform(srgbToP3); - } - case ColorSpace.displayP3: - switch (destination) { - case ColorSpace.sRGB: - return const _ClampTransform(p3ToSrgb); - case ColorSpace.extendedSRGB: - return p3ToSrgb; - case ColorSpace.displayP3: - return const _IdentityColorTransform(); - } - } -} - /// A description of a color filter to apply when drawing a shape or compositing /// a layer with a particular [Paint]. A color filter is a function that takes /// two colors, and outputs one color. When applied during compositing, it is diff --git a/lib/ui/painting/paint.cc b/lib/ui/painting/paint.cc index ab746738bb96c..b13f3bb6b1af7 100644 --- a/lib/ui/painting/paint.cc +++ b/lib/ui/painting/paint.cc @@ -21,25 +21,20 @@ namespace flutter { // Indices for 32bit values. -// Must match //lib/ui/painting.dart. constexpr int kIsAntiAliasIndex = 0; -constexpr int kColorRedIndex = 1; -constexpr int kColorGreenIndex = 2; -constexpr int kColorBlueIndex = 3; -constexpr int kColorAlphaIndex = 4; -constexpr int kColorSpaceIndex = 5; -constexpr int kBlendModeIndex = 6; -constexpr int kStyleIndex = 7; -constexpr int kStrokeWidthIndex = 8; -constexpr int kStrokeCapIndex = 9; -constexpr int kStrokeJoinIndex = 10; -constexpr int kStrokeMiterLimitIndex = 11; -constexpr int kFilterQualityIndex = 12; -constexpr int kMaskFilterIndex = 13; -constexpr int kMaskFilterBlurStyleIndex = 14; -constexpr int kMaskFilterSigmaIndex = 15; -constexpr int kInvertColorIndex = 16; -constexpr size_t kDataByteCount = 68; // 4 * (last index + 1) +constexpr int kColorIndex = 1; +constexpr int kBlendModeIndex = 2; +constexpr int kStyleIndex = 3; +constexpr int kStrokeWidthIndex = 4; +constexpr int kStrokeCapIndex = 5; +constexpr int kStrokeJoinIndex = 6; +constexpr int kStrokeMiterLimitIndex = 7; +constexpr int kFilterQualityIndex = 8; +constexpr int kMaskFilterIndex = 9; +constexpr int kMaskFilterBlurStyleIndex = 10; +constexpr int kMaskFilterSigmaIndex = 11; +constexpr int kInvertColorIndex = 12; +constexpr size_t kDataByteCount = 52; // 4 * (last index + 1) static_assert(kDataByteCount == sizeof(uint32_t) * (kInvertColorIndex + 1), "kDataByteCount must match the size of the data array."); @@ -49,6 +44,9 @@ constexpr int kColorFilterIndex = 1; constexpr int kImageFilterIndex = 2; constexpr int kObjectCount = 3; // One larger than largest object index. +// Must be kept in sync with the default in painting.dart. +constexpr uint32_t kColorDefault = 0xFF000000; + // Must be kept in sync with the default in painting.dart. constexpr uint32_t kBlendModeDefault = static_cast(SkBlendMode::kSrcOver); @@ -60,28 +58,6 @@ constexpr float kStrokeMiterLimitDefault = 4.0f; // Must be kept in sync with the MaskFilter private constants in painting.dart. enum MaskFilterType { kNull, kBlur }; -namespace { -DlColor ReadColor(const tonic::DartByteData& byte_data) { - const uint32_t* uint_data = static_cast(byte_data.data()); - const float* float_data = static_cast(byte_data.data()); - - float red = float_data[kColorRedIndex]; - float green = float_data[kColorGreenIndex]; - float blue = float_data[kColorBlueIndex]; - // Invert alpha so 0 initialized buffer has default value; - float alpha = 1.f - float_data[kColorAlphaIndex]; - uint32_t colorspace = uint_data[kColorSpaceIndex]; - (void)colorspace; - uint32_t encoded_color = - static_cast(std::round(alpha * 255.f)) << 24 | // - static_cast(std::round(red * 255.f)) << 16 | // - static_cast(std::round(green * 255.f)) << 8 | // - static_cast(std::round(blue * 255.f)) << 0; - // TODO(gaaclarke): Pass down color info to DlColor. - return DlColor(encoded_color); -} -} // namespace - Paint::Paint(Dart_Handle paint_objects, Dart_Handle paint_data) : paint_objects_(paint_objects), paint_data_(paint_data) {} @@ -161,7 +137,8 @@ const DlPaint* Paint::paint(DlPaint& paint, } if (flags.applies_alpha_or_color()) { - paint.setColor(ReadColor(byte_data)); + uint32_t encoded_color = uint_data[kColorIndex]; + paint.setColor(DlColor(encoded_color ^ kColorDefault)); } if (flags.applies_blend()) { @@ -261,7 +238,8 @@ void Paint::toDlPaint(DlPaint& paint) const { paint.setAntiAlias(uint_data[kIsAntiAliasIndex] == 0); - paint.setColor(ReadColor(byte_data)); + uint32_t encoded_color = uint_data[kColorIndex]; + paint.setColor(DlColor(encoded_color ^ kColorDefault)); uint32_t encoded_blend_mode = uint_data[kBlendModeIndex]; uint32_t blend_mode = encoded_blend_mode ^ kBlendModeDefault; diff --git a/lib/web_ui/lib/painting.dart b/lib/web_ui/lib/painting.dart index e224a1815422d..6369511173041 100644 --- a/lib/web_ui/lib/painting.dart +++ b/lib/web_ui/lib/painting.dart @@ -22,126 +22,25 @@ Color _scaleAlpha(Color a, double factor) { } class Color { - const Color(int value) - : _value = value & 0xFFFFFFFF, - colorSpace = ColorSpace.sRGB, - _a = null, - _r = null, - _g = null, - _b = null; - - const Color.from( - {required double alpha, - required double red, - required double green, - required double blue, - this.colorSpace = ColorSpace.sRGB}) - : _value = 0, - _a = alpha, - _r = red, - _g = green, - _b = blue; - + const Color(int value) : value = value & 0xFFFFFFFF; const Color.fromARGB(int a, int r, int g, int b) - : _value = (((a & 0xff) << 24) | + : value = (((a & 0xff) << 24) | ((r & 0xff) << 16) | ((g & 0xff) << 8) | ((b & 0xff) << 0)) & - 0xFFFFFFFF, - colorSpace = ColorSpace.sRGB, - _a = null, - _r = null, - _g = null, - _b = null; - - const Color._fromARGBC( - int alpha, int red, int green, int blue, this.colorSpace) - : _value = (((alpha & 0xff) << 24) | - ((red & 0xff) << 16) | - ((green & 0xff) << 8) | - ((blue & 0xff) << 0)) & - 0xFFFFFFFF, - _a = null, - _r = null, - _g = null, - _b = null; - + 0xFFFFFFFF; const Color.fromRGBO(int r, int g, int b, double opacity) - : _value = ((((opacity * 0xff ~/ 1) & 0xff) << 24) | + : value = ((((opacity * 0xff ~/ 1) & 0xff) << 24) | ((r & 0xff) << 16) | ((g & 0xff) << 8) | ((b & 0xff) << 0)) & - 0xFFFFFFFF, - colorSpace = ColorSpace.sRGB, - _a = null, - _r = null, - _g = null, - _b = null; - - double get a => _a ?? (alpha / 255); - final double? _a; - - double get r => _r ?? (red / 255); - final double? _r; - - double get g => _g ?? (green / 255); - final double? _g; - - double get b => _b ?? (blue / 255); - final double? _b; - - final ColorSpace colorSpace; - - static int _floatToInt8(double x) { - return ((x * 255.0).round()) & 0xff; - } - - int get value { - if (_a != null && _r != null && _g != null && _b != null) { - return _floatToInt8(_a) << 24 | - _floatToInt8(_r) << 16 | - _floatToInt8(_g) << 8 | - _floatToInt8(_b) << 0; - } else { - return _value; - } - } - final int _value; - + 0xFFFFFFFF; + final int value; int get alpha => (0xff000000 & value) >> 24; - double get opacity => alpha / 0xFF; - int get red => (0x00ff0000 & value) >> 16; - int get green => (0x0000ff00 & value) >> 8; - int get blue => (0x000000ff & value) >> 0; - - Color withValues( - {double? alpha, - double? red, - double? green, - double? blue, - ColorSpace? colorSpace}) { - Color? updatedComponents; - if (alpha != null || red != null || green != null || blue != null) { - updatedComponents = Color.from( - alpha: alpha ?? a, - red: red ?? r, - green: green ?? g, - blue: blue ?? b, - colorSpace: this.colorSpace); - } - if (colorSpace != null && colorSpace != this.colorSpace) { - final _ColorTransform transform = - _getColorTransform(this.colorSpace, colorSpace); - return transform.transform(updatedComponents ?? this, colorSpace); - } else { - return updatedComponents ?? this; - } - } - Color withAlpha(int a) { return Color.fromARGB(a, red, green, blue); } @@ -180,8 +79,6 @@ class Color { } 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) { return null; @@ -192,45 +89,42 @@ class Color { if (a == null) { return _scaleAlpha(b, t); } else { - assert(a.colorSpace == b.colorSpace); - return Color._fromARGBC( + return Color.fromARGB( 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, ); } } } 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) { + // Foreground completely transparent. return background; } final int invAlpha = 0xff - alpha; int backAlpha = background.alpha; if (backAlpha == 0xff) { - return Color._fromARGBC( + // Opaque background case + return Color.fromARGB( 0xff, (alpha * foreground.red + invAlpha * background.red) ~/ 0xff, (alpha * foreground.green + invAlpha * background.green) ~/ 0xff, (alpha * foreground.blue + invAlpha * background.blue) ~/ 0xff, - foreground.colorSpace, ); } else { + // General case backAlpha = (backAlpha * invAlpha) ~/ 0xff; final int outAlpha = alpha + backAlpha; assert(outAlpha != 0x00); - return Color._fromARGBC( + return Color.fromARGB( outAlpha, (foreground.red * alpha + background.red * backAlpha) ~/ outAlpha, (foreground.green * alpha + background.green * backAlpha) ~/ outAlpha, (foreground.blue * alpha + background.blue * backAlpha) ~/ outAlpha, - foreground.colorSpace, ); } } @@ -247,16 +141,16 @@ class Color { if (other.runtimeType != runtimeType) { return false; } - return other is Color && - other.value == value && - other.colorSpace == colorSpace; + return other is Color && other.value == value; } @override - int get hashCode => Object.hash(value, colorSpace); + int get hashCode => value.hashCode; @override - String toString() => 'Color(0x${value.toRadixString(16).padLeft(8, '0')})'; + String toString() { + return 'Color(0x${value.toRadixString(16).padLeft(8, '0')})'; + } } enum StrokeCap { @@ -520,101 +414,6 @@ class MaskFilter { String toString() => 'MaskFilter.blur($_style, ${_sigma.toStringAsFixed(1)})'; } -abstract class _ColorTransform { - Color transform(Color color, ColorSpace resultColorSpace); -} - -class _IdentityColorTransform implements _ColorTransform { - const _IdentityColorTransform(); - @override - Color transform(Color color, ColorSpace resultColorSpace) => color; -} - -class _ClampTransform implements _ColorTransform { - const _ClampTransform(this.child); - final _ColorTransform child; - @override - Color transform(Color color, ColorSpace resultColorSpace) { - return Color.from( - alpha: clampDouble(color.a, 0, 1), - red: clampDouble(color.r, 0, 1), - green: clampDouble(color.g, 0, 1), - blue: clampDouble(color.b, 0, 1), - colorSpace: resultColorSpace); - } -} - -class _MatrixColorTransform implements _ColorTransform { - const _MatrixColorTransform(this.values); - - final List values; - - @override - Color transform(Color color, ColorSpace resultColorSpace) { - return Color.from( - alpha: color.a, - red: values[0] * color.r + - values[1] * color.g + - values[2] * color.b + - values[3], - green: values[4] * color.r + - values[5] * color.g + - values[6] * color.b + - values[7], - blue: values[8] * color.r + - values[9] * color.g + - values[10] * color.b + - values[11], - colorSpace: resultColorSpace); - } -} - -_ColorTransform _getColorTransform(ColorSpace source, ColorSpace destination) { - const _MatrixColorTransform srgbToP3 = _MatrixColorTransform([ - 0.808052267214446, 0.220292047628890, -0.139648846160100, - 0.145738111193222, // - 0.096480880462996, 0.916386732581291, -0.086093928394828, - 0.089490172325882, // - -0.127099563510240, -0.068983484963878, 0.735426667591299, 0.233655661600230 - ]); - const _ColorTransform p3ToSrgb = _MatrixColorTransform([ - 1.306671048092539, -0.298061942172353, 0.213228303487995, - -0.213580156254466, // - -0.117390025596251, 1.127722006101976, 0.109727644608938, - -0.109450321455370, // - 0.214813187718391, 0.054268702864647, 1.406898424029350, -0.364892765879631 - ]); - switch (source) { - case ColorSpace.sRGB: - switch (destination) { - case ColorSpace.sRGB: - return const _IdentityColorTransform(); - case ColorSpace.extendedSRGB: - return const _IdentityColorTransform(); - case ColorSpace.displayP3: - return srgbToP3; - } - case ColorSpace.extendedSRGB: - switch (destination) { - case ColorSpace.sRGB: - return const _ClampTransform(_IdentityColorTransform()); - case ColorSpace.extendedSRGB: - return const _IdentityColorTransform(); - case ColorSpace.displayP3: - return const _ClampTransform(srgbToP3); - } - case ColorSpace.displayP3: - switch (destination) { - case ColorSpace.sRGB: - return const _ClampTransform(p3ToSrgb); - case ColorSpace.extendedSRGB: - return p3ToSrgb; - case ColorSpace.displayP3: - return const _IdentityColorTransform(); - } - } -} - // This needs to be kept in sync with the "_FilterQuality" enum in skwasm's canvas.cpp enum FilterQuality { none, @@ -654,7 +453,6 @@ class ImageFilter { enum ColorSpace { sRGB, extendedSRGB, - displayP3, } // This must be kept in sync with the `ImageByteFormat` enum in Skwasm's surface.cpp. diff --git a/testing/dart/color_test.dart b/testing/dart/color_test.dart index 56e8766f103b2..5d8f680cb8062 100644 --- a/testing/dart/color_test.dart +++ b/testing/dart/color_test.dart @@ -10,10 +10,6 @@ class NotAColor extends Color { const NotAColor(super.value); } -Matcher approxEquals(dynamic o) => (v) { - Expect.approxEquals(o as num, v as num); -}; - void main() { test('color accessors should work', () { const Color foo = Color(0x12345678); @@ -80,51 +76,6 @@ void main() { ); }); - test('Color.lerp different colorspaces', () { - bool didThrow = false; - try { - Color.lerp( - const Color.from( - alpha: 1, - red: 1, - green: 0, - blue: 0, - colorSpace: ColorSpace.displayP3), - const Color.from( - alpha: 1, red: 1, green: 0, blue: 0), - 0.0); - } catch (ex) { - didThrow = true; - } - expect(didThrow, isTrue); - }); - - test('Color.lerp same colorspaces', () { - expect( - Color.lerp( - const Color.from( - alpha: 1, - red: 0, - green: 0, - blue: 0, - colorSpace: ColorSpace.displayP3), - const Color.from( - alpha: 1, - red: 1, - green: 0, - blue: 0, - colorSpace: ColorSpace.displayP3), - 0.2), - equals( - const Color.from( - alpha: 1, - red: 0.2, - green: 0, - blue: 0, - colorSpace: ColorSpace.displayP3), - )); - }); - test('Color.alphaBlend', () { expect( Color.alphaBlend(const Color(0x00000000), const Color(0x00000000)), @@ -168,29 +119,6 @@ void main() { ); }); - test('Color.alphaBlend keeps colorspace', () { - expect( - Color.alphaBlend( - const Color.from( - alpha: 0.5, - red: 1, - green: 1, - blue: 1, - colorSpace: ColorSpace.displayP3), - const Color.from( - alpha: 1, - red: 0, - green: 0, - blue: 0, - colorSpace: ColorSpace.displayP3)), - const Color.from( - alpha: 1, - red: 0.5, - green: 0.5, - blue: 0.5, - colorSpace: ColorSpace.displayP3)); - }); - test('compute gray luminance', () { // Each color component is at 20%. const Color lightGray = Color(0xFF333333); @@ -206,124 +134,4 @@ void main() { // 0.0722 * ((0.18823529411 + 0.055) / 1.055) ^ 2.4 expect(brightRed.computeLuminance(), equals(0.24601329637099723)); }); - - test('from and accessors', () { - const Color color = Color.from(alpha: 0.1, red: 0.2, green: 0.3, blue: 0.4); - expect(color.a, equals(0.1)); - expect(color.r, equals(0.2)); - expect(color.g, equals(0.3)); - expect(color.b, equals(0.4)); - expect(color.colorSpace, equals(ColorSpace.sRGB)); - - expect(color.alpha, equals(26)); - expect(color.red, equals(51)); - expect(color.green, equals(77)); - expect(color.blue, equals(102)); - - expect(color.value, equals(0x1a334d66)); - }); - - test('fromARGB and accessors', () { - const Color color = Color.fromARGB(10, 20, 35, 47); - expect(color.alpha, equals(10)); - expect(color.red, equals(20)); - expect(color.green, equals(35)); - expect(color.blue, equals(47)); - }); - - test('constructor and accessors', () { - const Color color = Color(0xffeeddcc); - expect(color.alpha, equals(0xff)); - expect(color.red, equals(0xee)); - expect(color.green, equals(0xdd)); - expect(color.blue, equals(0xcc)); - }); - - test('p3 to extended srgb', () { - const Color p3 = Color.from( - alpha: 1, red: 1, green: 0, blue: 0, colorSpace: ColorSpace.displayP3); - final Color srgb = p3.withValues(colorSpace: ColorSpace.extendedSRGB); - expect(srgb.a, equals(1.0)); - expect(srgb.r, approxEquals(1.0931)); - expect(srgb.g, approxEquals(-0.22684034705162098)); - expect(srgb.b, approxEquals(-0.15007957816123998)); - expect(srgb.colorSpace, equals(ColorSpace.extendedSRGB)); - }); - - test('p3 to srgb', () { - const Color p3 = Color.from( - alpha: 1, red: 1, green: 0, blue: 0, colorSpace: ColorSpace.displayP3); - final Color srgb = p3.withValues(colorSpace: ColorSpace.sRGB); - expect(srgb.a, equals(1.0)); - expect(srgb.r, approxEquals(1)); - expect(srgb.g, approxEquals(0)); - expect(srgb.b, approxEquals(0)); - expect(srgb.colorSpace, equals(ColorSpace.sRGB)); - }); - - test('extended srgb to p3', () { - const Color srgb = Color.from( - alpha: 1, - red: 1.0931, - green: -0.2268, - blue: -0.1501, - colorSpace: ColorSpace.extendedSRGB); - final Color p3 = srgb.withValues(colorSpace: ColorSpace.displayP3); - expect(p3.a, equals(1.0)); - expect(p3.r, approxEquals(1)); - expect(p3.g, approxEquals(0)); - expect(p3.b, approxEquals(0)); - expect(p3.colorSpace, equals(ColorSpace.displayP3)); - }); - - test('extended srgb to p3 clamped', () { - const Color srgb = Color.from( - alpha: 1, - red: 2, - green: 0, - blue: 0, - colorSpace: ColorSpace.extendedSRGB); - final Color p3 = srgb.withValues(colorSpace: ColorSpace.displayP3); - expect(srgb.a, equals(1.0)); - expect(p3.r <= 1.0, isTrue); - expect(p3.g <= 1.0, isTrue); - expect(p3.b <= 1.0, isTrue); - expect(p3.r >= 0.0, isTrue); - expect(p3.g >= 0.0, isTrue); - expect(p3.b >= 0.0, isTrue); - }); - - test('hash considers colorspace', () { - const Color srgb = Color.from( - alpha: 1, red: 1, green: 0, blue: 0); - const Color p3 = Color.from( - alpha: 1, red: 1, green: 0, blue: 0, colorSpace: ColorSpace.displayP3); - expect(srgb.hashCode, notEquals(p3.hashCode)); - }); - - test('equality considers colorspace', () { - const Color srgb = Color.from( - alpha: 1, red: 1, green: 0, blue: 0); - const Color p3 = Color.from( - alpha: 1, red: 1, green: 0, blue: 0, colorSpace: ColorSpace.displayP3); - expect(srgb, notEquals(p3)); - }); - - // Regression test for https://github.com/flutter/flutter/issues/41257 - // CupertinoDynamicColor was overriding base class and calling super(0). - test('subclass of Color can override value', () { - const DynamicColorClass color = DynamicColorClass(0xF0E0D0C0); - expect(color.value, 0xF0E0D0C0); - // Call base class member, make sure it uses overridden value. - expect(color.red, 0xE0); - }); -} - -class DynamicColorClass extends Color { - const DynamicColorClass(int newValue) : _newValue = newValue, super(0); - - final int _newValue; - - @override - int get value => _newValue; }