Alpha Blend and some Optimizition

2009年2月24日

 

今天发现了我们的UIFramework中Redraw()函数的动作逻辑在绘制Alpha通道图像时有bug。
一般情况下,Alpha Blend 的算法很简单:
C = C0 * ( 1 - A1 ) + C1 * A1      【公式1】
其中C是计算后该像素的color value。C0是该像素在计算前的像素值,C1是Alpha图像上的源像素,我们正想把C1用Alpha透过的方式与C0混合,再画到C0原来的位置上。A1是C1的Alpha通道值,即透明度。
在网上一搜,中文的网页上,全都是这个公式。但现在我遇到了更麻烦的问题。利用【公式1】叠加N个像素所计算出来的像素值是一个绝对像素值,即该像素的Alpha值应该为255(255即100%)。这是因为【公式1】从头开始就没有考虑C0的Alpha值,也就是说,【公式1】默认为第一张图像完全不透明,所以利用【公式1】混合后的图像不再是一个可透明的Alpha图像。
而我现在想叠加N张Alpha图像,并且叠加后依然得到一个Alpha图像。就像是这样:
Pix0,Pix1,Pix2,三张Alpha图像,Pic0在最底层,Pic2在最顶层。
利用【公式1】,我可以得到:
Pix' = Pix0 + Pix1 + Pix2,Pix'是叠加后的效果。
但现在我暂时不知道Pix0的具体值是多少,它是会变的。所以我想要的计算顺序变成:
Pix' = Pix0 + (Pix1 + Pix2)
我需要1和2叠加后依然是Alpha图像,这样我才可以在不久的将来去叠加Pix0,否则Pix0将被完全覆盖。
一个噩耗是,【公式1】不好使了。

