使用 HTML Canvas 的广色域 2D 图形

当今网络上使用的大多数颜色都是 sRGB 颜色。这些颜色是通过熟悉的 #rrggbbrgb(r, g, b) CSS 语法指定的,其单个颜色分量的值范围是 [0, 255]。例如,rgb(255, 0, 0) 是 sRGB 色彩空间中最饱和的纯红色。但是 sRGB 的颜色范围——其色域——并不包含人类视觉系统所能感知的所有颜色,而且有些显示器可以产生更广泛的颜色范围。

sRGB 基于其标准化时(1990年代后期)存在的电脑显示器的色彩能力。从那时起,其他更广色域的色彩空间已被定义用于数字内容,它们覆盖了人类能够感知到的更多颜色。其中一个色彩空间是 Display P3,它包含比 sRGB 饱和度显著更高的颜色。

此浏览器报告显示器不支持 Display P3 颜色;此文章中的图片可能无法按预期显示。
A conic gradient showing a range of sRGB colors A conic gradient showing a range of Display P3 colors
显示完全饱和的 sRGB(左)和 Display P3(右)颜色的锥形渐变。在支持 Display P3 的浏览器和显示器上观看时,右侧圆圈中的颜色将比左侧的颜色更鲜艳。(以独立页面查看。
有关色彩空间的更深入介绍,请参阅 Dean Jackson 早期的文章:改善网络上的色彩

如今,市面上有许多电脑和移动设备配备了能够再现 Display P3 色域所有颜色的显示器,并且 Web 平台在过去几年中一直在发展,以允许作者充分利用这些显示器。WebKit 自 2016 年以来一直支持广色域图像和视频,并在去年成为第一个实现新色彩语法的浏览器引擎,该语法定义在CSS Color Module Level 4中,其中颜色可以指定在给定的色彩空间中(例如 color(display-p3 1 0 0),一个完全饱和的 Display P3 红色)。

广色域色彩支持的一个显著遗漏,直到现在为止,一直是在 HTML canvas 元素中。2D canvas API 在广色域显示器普及之前就已经推出,到目前为止,它只处理 sRGB 像素值的绘制和操作。今年早些时候,一项关于使用其他色彩空间创建 canvas 上下文的提案被添加到 HTML 标准中,我们最近已在 WebKit 中添加了对它的支持。

在广色域 canvas 渲染上下文上绘图

canvas 元素的 getContext 方法用于创建具有 2D 绘图 API 的渲染上下文对象,它接受一个新的选项来设置 canvas 后备存储的色彩空间。

<canvas id="canvas" width="400" height="300"></canvas>
<script>
let canvas = document.getElementById("canvas");
let context = canvas.getContext("2d", { colorSpace: "display-p3" });
// ... draw on context ...
</script>

默认色彩空间仍然是 sRGB,而不是让浏览器自动使用更广的色彩空间,以避免现有内容进行色彩空间转换带来的性能开销。可以请求的两个显式色彩空间是 "srgb""display-p3"

填充和描边样式可以使用任何支持的 CSS 颜色语法进行指定。

let position = 0;
for (let green of [1, 0]) {
    for (let blue of [1, 0]) {
        for (let red of [1, 0]) {
            context.fillStyle = `color(display-p3 ${red} ${green} ${blue})`;
            context.fillRect(position, position, 40, 40);
            position += 20;
        }
    }
}
Colored squares that have been clamped to sRGB Colored squares using Display P3 colors that are outside the sRGB gamut
用作 sRGB(左)和 Display P3(右)画布填充样式的 Display P3 颜色。左侧的颜色被限制在 sRGB 色域内。(以独立页面查看。

任何使用 canvas 色彩空间之外颜色的绘图都将被限制在色域内。例如,在 sRGB canvas 上用 color(display-p3 1 0 0) 填充矩形最终将使用完全饱和的 sRGB 红色。类似地,在 Display P3 canvas 上用 color(rec2020 0.9 0 0.9) 绘图(Rec.2020 色彩空间中接近全品红色),将导致使用大约 color(display-p3 1.0 0 0.923) 的像素,因为这是 Display P3 色域中最接近的颜色。

const COLORS = ["#0f0", "color(display-p3 0 1 0)"];
for (let y = 20; y < 180; y += 20) {
    context.fillStyle = COLORS[(y / 20) % 2];
    context.fillRect(20, y, 160, 20);
}
A filled square of full sRGB green Stripes of full sRGB green and full Display P3 green
在 sRGB(左)和 Display P3(右)画布上交错的 Display P3 和 sRGB 颜色条纹。由于颜色被限制在画布的色域内,两种绿色的深浅在 sRGB 画布上无法区分。(以独立页面查看。
在 macOS 上,您可以使用 ColorSync 实用工具在 sRGB、Display P3、Rec.2020 和其他一些预定义色彩空间之间转换颜色值。

广色域颜色可用于所有 canvas 绘图基元

  • 作为矩形、路径和文本的填充和描边
  • 在渐变停止点中
  • 作为阴影颜色

sRGB 和 Display P3 中的像素操作

getImageDataputImageData 可用于获取和设置广色域画布上的像素值。默认情况下,getImageData 将返回一个 ImageData 对象,其像素值位于画布的色彩空间中,但也可以指定一个与画布不匹配的显式色彩空间,此时将执行转换。

let context = canvas.getContext("2d", { colorSpace: "display-p3" });
context.fillStyle = "color(display-p3 0.5 0 0)";
context.fillRect(0, 0, 100, 100);

let imageData;

// Get ImageData in the canvas color space (Display P3).
imageData = context.getImageData(0, 0, 1, 1);
console.log(imageData.colorSpace);  // "display-p3"
console.log([...imageData.data]);   // [128, 0, 0, 255]

// Get ImageData in Display P3 explicitly.
imageData = context.getImageData(0, 0, 1, 1, { colorSpace: "display-p3" });
console.log(imageData.colorSpace);  // "display-p3"
console.log([...imageData.data]);   // [128, 0, 0, 255]

// Get ImageData converted to sRGB.
imageData = context.getImageData(0, 0, 1, 1, { colorSpace: "srgb" });
console.log(imageData.colorSpace);  // "srgb"
console.log([...imageData.data]);   // [141, 0, 0, 255]

ImageData 构造函数类似地接受一个可选的选项对象,其中包含一个 colorSpace 键。

let context = canvas.getContext("2d", { colorSpace: "display-p3" });

// Create and fill an ImageData with full Display P3 yellow.
let imageData = new ImageData(10, 10, { colorSpace: "display-p3" });
for (let i = 0; i < 10 * 10 * 4; ++i)
    imageData.data[i] = [255, 255, 0, 255][i % 4];

context.putImageData(imageData, 0, 0);

与使用不同色彩空间绘制形状时一样,ImageData 与目标 canvas 色彩空间之间的任何不匹配都将导致 putImageData 执行转换并可能裁剪结果像素。

序列化 canvas 内容

canvas DOM 元素的 toDataURLtoBlob 方法会生成一个包含 canvas 内容的栅格图像。在 WebKit 中,当在 Display P3 canvas 上调用这些方法时,它们现在会在生成的 PNG 或 JPEG 中嵌入适当的色彩配置文件,确保保留完整的色彩范围。

绘制广色域图像

putImageData 类似,当绘制色彩空间与 canvas 不同步的图像时,drawImage 方法会执行任何必要的色彩空间转换。当绘制到色彩空间与显示器匹配的 canvas(无论是 Display P3 还是 sRGB)时,img 元素引用的栅格图像使用的任何色彩配置文件,以及 video 元素引用的视频(无论是视频文件还是 WebRTC 流)中的任何色彩空间信息都将得到尊重。这确保了源图像/视频和 canvas 像素看起来相同。

这是一个交互式演示,展示了如何使用 canvas 制作滑动拼图。拼图方块通过应用剪切路径并调用 drawImage 来绘制,drawImage 指向左侧的 img 元素,该元素引用了一个广色域 JPEG 图片。勾选复选框可以显示在使用 sRGB canvas 时颜色是如何变得柔和的。

滑动拼图。勾选复选框将改变是使用 sRGB 还是 Display P3 canvas。(以独立页面查看。

Web Inspector 支持

Web Inspector 现在也显示 canvas 的色彩空间信息,以帮助确保您的 canvas 后备存储处于预期的色彩空间中。

在“图形”选项卡中,“Canvas 概览”将在每个 canvas 概览磁贴上显示每个 canvas 的色彩空间,紧邻上下文类型(例如 2D)。

点击 Canvas 概览磁贴进行检查后,色彩空间会在“详细信息”侧边栏的“属性”部分显示。

浏览器支持

广色域 canvas 自 r283541 起在 WebKit 的 macOS 和 iOS 端口中得到支持,并可在 Safari 上使用:

  • macOS Monterey 12.1 及以上版本
  • iOS 15.1 及以上版本

Safari 是第一个支持在 Display P3 画布上使用广色域 CSS 颜色绘制形状、文本、渐变和阴影的浏览器。所有其他功能,包括 Display P3 画布上的 getImageDataputImageDatadrawImage,均在 Safari 和 Chrome 94 及以上版本中得到支持。

功能检测

有几种技术可以用来检测广色域显示和 canvas 支持是否可用。

显示支持:要检查显示器是否支持 Display P3 颜色,请使用 color-gamut 媒体查询。

function displaySupportsP3Color() {
    return matchMedia("(color-gamut: p3)").matches;
}

Canvas 色彩空间支持:要检查浏览器是否支持广色域 canvas,请尝试创建一个并检查结果色彩空间。

function canvasSupportsDisplayP3() {
    let canvas = document.createElement("canvas");
    try {
        // Safari throws a TypeError if the colorSpace option is supported, but
        // the system requirements (minimum macOS or iOS version) for Display P3
        // support are not met.
        let context = canvas.getContext("2d", { colorSpace: "display-p3" });
        return context.getContextAttributes().colorSpace == "display-p3";
    } catch {
    }
    return false;
}

CSS Color Module Level 4 语法支持:要检查浏览器是否支持在 canvas 上指定广色域颜色,请尝试设置一个并检查它是否未被忽略。

function canvasSupportsWideGamutCSSColors() {
    let context = document.createElement("canvas").getContext("2d");
    let initialFillStyle = context.fillStyle;
    context.fillStyle = "color(display-p3 0 1 0)";
    return context.fillStyle != initialFillStyle;
}

未来工作

广色域 canvas 支持仍有几个可以改进的领域。

  • 2D canvas 仍然通过 ImageData 对象公开 8 位 RGBA 值的图像数据。支持其他像素格式以获得更大的色深可能很有用,例如 16 位整数,或单精度或半精度浮点值,尤其是在使用更广色域时,因为更高的精度可以帮助避免色带伪影。这已在 HTML 标准问题中提出。
  • 目前支持的两个预定义色彩空间是 sRGB 和 Display P3,但随着高动态范围视频和支持 HDR 的显示器变得越来越普遍,值得考虑允许 2D canvas 也使用这些和其他色彩空间。请参阅今年早些时候在 W3C Web 广色域和高动态范围研讨会上的此演示文稿,其中讨论了拟议的新色彩空间和 HDR 支持。
  • Canvas 可以与 2D 之外的其他上下文类型一起使用,例如 WebGL 和 WebGPU。在同一研讨会上,提出了一个关于这些上下文中的广色域和 HDR 支持的提案。

总结

WebKit 现在支持使用 Display P3 色彩空间创建 2D canvas 上下文,允许作者充分利用日益普及的显示器。此功能已在 macOS Monterey 12.1 和 iOS 15.1 上的 Safari 中启用。

如果您对该功能有任何意见或问题,请随时通过 @heycam 向我发送消息,更一般的评论可以发送到 @webkit Twitter 帐户。

进一步阅读