zyl910

优化技巧、硬件体系、图像处理、图形学、游戏编程、国际化与文本信息处理。

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

作者: zyl910

一、缘由

上一篇文章“用于分析26种画布合成模式(globalCompositeOperation)的演示页面”给出了便于测试的演示页面,现在探究一下合成模式的计算公式。
在网上搜索了一下,发现W3C《Compositing and Blending Level 1》对合成模式的公式说的最详细,于是仔细阅读了该文档。
该文档的篇幅比较长,且是英文版的,看起来比较吃力。且有些细节写的比较简略,若忽略了那些细节,可能会导致构造的公式不正确、计算结果不符。
于是整理了一下我的心得,编写了此文。

二、《Compositing and Blending Level 1》阅读心得

对于合成模式来说,最重要的内容在该文档的这几个章节——

5.1 Simple alpha compositing
9 Advanced compositing features
10 Blending

“5.1 Simple alpha compositing”是介绍最基础的的简单Alpha合成。
随后第9、10章是基于这个基础算法,创造了更丰富的合成模式。
从第9、10章的标题来看,该文档将合成运算分为了2个大类——

  • Compositing(合成):最常用的“Source Over”合成就是属于这一类的。画布的26种画布合成模式里,前10种是属于这一类的。
  • Blending(混合):画布的26种画布合成模式里,后16种是属于这一类的。

2.1 简单Alpha合成(“5.1 Simple alpha compositing”)

2.1.1 计算颜色分量

在“5.1 Simple alpha compositing”里,首先说明了如何计算颜色分量,摘录——

The formula for simple alpha compositing is
co = Cs x αs + Cb x αb x (1 - αs)

Where
co: the premultiplied pixel value after compositing
Cs: the color value of the source graphic element being composited
αs: the alpha value of the source graphic element being composited
Cb: the color value of the backdrop
αb: the alpha value of the backdrop

Note: All values are between 0 and 1 inclusive.

The pixel value after compositing (co) is given by adding the contributions from the source graphic element [Cs x αs] and the backdrop [Cb x αb x (1 - αs)]. For both the graphic element and the backdrop, the color values are multiplied by the alpha to determine the amount of color that contributes. With zero alpha meaning that the color does not contribute and partial alpha means that some percentage of the color contributes. The contribution of the backdrop is further reduced based on the opacity of the graphic element. Conceptually, (1 - αs) of the backdrop shows through the graphic element, meaning that if the graphic element is fully opaque (αs=1) then no backdrop shows through.

中文翻译——

5.1。简单的 alpha 合成
简单 alpha 合成的公式是
co = Cs x αs + Cb x αb x (1 - αs)

其中
co:合成后的预乘像素值
cs:正在合成的源图形元素的颜色值
αs:正在合成的源图形元素的 alpha 值
Cb:背景的颜色值
αb:背景的 alpha 值
注意:所有值都介于 0 和 1 之间。

合成后的像素值 (co) 由源图形元素 [Cs x αs] 和背景 [Cb x αb x (1 - αs)] 的贡献相加得出。对于图形元素和背景,颜色值乘以 alpha 以确定贡献的颜色量。零 alpha 意味着颜色没有贡献,部分 alpha 意味着一定百分比的颜色有贡献。背景的贡献基于图形元素的不透明度进一步减少。从概念上讲,背景的 (1 - αs) 通过图形元素显示,这意味着如果图形元素完全不透明 (αs=1),则没有背景显示。

“x”代表“*”,即乘法运算。
c代表颜色(Color)分量。如 R、G、B 分量的值。
很多关于Alpha合成的资料中,将 backdrop(背景)称为 Destination(目标)。了解了这一点之后,可以与其他资料进行对照。

2.1.2 计算Alpha分量

该小节随后说明了如何计算Alpha(不透明度)分量。摘录——

The simple alpha compositing formula listed above gives a resultant color which is the result of the weighted average of the backdrop color and graphic element color, with the weighting determined by the backdrop and graphic element alphas. The resultant alpha value of the composite is simply the sum of the contributed alpha of the composited elements. The formula for the resultant alpha of the composite is
αo = αs + αb x (1 - αs)

Where
αo: the alpha value of the composite
αs: the alpha value of the graphic element being composited
αb: the alpha value of the backdrop

中文翻译——

