Web 图形合成技术:Blending 与 Porter-Duff Compositing

13 阅读19分钟

Web 图形渲染中,当多个图层重叠时,需要将它们组合成最终显示的图像。本文基于 W3C Compositing and Blending 规范,系统讲解 Blending 和 Porter-Duff Compositing 两大核心技术。从数学原理到实际应用,深入解析 Web 图形合成的完整体系。

符号与公式约定

理解图形合成的数学公式需要先明确符号约定。本章定义文中使用的核心术语和数学符号,包括颜色表示方式、Alpha 通道处理以及合成过程中的中间结果。

核心术语

Source 是当前要绘制的图层,通常是前景元素。它包含颜色信息和 Alpha 通道。

Backdrop 是 Source 后面的图层内容,是 Source 要与之合成的对象。Backdrop 是之前所有图层合成的累积结果。

Destination 是渲染目标,即画布上的像素缓冲区。合成的最终结果会写入 Destination。

Alpha 通道表示像素的不透明度,取值范围为 [0, 1]。Alpha 为 0 表示完全透明,Alpha 为 1 表示完全不透明。

预乘 Alpha 与非预乘 Alpha

颜色可以用两种形式表示:

非预乘 Alpha (Non-premultiplied Alpha) 表示颜色分量和 Alpha 通道独立存储。例如,半透明红色表示为 (R=1.0, G=0, B=0, α=0.5),颜色值不受 Alpha 影响。

预乘 Alpha (Premultiplied Alpha) 表示颜色分量已乘以 Alpha 值。同样的半透明红色表示为 (r=0.5, g=0, b=0, α=0.5),其中 r = R × α。预乘形式在图形硬件中更高效,简化了合成运算。

两种形式之间的转换:

// 非预乘转预乘
const cs = Cs * αs;
const cb = Cb * αb;

// 预乘转非预乘(需处理 α = 0 的情况)
const Cs = αs > 0 ? cs / αs : 0;
const Cb = αb > 0 ? cb / αb : 0;

符号定义

本文使用以下符号约定:

符号含义取值范围
CsSource 的非预乘颜色 (R, G, B)[0, 1]
CbBackdrop 的非预乘颜色 (R, G, B)[0, 1]
CrBlending 后的结果颜色 (R, G, B)[0, 1]
Co最终输出的非预乘颜色 (R, G, B)[0, 1]
csSource 的预乘颜色 (r, g, b)[0, 1]
cbBackdrop 的预乘颜色 (r, g, b)[0, 1]
crBlending 后的预乘颜色 (r, g, b)[0, 1]
co最终输出的预乘颜色 (r, g, b)[0, 1]
αsSource 的 Alpha 值[0, 1]
αbBackdrop 的 Alpha 值[0, 1]
αo输出的 Alpha 值[0, 1]
FaSource 的 Porter-Duff 因子[0, 1]
FbBackdrop 的 Porter-Duff 因子[0, 1]

关键符号说明:

  • Cr (Result color):Blending 阶段计算出的中间结果颜色。它是 Backdrop 颜色 Cb 和 Source 颜色 Cs 通过 Blending 函数混合后的结果,会在 Porter-Duff Compositing 阶段作为新的 Source 颜色使用。
  • Fa 和 Fb:Porter-Duff Compositing 的控制因子,决定 Source 和 Backdrop 对最终结果的贡献程度。

颜色值裁剪

合成计算过程中,颜色值可能超出 [0, 1] 范围。需要进行裁剪 (clamp) 处理:

// 裁剪到有效范围
const clamp = (value) => Math.max(0, Math.min(1, value));

Co = clamp(Co);
αo = clamp(αo);

某些 Blending 模式(如 color-dodge)可能产生大于 1 的值,裁剪确保最终输出的颜色在显示设备的有效范围内。

合成模型

图形合成是一个严格的两阶段流程:先进行 Blending 计算重叠区域的颜色混合,再使用 Alpha 合成公式处理 Alpha 通道的影响。本章介绍通用合成公式及其特例 Simple Alpha Compositing。

通用合成公式

完整的合成流程严格按照以下顺序执行:

输入: Source (Cs, αs), Backdrop (Cb, αb)
     ↓
[1. Blending]
     Cr = (1 - αb) × Cs + αb × B(Cb, Cs)
     ↓
