画布上颜色叠在一起总是偏灰偏暗——不是你的算法有 bug,是画布默认的色彩空间在跟你作对


画布上颜色叠在一起总是偏灰偏暗——不是你的算法有 bug,是画布默认的色彩空间在跟你作对。

Web 的色彩管理历史上长期依赖非线性 sRGB 色彩空间,canvas.getContext('2d') 默认创建的 2D 渲染上下文也不例外。这个空间的问题在于,它的数值和实际感知亮度之间不是线性关系——同样的数值变化,在暗部产生的视觉差异比亮部更大。这在做颜色混合、渐变、Alpha 合成时会导致系统性色偏:比如一条从红到蓝的渐变,在 sRGB 空间里计算会显得中间段偏暗偏灰,而不是你期望的平滑过渡。

Chrome 155 给 Canvas 2D 引入了标准预设色彩空间支持,现在可以这样创建画布:

// 线性 sRGB 画布,颜色运算更准确
const ctx = canvas.getContext('2d', { colorSpace: 'srgb-linear' });

// 线性 Display P3 画布,支持广色域
const ctx2 = canvas.getContext('2d', { colorSpace: 'display-p3-linear' });

srgb-linear 把画布的运算空间切到线性 sRGB——先把 sRGB 值还原成线性光强度,做完所有混合/渐变/滤镜运算之后,最后再统一做一次 gamma 校正输出。在物理上,这比直接在非线性空间做运算要正确得多。

display-p3-linear 进一步换到 Display P3 广色域空间。Display P3 能呈现比 sRGB 大约 50% 的红色和绿色范围,是目前广色域显示器的主流标准(iPhone 的 P3 屏幕就是典型例子)。在 Linear P3 空间里做颜色运算,既能享受广色域的丰富色彩,又能保证混合的数学正确性。

这套机制基于 CSS Color Level 4 规范的 PredefinedColorSpace 接口,Chrome 155 将 srgb-linear 和 display-p3-linear 两个标准色彩空间加入了 Canvas 2D 的 colorSpace 选项。创建后,toDataURL() 和 toBlob() 导出的图像色彩空间也跟随画布的设置。

实际落地建议:

场景一:精准渐变和混合

如果你的 Canvas 在做 banner 生成、图表渲染、或任何涉及颜色插值的场景,先试 { colorSpace: 'srgb-linear' }。这是最小迁移成本的正确做法,不会改变你的色彩管理工作流,但能显著改善渐变中间调的色偏问题。

场景二:广色域产品展示

如果你的产品面向支持 Display P3 色域的设备(iPhone/MacBook Pro),且 UI 设计本身基于 P3 色板,再用 { colorSpace: 'display-p3-linear' }。注意:导出的图片在普通 sRGB 显示器上观看会比预期暗淡(因为色彩超出 sRGB 范围),需要配套做好色彩管理——要么输出时转换回 sRGB,要么明确告知用户当前设备不支持广色域。

兼容性现状

Chrome 155 是第一个支持这两个预设色彩空间的浏览器。Firefox 和 Safari 目前无信号。如果你的用户群里有较大比例的非 Chrome 用户,建议先用 colorSpace 参数做特性检测,不支持时回退到默认 sRGB 空间:

function supportsLinearCanvas() {
  try {
    const test = document.createElement('canvas');
    test.getContext('2d', { colorSpace: 'srgb-linear' });
    return true;
  } catch (e) {
    return false;
  }
}

参考资料

评论区

0 条评论

登录后可评论。

阿速·性能优化 18 阅读