上面列出的简单 alpha 合成公式给出的结果颜色是背景颜色和图形元素颜色的加权平均结果,权重由背景和图形元素 alpha 决定。合成的合成 alpha 值只是合成元素贡献的 alpha 的总和。合成的结果 alpha 的公式是
αo = αs + αb x (1 - αs)

其中
αo:合成的 alpha 值
αs:正在合成的图形元素的 alpha 值
αb:背景的 alpha 值

2.1.3 处理预乘(pre-multiplied)

该文档最后说明了如何处理预乘(pre-multiplied)。摘录——

Often, it can be more efficient to store a pre-multiplied value for the color and opacity. The pre-multiplied value is given by
cs = Cs x αs

with
cs: the pre-multiplied value
Cs: the color value
αs: the alpha value

Thus the formula for simple alpha compositing using pre-multiplied values becomes
co = cs + cb x (1 - αs)

To extract the color component of a pre-multiplied value, the formula is reversed:
Co = co / αo

中文翻译——

通常,存储颜色和不透明度的预乘值会更有效。预乘法的值由以下公式给出
cs = Cs x αs

其中
cs:预乘值
Cs:颜色值
αs:alpha值

因此,使用预乘值进行简单 alpha 合成时,公式变为
co = cs + cb x (1 - αs)

为了从预乘值提取颜色分量,可对公式进行逆向变换:
Co = co / αo

2.1.4 小结

初看这一节时可能会有这样的疑问——该章节标题是“简单Alpha合成”,但给出的公式怎么比较复杂。貌似很多资料上的“Alpha合成”公式比较简单。
原因在于这一节讲解的是 “源与背景均具有Alpha通道”时的合成算法。而不少资料讲解的仅是“没有Alpha通道时的特例”。
没有Alpha通道时,Alpha合成可看作简单的线性插值,计算公式很简单。且不用理会预乘(pre-multiplied)。
但源与背景均具有Alpha通道时,就没有这么简单了。需要基于加色法光照模型来推导的,颜色预乘后的结果才是实际的光照贡献值。这就是为什么主公式的计算结果为预乘值的原因。这也是很多资料推荐“用预乘的位图格式”的原因。

而Html5画布为了便于直观使用,规定了位图数据为“非预乘”(not use pre-multiplied、not multiplied。另一种称呼是 Straight,即“直通”)格式的,于是在Alpha混合后需再做一次“预乘值转非预乘值”的转换。

因Html5画布的位图是非预乘的,于是可以总结得到以下经验——

  • 该文中的提到的 源(s:source)、背景(b:backdrop)颜色值,一般是非预乘(not use pre-multiplied)值。
  • 该文中的提到的主要公式计算结果,一般是预乘(pre-multiplied)值。
  • 最后为了转为Html5规范的位图颜色值,得再做一次“预乘值转非预乘值”的转换。

总结一下,该文的“简单Alpha合成”计算过程,由这3个公式组成——

co = Cs x αs + Cb x αb x (1 - αs) // 主公式, 得到混合后的已预乘颜色分量值。
αo = αs + αb x (1 - αs) // 得到混合后的Alpha(不透明度)分量值。
Co = co / αo	// 对已预乘颜色分量进行“预乘值转非预乘值”转换,得到了非预乘的颜色分量。即转为Html5规范的位图颜色值。

2.2 Compositing类合成模式的算法(9 Advanced compositing features)

Compositing类合成模式,又被称为“The Porter Duff Compositing Operators”。“Porter Duff”是指 Thomas Porter 和 Tom Duff ,他们于 1984 年发表了一篇名为 “Compositing Digital Images”(合成数字图像)的开创性论文。在这篇文章中,两位作者描述了12种合成操作符,具体来说就是当我们把原图像绘制到目标图像处时应该如何计算二者结合后的颜色。

2.2.2 解读“9.1. The Porter Duff Compositing Operators”

《Compositing and Blending Level 1》第9章中的第1节,对此进行了说明:

9.1. The Porter Duff Compositing Operators
The landmark paper by Thomas Porter and Tom Duff, who worked for Lucasfilm, defined the algebra of compositing and developed the twelve "Porter Duff" operators. These operators control the results of mixing the four sub-pixel regions formed by the overlapping of graphical objects that have an alpha or pixel coverage channel/value. The operators use all practical combinations of the four regions.

There are 12 basic Porter Duff operators, satisfying all possible combinations of source and destination.