[2. Alpha Compositing]
     co = Fa × (Cr × αs) + Fb × (Cb × αb)
     αo = Fa × αs + Fb × αb
     ↓
输出: Result (Co, αo)

第 1 步:Blending

计算混合后的颜色 Cr,对每个颜色通道(R/G/B)应用 Blending 函数 B(Cb, Cs)

// 非预乘形式
Cr = (1 - αb) × Cs + αb × B(Cb, Cs);

B(Cb, Cs) 定义了 Backdrop 和 Source 颜色的混合方式。不同的 Blending 模式对应不同的 B 函数实现:

  • normalB(Cb, Cs) = Cs(无混合,保持 Source 原色)
  • multiplyB(Cb, Cs) = Cb × Cs(正片叠底)
  • screenB(Cb, Cs) = Cb + Cs - Cb × Cs(滤色)

αb 控制 Blending 的影响程度:当 Backdrop 透明(αb=0)时保持 Source 原色,当 Backdrop 不透明(αb=1)时完全混合。

第 2 步:Alpha Compositing

Cr 作为新的 Source 颜色,使用因子 FaFb 控制 Source 和 Backdrop 的贡献程度:

// 预乘形式
co = Fa × (Cr × αs) + Fb × (Cb × αb);
αo = Fa × αs + Fb × αb;

FaFb 是合成因子,通过不同的取值实现不同的合成效果。例如:

  • Fa = 1, Fb = 1 - αs:Source 覆盖 Backdrop(source-over)
  • Fa = αb, Fb = 0:Source 裁剪到 Backdrop 形状(source-in)
  • Fa = 1 - αb, Fb = 1:Backdrop 覆盖 Source(destination-over)

合并公式

将两个步骤合并,得到完整的合成公式:

// 完整的合成公式(预乘形式)
co = Fa × B(Cb, Cs) × αs × αb + Fa × (1 - αb) × cs + Fb × cb;
αo = Fa × αs + Fb × αb;

这个公式清楚地展示了三部分的贡献:

  • Fa × B(Cb, Cs) × αs × αb:Blending 结果对重叠区域的贡献
  • Fa × (1 - αb) × cs:Source 在 Backdrop 透明区域的贡献
  • Fb × cb:Backdrop 根据合成因子的贡献

Simple Alpha Compositing

Simple Alpha Compositing 是通用公式的最常用特例,对应参数:

  • Blending 模式:normal,即 B(Cb, Cs) = Cs
  • 合成因子:Fa = 1, Fb = 1 - αs

代入通用公式得到:

// 预乘形式
co = cs + cb × (1 - αs);
αo = αs + αb × (1 - αs);

这就是默认的图层叠加方式(source-over):Source 以其不透明度覆盖在 Backdrop 上方。当 Source 完全不透明(αs = 1)时,Backdrop 完全被遮盖;当 Source 完全透明(αs = 0)时,输出就是 Backdrop。

关键要点:

  • Blending 在前,Alpha Compositing 在后,顺序不可颠倒
  • Blending 通过函数 B 计算颜色,Alpha Compositing 通过因子 FaFb 控制区域和 Alpha 通道
  • Simple Alpha Compositing 是 B=Cs, Fa=1, Fb=1-αs 的特例
  • 两个阶段通过中间结果 Cr 连接

Porter-Duff Compositing Operators

Porter-Duff Compositing Operators 由 Thomas Porter 和 Tom Duff 在 1984 年提出,定义了通过 Alpha 通道控制图层合成区域的代数运算体系。本章介绍 Porter-Duff 操作符的数学原理、13 种标准操作符以及它们与 Compositing Groups 的交互行为。

操作符原理

Porter-Duff 操作符基于 Alpha 通道的线性合成。核心思想是通过两个因子 FaFb 控制 Source 和 Backdrop 对最终结果的贡献程度:

co = Fa × cs + Fb × cb;
αo = Fa × αs + Fb × αb;

Porter-Duff 理论将两张图像的像素空间分为四个逻辑区域:

  1. Source 独占区域:Source 不透明,Backdrop 透明(αs > 0, αb = 0)
  2. Backdrop 独占区域:Backdrop 不透明,Source 透明(αs = 0, αb > 0)
  3. 重叠区域:两者都不透明(αs > 0, αb > 0)
  4. 空白区域:两者都透明(αs = 0, αb = 0)

