1、 背景理论
1.1 对于alpha混合的理解
所谓 Alpha-Blending,其实就是按照“Alpha”混合向量的值来混合源像素和目标像素。
简单地说这是一种让物件产生透明感的技术。屏幕上显示的物件,每个像素中有红R、绿G、蓝B三组数值。若环境中允许像素能拥有一组α值,我们就称它拥有一个α通道。α值的内容,是记载像素的透明度。这样一来使得每一个物件都可以拥有不同的透明程度。
颜色在本质上是光的产物,如果把透明度理解为玻璃的透光性,则一切就变得非常简单。例如一个 alpha = 0.2 的颜色,就可以将其想像为透光率为 80% 的彩色玻璃。我们透过这块玻璃看去,由于 80% 的光都透过了,因此留下来的颜色只剩 20%,即所谓 0.2 的 alpha。现在我们来做一个混合:将 alpha 为 0.2 和 0.6 的颜色进行叠加。这时,我们有了两块玻璃,一块透光率为 80%,另一块为 40%。一道光束穿过,经过 80% 透光率的玻璃时,光线强度剩下 80%,再经过 40% 透光率的玻璃时,光线进一步被削弱,只剩下 80%
* 40% = 32%。这意味着有 32% 的透明性,即 alpha
= 0.68。总结上面的算法,我们可以得出:
1.2 Alpha混合与Porter-Duff混合
对于alpha混合,最简单的说法是Src*alpha
+ Dst*(1-alpha),首先,一个大前提,以下porter/duff公式是针对预乘(premultiplied)后的结果的。什么是预乘?假设一个像素点,用RGBA 四个分量来表示,记做(R,G,B,A),那预乘后的像素就是(R*A,G*A,B*A,
A),这里A的取值范围是[0,1]。所以,预乘就是每个颜色分量都与该像素的alpha分量预先相乘。可以发现,对于一个没有透明度,或者说透明度为1 的像素来说,预乘不预乘结果都是一样的。
为什么要用预乘方式来表示像素?主要是这样会使公式更简单。而且实际上,在实际运算过程中,使用预乘像素进行运算在某些情况下效率会更高。但预乘会 对运算精度有一定影响。关于预乘的种种内容,这里不讨论,有兴趣可以自行研究。
开始讨论porter/duff混合公式之前,先定义几个符号
C - 表示像素的颜色,即(RGBA)的RGB部分,C是color的缩写
A - 表示像素的透明度,A即alpha
s - 表示两个混合像素的源像素,s即source
d - 表示两个混合像素的目标像素,d即destination
r - 表示两个像素混合后的结果,r即result
F - 表示作用于C或A上的因子,F即factor
有了这个符号之后,可以开始用它们来描述porter/duff混合公式了,如下,
Cr = Cs*Fs + Cd*Fd
Ar = As*Fs + Ad*Fd
对于12种不同的混合方式,仅仅是Fs与Fd的取值不同,比如最为常见的SRC_OVER混合方式,
Fs = 1, Fd = (1-As)
所以
Cr = Cs + Cd*(1-As)
Ar = As + Ad*(1-As)
我们之前提到的Src*alpha + Dst*(1-alpha)其实就是SRC_OVER的一种特殊情况,即目标像素的alpha为1的情况。你可能决定这两个公式长得还有点区别,别忘了 porter/duff公式是以预乘方式来讨论的,展开成非预乘之后就是
Cr = Cs*As + Cd*Ad*(1-As)
如果Ad = 1,那就和之前的公式一样了。对于通常的GUI应用,因为最终的背景一定是alpha为1的,所以使用这个公式就足够了。但是,对于OSD(OnScreen-Display)与video叠 加的情况,由于OSD的最终背景很可能是半透明的,如果再使用这个简化过的公式,可能就会造成结果不正确了。这种时候 就必须按照porter/duff公式严格计算。
下面是12种porter/duff混合的具体定义:
1. CLEAR
Fs = Fd = 0
2. SRC
Fs = 1 Fd = 0
3. DST
Fs = 0 Fd = 1
4. SRC OVER
Fs = 1 Fd = (1-As)
5. DST OVER
Fs = (1-Ad) Fd = 1
6. SRC IN
Fs = Ad Fd = 0
7. DST IN
Fs = 0 Fd = As
8. SRC OUT
Fs = (1-Ad) Fd = 0
9. DST OUT
Fs = 0 Fd = (1-As)
10.SRC ATOP
Fs = Ad Fd = (1-As)
11.DST ATOP
Fs = (1-Ad) Fd = As
12.XOR
Fs = (1-Ad) Fd = (1-As)
1.3 中间件原先alpha混合的关键代码
if(eDstFormat == EM_UDIOSG_PIXEL_FORMAT_ARGB_8888)
{
UINT tmpDst, tmpSrc, *dstAddr, *srcAddr;
UCHAR dR, dG, dB, dA, sR, sG, sB, sA, alpha;
for (ii = 0; ii < h; ++ii)
{
dstAddr = (UINT*)pucDst;
srcAddr = (UINT*)pucSrc;
for (jj = 0; jj < w; ++jj)
{
tmpSrc = *srcAddr;
alpha = (UCHAR)(tmpSrc>>24);
if(alpha != 0)
{
if(blending_factor != 0xFF)
{
alpha = (alpha * blending_factor) >>8;
//全局Alpha参与后,如果源的Alpha位为0,则不拷贝
if(alpha == 0)
{
continue;
}
}
sB = (UCHAR)tmpSrc;
sG = (UCHAR)(tmpSrc>>8);
sR = (UCHAR)(tmpSrc>>16);
sA = alpha;
tmpDst = *dstAddr;
dB = (UCHAR)tmpDst;
dG = (UCHAR)(tmpDst>>8);
dR = (UCHAR)(tmpDst>>16);
dA = (UCHAR)(tmpDst>>24);
/** 多层Alpha混合公式 */
dA = sA + dA - ((sA * dA)/0xff);
dR = dR + (sR - dR) * sA /dA;
dG = dG + (sG - dG) * sA /dA;
dB = dB + (sB - dB) * sA /dA;
*dstAddr = ((UINT)dA<<24)|((UINT)dR<<16)|((USHORT)dG<<8)|dB;
}
++dstAddr;
++srcAddr;
}
pucDst += dstPitch;
pucSrc += srcPitch;
}
}
1.4 OpenGL的alpha混合计算方法
OpenGL 会把源颜色和目标颜色各自取出,并乘以一个系数(源颜色乘以的系数称为“源因子”,目标颜色乘以的系数称为“目标因子”),然后相加,这样就得到了新的颜 色。(也可以不是相加,新版本的OpenGL可以设置运算方式,包括加、减、取两者中较大的、取两者中较小的、逻辑运算等,但我们这里为了简单起见,不讨 论这个了)
下面用数学公式来表达一下这个运算方式。假设源颜色的四个分量(指红色,绿色,蓝色,alpha值)是(Rs,
Gs, Bs, As),目标颜色的四个分量是(Rd, Gd, Bd, Ad),又设源因子为(Sr,
Sg, Sb, Sa),目标因子为(Dr, Dg, Db, Da)。则混合产生的新颜色可以表示为:
(Rs*Sr+Rd*Dr, Gs*Sg+Gd*Dg, Bs*Sb+Bd*Db, As*Sa+Ad*Da)
当然了,如果颜色的某一分量超过了1.0,则它会被自动截取为1.0,不需要考虑越界的问题。
源因子和目标因子是可以通过glBlendFunc函数来进行设置的。glBlendFunc有两个参数,前者表示源因子,后者表示目标因子。这两个参数可以是多种值,下面介绍比较常用的几种。
GL_ZERO: 表示使用0.0作为因子,实际上相当于不使用这种颜色参与混合运算。
GL_ONE: 表示使用1.0作为因子,实际上相当于完全的使用了这种颜色参与混合运算。
GL_SRC_ALPHA:表示使用源颜色的alpha值来作为因子。
GL_DST_ALPHA:表示使用目标颜色的alpha值来作为因子。
GL_ONE_MINUS_SRC_ALPHA:表示用1.0减去源颜色的alpha值来作为因子。
GL_ONE_MINUS_DST_ALPHA:表示用1.0减去目标颜色的alpha值来作为因子。
除 此以外,还有GL_SRC_COLOR(把源颜色的四个分量分别作为因子的四个分量)、GL_ONE_MINUS_SRC_COLOR、 GL_DST_COLOR、GL_ONE_MINUS_DST_COLOR等,前两个在OpenGL旧版本中只能用于设置目标因子,后两个在OpenGL 旧版本中只能用于设置源因子。新版本的OpenGL则没有这个限制,并且支持新的GL_CONST_COLOR(设定一种常数颜色,将其四个分量分别作为 因子的四个分量)、GL_ONE_MINUS_CONST_COLOR、GL_CONST_ALPHA、 GL_ONE_MINUS_CONST_ALPHA。另外还有GL_SRC_ALPHA_SATURATE。新版本的OpenGL还允许颜色的alpha 值和RGB值采用不同的混合因子。
举例来说:
如果设置了glBlendFunc(GL_ONE, GL_ZERO);,则表示完全使用源颜色,完全不使用目标颜色,因此画面效果和不使用混合的时候一致(当然效率可能会低一点点)。如果没有设置源因子和目标因子,则默认情况就是这样的设置。
如果设置了glBlendFunc(GL_ZERO, GL_ONE);,则表示完全不使用源颜色,因此无论你想画什么,最后都不会被画上去了。(但这并不是说这样设置就没有用,有些时候可能有特殊用途)
如 果设置了glBlendFunc(GL_SRC_ALPHA,
GL_ONE_MINUS_SRC_ALPHA);,则表示源颜色乘以自身的alpha 值,目标颜色乘以1.0减去源颜色的alpha值,这样一来,源颜色的alpha值越大,则产生的新颜色中源颜色所占比例就越大,而目标颜色所占比例则减 小。这种情况下,我们可以简单的将源颜色的alpha值理解为“不透明度”。这也是混合时最常用的方式。
如果设置了glBlendFunc(GL_ONE, GL_ONE);,则表示完全使用源颜色和目标颜色,最终的颜色实际上就是两种颜色的简单相加。例如红色(1,
0, 0)和绿色(0, 1, 0)相加得到(1,
1, 0),结果为黄色。
1.5 Qt关于预乘alpha混合算法的代码:
1)对RGB分量进行预乘操作
unsigned premultipliedARGBFromColor(const Color& color)
{
unsigned pixelColor;
if (unsigned alpha = color.alpha()) {
pixelColor = alpha << 24 |
((color.red() * alpha + 254) / 255) << 16 |
((color.green() * alpha + 254) / 255) << 8 |
((color.blue() * alpha + 254) / 255);
} else
pixelColor = color.rgb();
return pixelColor;
}
2)将预乘后的数据进行非预乘还原
Color colorFromPremultipliedARGB(unsigned pixelColor)
{
RGBA32 rgba;
if (unsigned alpha = (pixelColor & 0xFF000000) >> 24) {
rgba = makeRGBA(((pixelColor & 0x00FF0000) >> 16) * 255 / alpha,
((pixelColor & 0x0000FF00) >> 8) * 255 / alpha,
(pixelColor & 0x000000FF) * 255 / alpha,
alpha);
} else
rgba = pixelColor;
return Color(rgba);
}
3)预乘后的数据采用的混合算法
static inline Color blendFunc(const AnimationBase* anim, const Color& from, const Color& to, double progress)
{
// We need to preserve the state of the valid flag at the end of the animation
if (progress == 1 && !to.isValid())
return Color();
// Contrary to the name, RGBA32 actually stores ARGB, so we can initialize Color directly from premultipliedARGBFromColor().
// Also, premultipliedARGBFromColor() bails on zero alpha, so special-case that.
Color premultFrom = from.alpha() ? premultipliedARGBFromColor(from) : 0;
Color premultTo = to.alpha() ? premultipliedARGBFromColor(to) : 0;
Color premultBlended(blendFunc(anim, premultFrom.red(), premultTo.red(), progress),
blendFunc(anim, premultFrom.green(), premultTo.green(), progress),
blendFunc(anim, premultFrom.blue(), premultTo.blue(), progress),
blendFunc(anim, premultFrom.alpha(), premultTo.alpha(), progress));
return Color(colorFromPremultipliedARGB(premultBlended.rgb()));
}