From the geometric representation of each operator, the contribution of each shape can be seen to be expressed as a fraction of the total coverage of the output. For example, in source over, the possible contribution of source is full (1) and the possible contribution of destination is whatever is remaining (1 – αs). This is modified by the coverage of source and destination to give the equation for the final coverage of the pixel:
αo = αs x 1 + αb x (1 – αs)

The fractional terms Fa (1 in this example) and Fb (1 – αs in this example) are defined for each operator and specify the fraction of the shapes that may contribute to the final pixel value. The general form of the equation for coverage is:
αs x Fa + αb x Fb

and incorporating color gives the general Porter Duff equation
co = αs x Fa x Cs + αb x Fb x Cb

Where:
co is the output color pre-multiplied with the output alpha [0 <= co <= 1]
αs is the coverage of the source Fa is defined by the operator and controls inclusion of the source Cs is the color of the source (not multiplied by alpha)
αb is the coverage of the destination Fb is defined by the operator and controls inclusion of the destination Cb is the color of the destination (not multiplied by alpha)

中文翻译——

9.1. 波特-达夫合成算子
为卢卡斯影业工作的托马斯-波特和汤姆-达夫发表了一篇具有里程碑意义的论文,定义了合成的代数并开发了12个 "波特-达夫 "算子。这些算子控制着由具有阿尔法或像素覆盖通道/值的图形对象的重叠所形成的四个子像素区域的混合结果。这些算子使用这四个区域的所有实际组合。

有12个基本的波特-达夫算子,满足源和目的的所有可能组合。

从每个算子的几何表示法来看,每个形状的贡献可以被看作是输出的总覆盖率的一部分来表示。例如,在源超过,源的可能贡献是完全的(1),目的地的可能贡献是任何剩余的(1-αs)。这是由源和目的地的覆盖率修改的,从而得到像素的最终覆盖率的方程式。
αo = αs x 1 + αb x (1 - αs)

分数项Fa(本例中为1)和Fb(本例中为1-αs)是为每个算子定义的,并指定了可能对最终像素值有贡献的形状的分数。覆盖率方程的一般形式是。
αs x Fa + αb x Fb

并结合颜色给出一般波特达夫方程
co = αs x Fa x Cs + αb x Fb x Cb

其中:
co是输出颜色预先乘以输出α[0 <= co <= 1] 。
αs是信号源的覆盖范围 Fa是由操作者定义的,控制信号源的包含 Cs是信号源的颜色(未乘以α)。
αb是目的地的覆盖范围 Fb由操作者定义,控制目的地的包含范围 Cb是目的地的颜色(不乘以alpha)。

观察上文中的“pre-multiplied”,可以发现——

  • 输出的颜色(co):是与Alpha进行预乘后的值(is the output color pre-multiplied with the output alpha)。因它是预乘值,为了转为Html5规范的位图颜色值,得再做一次“预乘值转非预乘值”的转换。
  • 源或背景的颜色(cs、cb):是非预乘的值(not multiplied by alpha)。因它是非预乘值,可直接使用Html5的位图颜色值。

Fa、Fb是Compositing类合成模式的系数。靠调整Fa、Fb的取值,构造了这12种“The Porter Duff Compositing Operators”。

2.2.2 “Source Over”运算模式的分析

“Source Over”是最常用的混合模式,现在以它为例来说明“Porter Duff公式”(the general Porter Duff equation)的用法。

摘录该文档的“9.1.4. Source Over”——

9.1.4. Source Over
Source is placed over the destination.

example of porter duff source over
Fa = 1; Fb = 1 – αs
co = αs x Cs + αb x Cb x (1 – αs)
αo = αs + αb x (1 – αs)

co的计算公式是这样得到的——

将“Fa = 1; Fb = 1 – αs”带入“Porter Duff公式”(the general Porter Duff equation)后:

co = αs x Fa x Cs + αb x Fb x Cb
 = αs x (1) x Cs + αb x (1 – αs) x Cb
 = αs x Cs + αb x Cb x (1 – αs)

将上述co计算公式,与“5.1 Simple alpha compositing”里的公式进行对比,会发现它们是同一个公式。

co = Cs x αs + Cb x αb x (1 - αs)

即“Source Over”就是“Simple alpha compositing”(简单Alpha合成)。

2.2.3 演示页面是怎样显示“Source Over”的详细计算过程的