为了精确控制这四个区域,FaFb 的取值被限制在与 Alpha 值相关的离散集合中:

Fa ∈ {0, αb, 1 - αb, 1}
Fb ∈ {0, αs, 1 - αs, 1}

这种限制确保了操作符具有明确的几何意义:

  • Fa = 0:Source 不贡献
  • Fa = 1:Source 完全贡献
  • Fa = αb:Source 仅在 Backdrop 存在的区域贡献
  • Fa = 1 - αb:Source 仅在 Backdrop 不存在的区域贡献

Fb 的含义与 Fa 对称。

13 种 Porter-Duff 操作符

从数学上看,FaFb 的取值集合可以产生 4 × 4 = 16 种组合。W3C 规范定义了其中 13 种具有实际意义的操作符:

操作符FaFb效果描述
clear00清除所有内容
copy10仅显示 Source
destination01仅显示 Backdrop
source-over11 - αsSource 覆盖 Backdrop(默认)
destination-over1 - αb1Backdrop 覆盖 Source
source-inαb0Source 裁剪到 Backdrop 形状
destination-in0αsBackdrop 裁剪到 Source 形状
source-out1 - αb0Source 在 Backdrop 外的部分
destination-out01 - αsBackdrop 在 Source 外的部分
source-atopαb1 - αsSource 在 Backdrop 上方(限定在 Backdrop 区域)
destination-atop1 - αbαsBackdrop 在 Source 上方(限定在 Source 区域)
xor1 - αb1 - αs仅显示非重叠区域
lighter11相加(需要 clamp)

未定义的 3 种组合(Fa=αb, Fb=αsFa=αb, Fb=1Fa=1, Fb=αs)缺乏明确的语义或实际应用价值,因此被排除在规范之外。