有个爱玩跷跷板的小孩说,给他一个支点,他就敢跟地球玩跷跷板。
那么给我一个好用的公式1,我就敢推出更好用的公式2。
现在的情况是这样:
第一次叠加:
C = C0 * ( 1 - A1 ) + C1 * A1
第二次叠加:
C = ( C0 * ( 1 - A1 ) + C1 * A1 )( 1 - A2 ) + C2 * A2
而我需要的第二次叠加的效果应该是这样:
C = C0 * ( 1 - A' ) + C' * A'
其中C'是Pix1和Pix2叠加后的rgb值,而A'则是Pix1和Pix2叠加后的Alpha值。
今天我之所以写出了这篇东西,关键在于我发贱地把第二次叠加的公式打开了,于是我发现:
C = C0 * ( 1 - ( A1 + A2 - A1 * A2 )) + ( C1 * A1 + C2 * A2 - C1 * A1 * A2)
我令 A' = A1 + A2 - A1 * A2,得到新的展开式:
C = C0 * ( 1 - A' ) + (( C1 * A1 + C1 * A2 - C1 * A1 * A2 + C2 * A2 - C1 * A2 ) / A') * A'
哈哈,这么看来,这个嗷嗷长的式子还是比较形似 C = C0 * ( 1 - A' ) + C' * A' 的嘛。
那么C'就理所应当地等于( C1 * A1 + C1 * A2 - C1 * A1 * A2 + C2 * A2 - C1 * A2 ) / A'这玩意了。
把C'整理一下得到【公式2】
A' = A1 + A2 - A1 * A2
C' = C1 + (C2 - C1) * A2 / A'
其中,C1,A1,C2,A2 都是已知量,解这个二元一次方程组则C'和A'可求。
从【公式2】可以看出,当A1等于100%时,计算rgb值的C'的公式即为:
C' = C1 * ( 1 - A2 ) + C2 * A2
而这跟【公式1】是一致的。

这个公式已经被写入俺们滴UIFramework,并且经测试为正确结论。

下午我兴致勃勃滴跟老大说起这个bug,老大想了想说,啊,还真是有这个问题,以前忽略了,上网搜一下看看老外是怎么解决的。于是我们在google上随便点了几个靠前的关于Alpha Blend的网页,让我沮丧的是,有个paper上得出了跟我们相同的结论,唉,可惜俺们是今天上午才得出的,要不我也写个paper啊。
该paper上还列出了一些优化的算法。其中一个比较究极,大概是这样:
公式中充满了乘法,这是万恶的。因为这个算法是用来画像素的,想象一下800x600的分辨率,按Alpha透过方式刷新一次屏幕需要执行48万次该算法,因此该算法中每减少一次乘法,对于提高性能的意义都很重大。
捡简单的打个比方,就说【公式1】吧,C = C0 * ( 1 - A1 ) + C1 * A1,rgb三种颜色每种算一次,总共就有6次乘法。不过,其中C1和A1是已知的,每张图像都可以提前把自己的(C1 * A1)计算出来写成一张表,在每次Alpha混合时查表取值。这样,每张图像只需要一次计算生成该表,即可被无数次使用,而不必在每次混合时计算。这样,就把每个像素的Alpha混合从6次乘法减少到3次。该paper到此结束。
现在剩下的三次乘法计算分别是:
C0r' = (C0r * A1) >> 8
C0g' = (C0g * A1) >> 8
C0b' = (C0b * A1) >> 8
由于C0和A1的对应关系事先我们不知道,所以这一顿乘法是免不了的。我在回家的路上一直在想,能不能把rgb(C0 * A1)这三次乘法再简化呢?因为rgb通常是保存在一个16bit(565)或32bit(888)的内存中(我们的UIFramework只考虑32bit的情况),可看成一个无符号整数,能否利用Crgb * A1这一次32bit的乘法计算来优化算法呢?哪怕只减少一次乘法也行。在马上要下车的时候我终于想出了一个貌似可行的办法。吼吼。
利用32bit乘法的优化算法步骤如下:
1,我把C0的RGB这三个字节按顺序放在一个32位无符号整型数的低24位,这样最高8位是0,用C0rgb表示(其实它们本来就是这么放的,我只需要把C0的A0位置清零)。
2,用这个整型数乘以A1,由于A1是一个8bit无符号整数,因此C0rgb * A1得到一个32bit无符号整型数C0rgb'。
3,根据乘法规则,对与C0':
最低的8位只受C0r * A1的影响,
第二个8位受C0r * A1 和 C0g * A1的影响,
第三个8位受C0g * A1 和 C0b * A1的影响,
最高的8位只受C0b * A1的影响。
4,可见C0g*A1对结果的影响最大,因此我将C0g*A1求出。此时已经有两次乘法了。
5,C0g*A1是一个16bit的整数,我将它左移8bit,让这个结果正好等于C0g在C0rgb'中的影响范围。令g' = (C0g*A1) << 8
6,此时:
C0r' = ((C0rgb' - g') & 0x0000FFFF) >> 8 (取低16位,再右移8bit)
C0g' = g' >> 16
C0b' = (C0rgb' - g') >> 24
终于圆满了。

这样,利用【公式1】对一个像素进行一次Alpha混合计算的乘法被减少到两次。其他只是几步加法和移位运算,比起原始公式6步乘法,还是有比较大的进步。在我们的开发板上,估计大概每40至70毫秒之内将减少192万次乘法运算。不过这只是对【公式1】的优化。【公式2】看上去要比这个复杂一些,并且还要计算新的Alpha值。
但是我刚才突然想到了一个好的办法(就在写公式推导那段的时候),不过有理论误差。那就是像素的新rgb色彩值按照【公式1】来计算,而新Alpha值则选择A1和A2之中较大的那个。因为当A1和A2的取值范围都是[0,1)时,则 A' = A1 + A2 - A1 * A2 这个表达式使得A'总是趋近于A1和A2之中较大的那个。当我把A'取为较大的那个时,【公式2】就可以简化来简化去,最终趋近于【公式1】了。但这么做理论上就有误差,老大是肯定不会允许我这么写的。
解释一下为什么A1和A2的取值是[0,1)而不是[0,1]:这是因为Alpha的取值是0-255,255时代表100%,而上面的每个公式都没有把255当做分母,而是用256来代替(右移8bit和除以255,让你选的话你选啥?)。所以虽然理论上A1和A2的取值应该从0/255至255/255,但现实总是残酷的,我们通常都把它们视为0/256至255/256,就是如此。如果你硬说这也是理论误差,那我也没辙。而且上述算法中的除法全部是整型除法,所得之数本来就比真实数据要小,而因为这两个原因,A' = A1 + A2 - A1 * A2这个公式算起来,倒有使A'正好等于1的可能,这样两个误差正负拉锯,反而可能使实际误差更小一些,哈哈。啊对,A'归根结底是不能大于等于1的,因为8bit的Alpha通道值不可能大于255,因此如果Alpha值大于255了,就把它圆整为255。

累死我了。写了这么长时间,还不知道是不是又已经有一堆paper了,还是赶紧睡觉吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值