上一篇文章“用于分析26种画布合成模式(globalCompositeOperation)的演示页面”里,说到了该演示页面支持显示“点击像素的当前合成模式详细计算过程”。现在来说明一下演示页面是怎么实现的。

首先来看函数调用流程——

  1. canvas_mousedown、canvas_mousemove事件里,会调用 showClickInfo。
  2. showClickInfo 获取点击的像素值,随后调用 getInfoByComposite,最后进行展示。
  3. getInfoByComposite 函数负责获取详细计算过程。

getInfoByComposite里与“Source Over”合成模式相关的代码,摘录如下:

/** Get info by composite detail.
 *
 * @param {String}	compositeMode	The composite mode. From `Context.globalCompositeOperation` .
 * @param {Array}	dataD	The destination pixel value (RGBA bytes).
 * @param {Array}	dataS	The source pixel value (RGBA bytes).
 * @return	{String} Returns color info.
 */
function getInfoByComposite(compositeMode, dataD, dataS) {
	const digits = 3;
	const ALL_CHANNELS = 4;
	const COLOR_CHANNELS = 3;
	const ALPHA_INDEX = COLOR_CHANNELS;
	const colorNames = ["R", "G", "B", "A"];
	let msg = "compositeMode:\t" + compositeMode;
	let fD = toFloatColor(dataD);
	let fS = toFloatColor(dataS);
	let fO = new Array(ALL_CHANNELS);
	let Ab = fD[ALPHA_INDEX];	// background(destination) alpha.
	let As = fS[ALPHA_INDEX];	// source alpha.
	let AbStr = Ab.toFixed(digits);
	let AsStr = As.toFixed(digits);
	let info;
	let i;
	let showOut = false;
	if ("source-over"==compositeMode) {
		msg += "\n\tFa = 1; Fb = 1 - As"
			+";\tCo = As * Cs + Ab * Cb * (1 - As)"
			+";\tAo = As + Ab * (1 - As)";
		showOut = true;
		for(i=0; i<COLOR_CHANNELS; ++i) {
			fO[i] = As * fS[i] + Ab * fD[i] * (1 - As);
			let name = colorNames[i];
			msg += `\n${name}o = As * ${name}s + Ab * ${name}b * (1 - As) = ${AsStr} * ${fS[i].toFixed(digits)} + ${AbStr} * ${fD[i].toFixed(digits)} * (1 - ${AsStr}) = ${fO[i].toFixed(digits)}`;
		}
		i = ALPHA_INDEX;
		fO[i] = As + Ab * (1 - As);
		msg += `\nAo = As + Ab * (1 - As) = ${AsStr} + ${AbStr} * (1 - ${AsStr}) = ${fO[i].toFixed(digits)}`;
	}
	if (showOut) {
		let dataO = fromFloatColor(fO);
		let infoO = getInfoByColorBytes(dataO);
		msg += "\nPremultiplie:" + infoO;
		let fW = alphaPremultiplieToStraight(fO);
		let dataW = fromFloatColor(fW);
		let infoW = getInfoByColorBytes(dataW);
		msg += "\nOutput     : " + infoW;
	}
	return msg;
}

上面的代码比较简单,主要内容在2个if语句中——

  1. compositeMode的if:用于实现“Source Over”的计算处理。首先将运算公式拼接到msg字符串里,随后用for循环里按公式处理R、G、B这3个颜色通道的计算,最后按公式处理A通道的计算。因计算结果是预乘的,于是设置showOut变量为true,统一由最后的if语句来展示计算结果。
  2. showOut的if:统一展示计算结果。首先展示直接的运算结果,它是预乘(Premultiplie)的值;随后做一次“预乘值转非预乘值”转换(alphaPremultiplieToStraight),再进行展示,此时得到是Html5画布里的颜色值。

