概述

#

dart:ui 中的 Color 类 API 正在更改,以支持宽色域色彩空间

背景

#

Flutter 引擎已通过 Impeller 支持宽色域色彩,现在正将此支持添加到框架中

Flutter 支持的 iOS 设备渲染到更广泛的色彩数组,特别是在 DisplayP3 色彩空间中。此更改后,Flutter 框架可以在 iOS Impeller 上渲染所有这些颜色,并且 Color 类为未来的色彩空间或颜色分量位深度的变化做好了更好的准备。

变更说明

#

Color 的更改

  1. 添加了一个枚举字段,用于指定其 ColorSpace
  2. 添加了使用标准化浮点颜色分量的 API。
  3. 移除了使用8位无符号整数颜色分量并可能导致数据丢失的 API。

ColorSpace 的更改

  1. 添加了 displayP3 属性。

迁移指南

#

8位无符号整数构造函数

#

诸如 Color.fromARGB 之类的构造函数保持不变并继续受支持。要利用 Display P3 颜色,您必须使用新的 Color.from 构造函数,该构造函数接受标准化的浮点颜色分量。

dart
// Before: Constructing an sRGB color from the lower 8 bits of four integers.
final magenta = Color.fromARGB(0xff, 0xff, 0x0, 0xff);

// After: Constructing a color with normalized floating-point components.
final magenta = Color.from(alpha: 1.0, red: 1.0, green: 0.0, blue: 1.0);

Color 的实现者

#

Color 中正在添加新方法,因此任何 implements Color 的类都将中断,并且必须实现新方法,例如 Color.aColor.b

最终,实现者应该迁移以利用新 API。短期内,这些方法可以轻松实现,而无需更改类的底层结构。

例如

dart
class Foo implements Color {
  int _red;

  @override
  double get r => _red / 255.0;
}

色彩空间支持

#

使用 Color 并对颜色分量执行任何计算的客户端现在应在执行计算之前首先检查色彩空间分量。为此,您可以使用新的 Color.withValues 方法来执行色彩空间转换。

迁移示例

dart
// Before
double redRatio(Color x, Color y) => x.red / y.red;

// After
double redRatio(Color x, Color y) {
  final xPrime = x.withValues(colorSpace: ColorSpace.extendedSRGB);
  final yPrime = y.withValues(colorSpace: ColorSpace.extendedSRGB);
  return xPrime.r / yPrime.r;
}

在不调整色彩空间的情况下对颜色分量执行计算可能会导致细微的意外结果。在前面的示例中,当使用不同的色彩空间与对齐的色彩空间计算时,redRatio 会有 0.09 的差异。

访问颜色分量

#

如果您的应用程序曾访问 Color 分量,请考虑利用浮点分量。短期内,您可以自行缩放这些分量。

dart
extension IntColorComponents on Color {
  int get intAlpha => _floatToInt8(this.a);
  int get intRed => _floatToInt8(this.r);
  int get intGreen => _floatToInt8(this.g);
  int get intBlue => _floatToInt8(this.b);

  int _floatToInt8(double x) {
    return (x * 255.0).round() & 0xff;
  }
}

不透明度

#

在 Flutter 3.27 之前,Color 有“不透明度”的概念,表现为 opacitywithOpacity() 方法。不透明度被引入作为一种方式,通过浮点值 ([0.0, 1.0]) 与 Color 的 alpha 通道进行通信。不透明度方法是设置 8 位 alpha 值 ([0, 255]) 的便捷方法,但从未提供浮点数的完整表达。当颜色分量存储为 8 位整数时,这已足够。

自 Flutter 3.27 起,alpha 以浮点值存储。使用 .a.withValues() 将提供浮点值的完整表达,并且不会被量化(限制在有限范围内)。这意味着“alpha”更正确地表达了“不透明度”的意图。不透明度在微妙之处有所不同,其使用可能导致意外的数据丢失,因此 .withOpacity().opacity 已被废弃,但其语义已得到维护,以避免破坏任何现有功能。

例如

dart
// Prints 0.5019607843137255.
print(Colors.black.withOpacity(0.5).a);
// Prints 0.5.
print(Colors.black.withValues(alpha: 0.5).a);

几乎所有用法都将直接受益于更准确的颜色。在极少数情况下,如果不是这样,可以注意使用 .alpha.withAlpha() 将不透明度量化到 [0, 255],以匹配 Flutter 3.27 之前的行为。

迁移 opacity

#
dart
// Before: Access the alpha channel as a (converted) floating-point value.
final x = color.opacity;

// After: Access the alpha channel directly.
final x = color.a;

迁移 withOpacity

#
dart
// Before: Create a new color with the specified opacity.
final x = color.withOpacity(0.0);

// After: Create a new color with the specified alpha channel value,
// accounting for the current or specified color space.
final x = color.withValues(alpha: 0.0);

相等性

#

一旦 Color 将其颜色分量存储为浮点数,相等性将略有不同。在计算颜色时,可能存在微小的值差异,这可能被认为是相等的。为了适应这一点,请使用 closeToisColorSameAs 匹配器。

dart
// Before: Check exact equality of int-based color.
expect(calculateColor(), const Color(0xffff00ff));

// After: Check rough equality of floating-point-based color.
expect(calculateColor(), isSameColorAs(const Color(0xffff00ff)));

时间线

#

阶段 1 - 新 API 引入,旧 API 废弃

#

发布版本:3.26.0-0.1.pre
稳定版本:3.27.0

阶段 2 - 旧 API 移除

#

生效版本:尚未发布
稳定版本:尚未发布

参考资料

#

相关议题

相关 PR