W3C 选择这 13 种操作符的原则是:

  1. 完备性:覆盖所有常见的图像合成需求(覆盖、裁剪、遮罩、异或等)
  2. 对称性:提供 Source 和 Destination 的对称操作(如 source-in 对应 destination-in
  3. 语义清晰:每个操作符都有明确的视觉效果和应用场景

常见应用场景

遮罩效果:使用 source-indestination-in

const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");

// 绘制遮罩形状(圆形)
ctx.arc(100, 100, 50, 0, Math.PI * 2);
ctx.fill();

// 使用 source-in 将图像裁剪到圆形
ctx.globalCompositeOperation = "source-in";
ctx.drawImage(image, 0, 0);

擦除效果:使用 destination-out

// 绘制底图
ctx.drawImage(image, 0, 0);

// 使用 destination-out 擦除
ctx.globalCompositeOperation = "destination-out";
ctx.arc(100, 100, 30, 0, Math.PI * 2);
ctx.fill(); // 擦除圆形区域

发光叠加:使用 lighter

// 绘制第一个光源
ctx.drawImage(light1, 0, 0);

// 使用 lighter 叠加第二个光源
ctx.globalCompositeOperation = "lighter";
ctx.drawImage(light2, 50, 50);

Blending 混合模式

Blending 控制重叠区域的颜色如何混合。与 Porter-Duff Compositing Operators 通过 FaFb 控制区域选择不同,Blending 通过函数 B(Cb, Cs) 改变颜色的计算方式。本章介绍 Blending 函数、与 Alpha Compositing 的结合、可分离和不可分离混合模式,以及 Group isolation 对 Blending 的影响。

Blending 函数

Blending 的核心是 Blending 函数 B(Cb, Cs),它定义了 Backdrop 颜色 Cb 和 Source 颜色 Cs 如何计算出混合后的颜色 Cr

// Blending 公式(非预乘形式)
Cr = (1 - αb) × Cs + αb × B(Cb, Cs);

αb 控制 Blending 函数的影响程度:

  • αb = 0(Backdrop 透明)时,Cr = Cs(保持 Source 原色)
  • αb = 1(Backdrop 不透明)时,Cr = B(Cb, Cs)(完全混合)

不同的 Blending 模式对应不同的 B 函数实现。Blending 模式分为两大类:

  • 可分离混合模式 (Separable Blend Modes)B(Cb, Cs) 对 RGB 每个通道独立计算
  • 不可分离混合模式 (Non-separable Blend Modes)B(Cb, Cs) 需要同时考虑所有颜色通道,涉及 RGB 与 HSL 色彩空间转换

Blending 与 Alpha Compositing

在完整的合成流程中,Blending 计算出 Cr 后,需要与 Porter-Duff Compositing 结合。最常见的简化模式是:Blending 之后进行 source-over 合成

这种模式对应参数:

  • Blending 模式:任意 B(Cb, Cs) 函数(如 multiply、screen 等)
  • Alpha Compositing 因子:Fa = 1, Fb = 1 - αs(source-over)

完整公式为:

// 第 1 步:Blending
Cr = (1 - αb) × Cs + αb × B(Cb, Cs);

// 第 2 步:source-over Alpha Compositing
co = Cr × αs + Cb × αb × (1 - αs);
αo = αs + αb × (1 - αs);

合并为单一公式:

// Blending + source-over 合成(预乘形式)
co = B(Cb, Cs) × αs × αb + (1 - αb) × cs + (1 - αs) × cb;
αo = αs + αb × (1 - αs);

这个公式清楚地展示了三部分的贡献:

  • B(Cb, Cs) × αs × αb:Blending 结果对重叠区域的贡献
  • (1 - αb) × cs:Source 在 Backdrop 透明区域的贡献
  • (1 - αs) × cb:Backdrop 在 Source 透明区域的贡献

这是 Web 平台最常用的合成模式,通过 CSS 的 mix-blend-mode 属性或 Canvas 的 globalCompositeOperation 属性使用。

可分离混合模式

可分离混合模式的 B(Cb, Cs) 函数对每个颜色通道独立应用。

混合模式B(Cb, Cs) 公式效果描述常见用途
normalCs无混合默认
multiplyCb × Cs正片叠底阴影、纹理叠加
screenCb + Cs - Cb × Cs滤色发光效果
overlayCb ≤ 0.5 ? 2×Cb×Cs : 1 - 2×(1-Cb)×(1-Cs)叠加图像增强
darkenmin(Cb, Cs)变暗暗部合成
lightenmax(Cb, Cs)变亮亮部合成
color-dodgeCs ≥ 1 ? 1 : min(1, Cb / (1 - Cs))颜色减淡强光效果
color-burnCs ≤ 0 ? 0 : 1 - min(1, (1 - Cb) / Cs)颜色加深加深效果
hard-lightCs ≤ 0.5 ? 2×Cb×Cs : 1 - 2×(1-Cb)×(1-Cs)强光高对比度效果
soft-light复杂公式(详见 W3C 规范)柔光柔光效果
difference|Cb - Cs|差值差异检测
exclusionCb + Cs - 2×Cb×Cs排除柔和差异

代码示例:使用 multiply 混合模式

const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");

// 绘制 Backdrop(蓝色矩形)
ctx.fillStyle = "blue";
ctx.fillRect(50, 50, 100, 100);

// 使用 multiply 混合模式绘制 Source(红色圆形)
ctx.globalCompositeOperation = "multiply";
ctx.fillStyle = "red";
ctx.arc(100, 100, 50, 0, Math.PI * 2);
ctx.fill();
// 重叠区域:blue × red = 暗紫色

不可分离混合模式

不可分离混合模式需要在 HSL 色彩空间中操作。辅助函数:

// 计算亮度 (Luminosity)
Lum(C) = 0.3 × C.red + 0.59 × C.green + 0.11 × C.blue;

// 计算饱和度 (Saturation)
Sat(C) = max(C.red, C.green, C.blue) - min(C.red, C.green, C.blue);

// 设置亮度:保持色相和饱和度,修改亮度
SetLum(C, lum);

// 设置饱和度:保持色相和亮度,修改饱和度
SetSat(C, sat);
混合模式B(Cb, Cs) 公式效果描述常见用途
hueSetLum(SetSat(Cs, Sat(Cb)), Lum(Cb))使用 Source 色相色彩调整
saturationSetLum(SetSat(Cb, Sat(Cs)), Lum(Cb))使用 Source 饱和度饱和度调整
colorSetLum(Cs, Lum(Cb))使用 Source 色相和饱和度着色效果
luminositySetLum(Cb, Lum(Cs))使用 Source 亮度亮度调整

Compositing Groups

Compositing Groups 影响元素如何与背景交互。理解 Groups 对于掌握复杂的视觉效果至关重要,它决定了子元素能否看到外部背景,以及 Group 如何作为整体与外部合成。

Group Invariance(组不变性)

在复杂的页面结构中,多个元素通常会被组织成层次化的 Group(如 HTML 的 <div> 嵌套)。Group Invariance 定义了这些 Group 与外部背景合成时应遵循的基本原则。

Group Invariance 核心原则:Group 作为整体与外部 Backdrop 合成的结果,应该与 Group 内部的实现细节无关。

这意味着:

  1. Group 内部的元素先相互合成,生成 Group 的中间结果
  2. Group 的中间结果作为单个图层与外部 Backdrop 进行合成
  3. Group 内部元素的顺序、数量、合成方式对外部不可见

这个原则确保了模块化和封装性。例如,一个 <div> 中包含 10 个子元素还是 100 个子元素,只要最终视觉效果相同,与页面其他部分的合成结果就应该相同。

数学表达

Group Invariance 的合成流程可以表示为:

// 第 1 步:Group 内部合成
let groupResult = initialBackdrop;
for (const element of groupElements) {
  groupResult = composite(groupResult, element);
}

// 第 2 步:Group 结果与外部合成
const finalResult = composite(externalBackdrop, groupResult);

关键问题:什么时候组不变性成立?什么时候会被打破?

Simple Alpha Compositing 天然满足组不变性

对于默认的 Simple Alpha Compositing(source-over 模式),组不变性总是成立。原因在于 source-over 公式是线性的且满足结合律

// source-over 公式(预乘形式)
co = cs + cb × (1 - αs);
αo = αs + αb × (1 - αs);

// 结合律:合成顺序可以任意分组
composite(composite(A, B), C) === composite(A, composite(B, C));
// (A ⊕ B) ⊕ C = A ⊕ (B ⊕ C)

因为满足结合律,我们可以:

  • 先把 A 和 B 合成,再与 C 合成 → (A ⊕ B) ⊕ C
  • 或者先把 B 和 C 合成,再与 A 合成 → A ⊕ (B ⊕ C)
  • 两种方式结果完全相同

这就是为什么在普通情况下(不使用 Blending 或特殊 Porter-Duff 操作符),页面渲染可以任意分组和优化,而不影响最终显示效果。

Blending 打破组不变性

然而,当引入 Blending 混合模式(如 multiplyscreen 等)时,组不变性会被打破。原因是 Blending 函数 B(Cb, Cs) 通常是非线性的,不满足结合律。

multiply 模式为例:

B(Cb, Cs) = Cb × Cs;

// 非线性导致不满足结合律
B(B(Ca, Cb), Cc) ≠ B(Ca, B(Cb, Cc));

考虑完整的 Blending 公式(回顾合成模型章节):

Cr = (1 - αb) × Cs + αb × B(Cb, Cs);

由于 B 函数的非线性和 αb 的参与,不同的分组方式会导致不同的 Backdrop 和 Alpha 值组合,从而产生不同的结果:

// 情况 1:元素 B 在 Group 内与 C 合成
// B 看到的 Backdrop 是 C,αb 来自 C
groupResult = blend(C, B); // B 与 C 混合
final = blend(A, groupResult); // Group 结果与 A 混合

// 情况 2:元素 B 直接与外部 A 合成
// B 看到的 Backdrop 是 A,αb 来自 A
temp = blend(A, B); // B 与 A 混合
final = blend(temp, C); // 再与 C 混合

// 两种情况的 αb 和 Backdrop 颜色都不同,结果也不同

解决方案:创建 Compositing Group

为了在使用 Blending 时保持确定性可预测性,Web 规范要求创建 Compositing Group 来明确定义合成顺序:

.element {
  mix-blend-mode: multiply;
  /* 自动创建 Compositing Group(层叠上下文) */
}

通过创建 Compositing Group,浏览器明确定义:

  1. 合成顺序:Group 内的元素按文档顺序依次合成
  2. Backdrop 来源:每个元素看到的 Backdrop 是前面所有元素的合成结果
  3. 初始 Backdrop:Group 的第一个元素看到的 Backdrop 来自 initialBackdrop

实际例子:

<div style="background: red;">
  <div style="background: green; mix-blend-mode: multiply;">
    <div style="background: blue;"></div>
  </div>
</div>

渲染流程:

  1. 绿色层设置了 multiply,自动创建 Compositing Group
  2. 蓝色层在绿色层的 Group 内:
    • 先计算 blue ⊗ green(⊗ 表示 multiply blending)
    • 生成绿色 Group 的最终结果
  3. 绿色 Group 的结果再与红色背景混合:
    • 计算 red ⊗ (blue ⊗ green)
  4. 合成顺序:blue → green → red,确定且不可改变

initialBackdrop 的作用

注意上面流程中,蓝色层混合时需要一个 Backdrop(这里是绿色)。但绿色层自己混合时,它的 Backdrop 是什么?这就引出了 initialBackdrop 的概念。

initialBackdrop 定义了 Group 的起始背景

  • Isolated GroupinitialBackdrop = 透明,Group 内元素看不到外部背景
  • Non-isolated GroupinitialBackdrop = 外部背景,Group 内元素可以看到外部背景

这就是 Isolated 和 Non-isolated Groups 的核心区别,我们将在下一节详细讨论。

Isolated 和 Non-isolated Groups

Isolated Groups 和 Non-isolated Groups 的核心区别在于 initialBackdrop 的取值:

Isolated Groups(隔离组)

初始 Backdrop 为全透明黑色 rgba(0, 0, 0, 0),Group 内部元素看不到外部背景:

let groupResult = { r: 0, g: 0, b: 0, α: 0 }; // 透明背景

for (const element of groupElements) {
  groupResult = composite(groupResult, element);
}

// Group 作为整体与外部合成
const finalResult = composite(externalBackdrop, groupResult);

Non-isolated Groups(非隔离组)

初始 Backdrop 使用外部背景,元素可以看到外部内容。从效果上看,Non-isolated Group 相当于直接合成

let groupResult = externalBackdrop; // 使用外部背景

for (const element of groupElements) {
  groupResult = composite(groupResult, element);
}

const finalResult = groupResult;

创建 Isolated Group 的方式

  • CSS:创建层叠上下文 (stacking context) 的操作会创建 Isolated Group
    • mix-blend-mode(非 normal
    • opacity < 1
    • transformfilterisolation: isolate
  • SVG:opacity、filters、3D transforms、blending、masking

不创建 Isolated Group:CSS 的 background-blend-mode、Canvas 2D 的 globalCompositeOperation

示例:Isolated vs Non-isolated

假设有一个绿色圆形使用 multiply 混合模式叠加在红色方块和蓝色背景上:

<!-- Non-isolated Group(默认) -->
<div style="background: blue; padding: 50px;">
  <div style="background: red; width: 100px; height: 100px;"></div>
  <div
    style="background: green; border-radius: 50%; width: 80px; height: 80px;
              margin: -60px 0 0 40px; mix-blend-mode: multiply;"
  ></div>
</div>

<!-- Isolated Group -->
<div style="background: blue; padding: 50px;">
  <div style="isolation: isolate;">
    <div style="background: red; width: 100px; height: 100px;"></div>
    <div
      style="background: green; border-radius: 50%; width: 80px; height: 80px;
                margin: -60px 0 0 40px; mix-blend-mode: multiply;"
    ></div>
  </div>
</div>

Non-isolated Group 的合成过程:

// 初始 Backdrop 是蓝色背景
let result = blue;

// 红色方块与蓝色背景合成
result = composite(result, red); // → 蓝色背景上的红色方块

// 绿色圆形使用 multiply 模式与当前结果合成
// 绿色圆形会与红色方块和蓝色背景都发生混合
result = composite_with_blend(result, green, multiply);
// 重叠红色区域:green × red = 暗红色
// 重叠蓝色区域:green × blue = 暗蓝色

Isolated Group 的合成过程:

// Group 内部:初始 Backdrop 是透明
let groupResult = transparent;

// 红色方块与透明背景合成
groupResult = composite(groupResult, red); // → 红色方块(无背景)

// 绿色圆形使用 multiply 模式与红色方块合成
// 绿色圆形只能看到红色方块,看不到外部蓝色背景
groupResult = composite_with_blend(groupResult, green, multiply);
// 重叠区域:green × red = 暗红色
// 非重叠区域:保持原色

// Group 结果与蓝色背景合成
result = composite(blue, groupResult);
// 最终:蓝色背景 + 红色方块 + 暗红色重叠区域(绿色圆形不会与蓝色混合)

关键区别:

  • Non-isolated:绿色圆形与红色方块和蓝色背景都发生 multiply 混合
  • Isolated:绿色圆形只与红色方块发生 multiply 混合,看不到蓝色背景

实际应用

Compositing 和 Blending 技术在 Web 平台的三个主要领域有广泛应用:Canvas 2D API、HTML/CSS、SVG。本章介绍如何在这些平台使用这些技术,以及性能优化建议。

平台能力对比

  • Canvas 2D:同时支持 Porter-Duff 操作符和 Blending 模式
  • CSS:仅支持 Blending 模式(通过 mix-blend-modebackground-blend-mode
  • SVG:通过滤镜元素支持 Porter-Duff 操作符(feComposite)和 Blending 模式(feBlend

重要说明:在所有 Web 平台(Canvas 2D、CSS、SVG)中,Blending 模式都采用 Blending + source-over 的合成方式,即 Blending 计算后使用 source-over 进行 Alpha Compositing(Fa=1, Fb=1-αs)。

Canvas 2D

Canvas 2D 通过 globalCompositeOperation 属性控制 Compositing 和 Blending。

globalCompositeOperation 属性

该属性接受字符串值,指定合成或混合模式。支持的值包括:

  • Porter-Duff 操作符source-over(默认), source-in, source-out, source-atop, destination-over, destination-in, destination-out, destination-atop, lighter, copy, xor, destination
  • Blending 模式multiply, screen, overlay, darken, lighten, color-dodge, color-burn, hard-light, soft-light, difference, exclusion, hue, saturation, color, luminosity

CSS

CSS 仅支持 Blending 模式,不支持 Porter-Duff 操作符。提供以下属性控制 Blending:

mix-blend-mode

控制元素与其 Backdrop 的混合方式。元素会与下方的所有内容进行混合。

  • 语法:mix-blend-mode: <blend-mode>
  • 支持的值:normal(默认), multiply, screen, overlay, darken, lighten, color-dodge, color-burn, hard-light, soft-light, difference, exclusion, hue, saturation, color, luminosity
  • 自动创建 Compositing Group(非 normal 时)

background-blend-mode

控制同一元素的多个背景层之间的混合。多个背景按照从上到下的顺序混合。

  • 语法:background-blend-mode: <blend-mode>
  • 支持的值:与 mix-blend-mode 相同
  • 可以指定多个值,对应多个背景层

isolation

显式创建 Isolated Group,隔离子元素的混合效果。

  • 语法:isolation: auto | isolate
  • auto:默认,不创建隔离组
  • isolate:创建 Isolated Group,子元素的混合不会影响外部背景

SVG

SVG 通过滤镜元素实现 Compositing 和 Blending。

feComposite 元素

实现 Porter-Duff Compositing 操作符。

  • 属性:operator 指定操作符类型
  • 支持的值:over, in, out, atop, xor, lighter, arithmetic
  • inin2 属性指定 Source 和 Backdrop
  • arithmetic 模式支持自定义线性组合公式

feBlend 元素

实现 Blending 混合模式。

  • 属性:mode 指定混合模式
  • 支持的值:normal, multiply, screen, overlay, darken, lighten, color-dodge, color-burn, hard-light, soft-light, difference, exclusion, hue, saturation, color, luminosity
  • inin2 属性指定要混合的两个输入源

Isolated Group

Isolated Group 需要离屏渲染,带来额外的性能开销:

  • 浏览器为 Group 分配离屏缓冲区(offscreen buffer)
  • Group 内容先渲染到离屏缓冲区,再合成到主画布
  • 增加内存占用和 GPU 计算负担

过多或嵌套的 Isolated Group 会显著影响渲染性能。

Canvas 2D

Canvas 2D 是即时模式(immediate mode)绘图 API,不会自动创建 Isolated Group。如需隔离效果,需手动使用离屏 Canvas。

HTML/CSS

根据 W3C 规范 Section 3.2,HTML/CSS 中所有创建层叠上下文(Stacking Context)的属性都会创建 Isolated Group,包括 opacitytransformfiltermix-blend-modeisolation: isolate 等。

SVG

根据 W3C 规范 Section 3.3,SVG 中只有特定操作会创建 Isolated Group:

  • opacity
  • filters(滤镜)
  • 3D transforms(2D transforms 不会创建)
  • blending(混合模式)
  • masking(遮罩)