补充说明——

  • 为了方便运算,dataD, dataS是字节数组。它是一个长度为4的数组,分别为 R、G、B、A 分量的字节值,每个分量的值在 [0,255] 范围内。
  • 因文档上的公式都是用归一化浮点颜色值的,于是调用了toFloatColor函数,将dataD, dataS,转为了浮点颜色值 fD、fS。浮点颜色值是一个长度为4数组,分别为 R、G、B、A 分量的浮点值,每个分量的值在 [0,1] 范围内。
  • fromFloatColor用于将浮点颜色值,转为RGBA整数颜色值。
  • getInfoByColorBytes用于从RGBA整数颜色值得到颜色的详细说明。格式为“RGBA、Byte、#rrggbbaa、hsl、Pos”,即分别为“归一化的RGBA值、各分量的字节值、十六进制表示的颜色值、hsl格式的颜色值”。如“RGBA(0.357, 0.106, 0.427, 0.875), Byte(91, 27, 109, 223), #5b1b6ddf, hsl(287, 0.603, 0.267)”。
  • alphaPremultiplieToStraight用于将预乘值,转为直通(Straight)值,即非预乘值。

2.3 Blending类合成模式的算法(10 Blending)

Compositing类合成模式(The Porter Duff Compositing Operators)偏重于原图像绘制到目标(背景)图像时的几何关系,而Blending类合成模式与此不同。Blending类合成模式里有2个关键步骤,先通过一个混合函数(B(Cb, Cs))将源与目标(背景)的颜色进行混合,再将“混合结果”的颜色进行“简单Alpha合成”运算,绘制到目标图像上。

2.3.1 第1步:混合函数处理

摘录——

10. Blending
Blending is the aspect of compositing that calculates the mixing of colors where the source element and backdrop overlap.
Conceptually, the colors in the source element are blended in place with the backdrop. After blending, the modified source element is composited with the backdrop. In practice, this is usually all performed in one step.
The blending calculations must not use pre-multiplied color values.

The "mixing" formula is defined as:
Cm = B(Cb, Cs)

with:
Cm: the result color after blending
B: the formula that does the blending
Cb: the backdrop color
Cs: the source color
The result of the mixing formula must be clamped to the minimum and maximum values of the color range.

中文翻译——

10. 混合
混合是合成的一个方面,在源元素和背景重叠的地方计算颜色的混合。
从概念上讲,源元素的颜色是与背景混合在一起的。混合后,修改后的源元素与背景合成。在实践中,这通常是在一个步骤中完成的。
混合的计算不使用预乘的颜色值。

混合 "公式定义为:
Cm = B(Cb, Cs)

其中:
Cm:混合后的结果颜色
B:进行混合的公式
Cb:背景色
Cs:源色
混合公式的结果必须限制在颜色范围的最小值和最大值中。

除了混合函数的概念外,这一段重点是“混合的计算不使用预乘的颜色值”。即源、背景颜色值均是“非预乘”的,与Html5的画布位图一致。
其次提到对于混合函数的计算结果,需将它限制在颜色范围内,因为有时运算结果会超出范围。对于归一化的浮点颜色值来说,范围是 [0,1] 。

2.3.2 第2步:计算混合结果

摘录——

The result of the mixing function is modulated by the backdrop alpha. A fully opaque backdrop allows the mixing function to be fully realized. A transparent backdrop will cause the final result to be a weighted average between the source color and mixed color with the weight controlled by the backdrop alpha. The value of the new color becomes:
Cr = (1 - αb) x Cs + αb x B(Cb, Cs)

with:
Cr: the result color
B: the formula that does the blending
Cs: the source color
Cb: the backdrop color
αb: the backdrop alpha

中文翻译——

混合功能的结果是由背景的α值来调制的。一个完全不透明的背景允许完全实现混合功能。一个透明的背景会导致最终的结果是源色和混合色之间的加权平均,其权重由背景的alpha控制。新颜色的值变成了。
Cr = (1 - αb) x Cs + αb x B(Cb, Cs)

其中:
Cr:结果颜色
B:进行混合的公式
Cs:源色
Cb:背景色
αb:背景的阿尔法

单看公式,这一部分是比较好懂的,就是使用αb(背景Alpha值)对“B(Cb, Cs)函数运算结果 与 Cs(源颜色)进行Alpha插值”。
但是这一部分的意义不止如此,后面会详细说明。

2.3.3 第3步:使用简单Alpha合成

摘录——

EXAMPLE 12
example of blending with opacity
This example has a red rectangle with a blending mode that is placed on top of a set of green rectangles that have different levels of opacity.

Note how the top rectangle shifts more toward red as the opacity of the backdrop gets smaller.

Note: The following formula gives the color value in the area where the source and backdrop intersects and then composites with the specified Porter Duff compositing formula. For simple alpha blending, the formula thus becomes:

simple alpha compositing:
  co = cs + cb x (1 - αs)
written as non-premultiplied:
  αo x Co = αs x Cs + (1 - αs) x αb x Cb
now substitute the result of blending for Cs:
    αo x Co = αs x ((1 - αb) x Cs + αb x B(Cb, Cs)) + (1 - αs) x αb x Cb
            = αs x (1 - αb) x Cs + αs x αb x B(Cb, Cs) + (1 - αs) x αb x Cb

中文翻译——

示例 12
用不透明度进行混合的例子
这个例子有一个具有混合模式的红色矩形,它被放置在一组具有不同不透明度的绿色矩形的上面。

请注意,当背景的不透明度变小时,顶部的矩形是如何向红色转移的。

注意:下面的公式给出了源和背景相交的区域的颜色值,然后与指定的  Porter Duff  合成公式进行合成。对于简单的 alpha 混合,公式因此变为:

简单的 alpha 合成:
  co = cs + cb x (1 - αs)
写成非预乘:
  αo x Co = αs x Cs + (1 - αs) x αb x Cb
现在用混合后的结果代替Cs:
    αo x Co = αs x ((1 - αb) x Cs + αb x B(Cb, Cs)) + (1 - αs) x αb x Cb
            = αs x (1 - αb) x Cs + αs x αb x B(Cb, Cs) + (1 - αs) x αb x Cb

这一步非常关键,但该文档里写的太简略。导致很容易引起误会。
首先这里未加说明的引用了“预乘版的简单Alpha合成”,随后又简单的演示了“写成非预乘”。缺少详细说明,这对“预乘”概念不熟的人来说,很容易弄晕。
更易引起误会的是“现在用混合后的结果代替Cs”的公式。“混合后的结果”(the result of blending)很容易被误会为第1步“B(Cb, Cs)”函数的计算结果,但它其实是第2步“Cr”(the result color)的值。这里“result”并不是指最终结果,而仅是指第2步的结果。
且最后变换得到的式子还不够实用。B(Cb, Cs)这样的函数运算可移到最右侧,且“(1 - x)”这样的运算可挪到每个子项的右侧,这能使公式看起来更清晰.

现在来对上面公式变换补充说明——

引用预乘版简单的 alpha 合成公式:
  co = cs + cb x (1 - αs)
乘以各自的Alpha后,便是非预乘版公式:
  αo x Co = αs x Cs + (1 - αs) x αb x Cb
现在用混合后的结果(Cr)代替Cs:
  αo x Co = αs x Cr + (1 - αs) x αb x Cb
           = αs x ((1 - αb) x Cs + αb x B(Cb, Cs)) + (1 - αs) x αb x Cb
           = αs x (1 - αb) x Cs + αs x αb x B(Cb, Cs) + (1 - αs) x αb x Cb
           = αs x Cs x (1 - αb) + αb x Cb x (1 - αs) + αs x αb x B(Cb, Cs)

这个最后得到的公式,看起来是非常整齐的。

2.3.4 如何计算Alpha通道

“10. Blending”这一章还遗漏一项关键内容——没有明确提到如何计算Alpha通道。

既然这一章提到了“简单的 alpha 合成”,那么Alpha通道的计算可能是与“简单的 alpha 合成”一样的,即——

αo = αs + αb x (1 – αs)

2.3.5 与Android公式的对比

Android的图像合成模式,用的是PorterDuff.Mode枚举。从名字上来看,它与“Porter Duff”提出的图像合成模式有关。但该枚举在不仅包含了Compositing类合成模式(PorterDuff.Mode枚举值 0~11),且包含了Blending类合成模式(PorterDuff.Mode枚举值 12~17)。
PorterDuff.Mode枚举的说明里,每一个枚举值都给出了公式缩写。
其中与“2.3.3 第3步:使用简单Alpha合成”最相像的,是DARKEN的公式,摘录如下——

[Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)]

Android公式由2个式子组成,前者(Sa + Da - SaDa)是Alpha通道的公式,后者(Sc(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc))是颜色通道的公式。
Android公式使用了不同的缩写,且默认使用了预乘,导致初看起来有些不像,但其实是非常相像的。

先看Alpha通道,进行缩写替换,用“αs”代替“Sa”、“αb”代替“Da”——

Sa + Da - Sa*Da
= αs + αb - αs *αb
= αs + 1 * αb - αs *αb
= αs + αb*(1 - αs)

可发现它与“2.3.4 如何计算Alpha通道”的公式是相同的。

然后看颜色通道的公式。因——

  • Sc就是预乘的源颜色值,故它相当于w3c公式里的“αs x Cs”。
  • Dc就是预乘的目标颜色值,故它相当于w3c公式里的“αb x Cb”。

代入颜色通道的公式后——

Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)
= αs x Cs x (1 - αb) + αb x Cb x (1 - αs) + min(Sc, Dc)

此时便会发现它与“第3步:使用简单Alpha合成”的公式非常相似。摘录——

αo x Co = αs x Cs x (1 - αb) + αb x Cb x (1 - αs) + αs x αb x B(Cb, Cs)

“min(Sc, Dc)”可看作混合函数处理(B(Cb, Cs))。但细节上有些对不上,“min(αb x Cb, αs x Cs)”与“αs x αb x min(Cb, Cs)”的计算结果是不同的。
且Android的Blending类合成模式(PorterDuff.Mode枚举值 12~17)中,很多模式的Alpha通道计算公式不同。如MULTIPLY,Alpha公式为“Sa + Da - Sa*Da”。而w3c的Blending类合成模式,Alpha公式均是相同的。

为了便于分析这些差别,我的 演示页面 对于Blending类合成模式,会分别给出w3c与Android的计算过程。例如对于darken模式,代码为——

	} if ("darken"==compositeMode) {
		msg += "\n\tB(Cb, Cs) = min(Cb, Cs)";
		showOut = true;
		for(i=0; i<COLOR_CHANNELS; ++i) {
			let name = colorNames[i];
			let Cs = fS[i];
			let Cb = fD[i];
			let Cm = Math.min(Cb, Cs);
			fO[i] = As*Cs * (1 - Ab) + Ab*Cb * (1 - As) + As * Ab * Cm;
			msg += `\n${name}m = min(${name}b, ${name}s) = min(${Cb.toFixed(digits)}, ${Cs.toFixed(digits)}) = ${Cm.toFixed(digits)}`;
			msg += `;\t${name}o = ${AsStr}*${Cs.toFixed(digits)} * (1 - ${AbStr}) + ${AbStr}*${Cb.toFixed(digits)} * (1 - ${AsStr})  + ${AsStr} * ${AbStr} * ${Cm.toFixed(digits)} = ${fO[i].toFixed(digits)}`;
		}
		i = ALPHA_INDEX;
		fO[i] = As + Ab * (1 - As);
		msg += `\nAo = As + Ab * (1 - As) = ${AsStr} + ${AbStr} * (1 - ${AsStr}) = ${fO[i].toFixed(digits)}`;
		info = getInfoByColorBytes(fromFloatColor(fO));
		msg += "\nPremultiplie:" + info;
		let fW = alphaPremultiplieToStraight(fO);
		info = getInfoByColorBytes(fromFloatColor(fW));
		msg += "\nOutput     : " + info;
		// Android.
		msg += "\n\tAndroid: [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)]";
		fD = alphaStraightToPremultiplie(fD);
		fS = alphaStraightToPremultiplie(fS);
		for(i=0; i<COLOR_CHANNELS; ++i) {
			let name = colorNames[i];
			let Sc = fS[i];
			let Dc = fD[i];
			fO[i] = Sc*(1 - Da) + Dc*(1 - Sa) + Math.min(Sc, Dc);
			msg += `\n${name}o = S${name}*(1 - Da) + D${name}*(1 - Sa) + min(S${name}, D${name}) = ${fS[i].toFixed(digits)}*(1 - ${DaStr}) + ${fD[i].toFixed(digits)}*(1 - ${SaStr}) + min(${fS[i].toFixed(digits)}, ${fD[i].toFixed(digits)}) = ${fO[i].toFixed(digits)}`;
		}
		i = ALPHA_INDEX;
		fO[i] = Sa + Da - Sa * Da;
		msg += `\nAo = Sa + Da - Sa * Da = ${SaStr} + ${DaStr} - ${SaStr} * ${DaStr} = ${fO[i].toFixed(digits)}`;
	}

三、尾声

源码地址:
https://github.com/zyl910/zhtml5info/blob/master/src/canvas/CanvasComposite.htm

参考文献

posted on 2022-08-07 00:14  zyl910  阅读(747)  评论(0编辑  收藏  举报