预乘 alpha 合成

4

我正在尝试实现预乘alpha混合。在这个页面上:什么是颜色混合?,他们解释了标准的alpha混合,但没有针对预乘值进行解释。

Alpha混合:(源 × 混合.源Alpha)+ (目标 × 混合.反向源Alpha)

根据公式,它转换为:

  a = ((srcA * srcA) >> 8) + ((tgtA * (255 - srcA)) >> 8);
  r = ((srcR * srcA) >> 8) + ((tgtR * (255 - srcA)) >> 8);
  g = ((srcG * srcA) >> 8) + ((tgtG * (255 - srcA)) >> 8);
  b = ((srcB * srcA) >> 8) + ((tgtB * (255 - srcA)) >> 8);
                         

显然它有效...

现在我该如何将其转换为处理预乘值?

  a = ((srcA)) + ((tgtA * (255 - srcA)) >> 8);
  r = ((srcR)) + ((tgtR * (255 - srcA)) >> 8);
  g = ((srcG)) + ((tgtG * (255 - srcA)) >> 8);
  b = ((srcB)) + ((tgtB * (255 - srcA)) >> 8);
                          

由于已经进行了预乘,我在第一个术语中放弃了乘法...对吧!? 但结果介于 alpha 混合和加性混合之间,更倾向于加性。最终看起来并不太混合。这可能是错误的,因为它应该看起来完全像经典的 alpha 混合;或者这是预期的行为吗?

谢谢。


我认为第一个代码块中的 srcA * srcA 应该只是 srcA。您不需要将 alpha 乘以自身。 - user180326
4个回答

6

预乘的原因在于它实际上会使目标图像的 alpha 值平方后再将源图像添加到目标图像中。

例如,如果没有进行预乘,我们得到的源图像数据如下:

srcA = origA
srcR = origR
srcG = origG
srcB = origB

当应用于目标时,我们得到了以下结果图像:

a = ((srcA * srcA) >> 8) + ((tgtA * (255 - srcA)) >> 8)
r = ((srcR * srcA) >> 8) + ((tgtR * (255 - srcA)) >> 8)
g = ((srcG * srcA) >> 8) + ((tgtG * (255 - srcA)) >> 8)
b = ((srcB * srcA) >> 8) + ((tgtB * (255 - srcA)) >> 8)

扩展开来,我们得到:

a = ((origA * origA) >> 8) + ((tgtA * (255 - origA)) >> 8)
r = ((origR * origA) >> 8) + ((tgtR * (255 - origA)) >> 8)
g = ((origG * origA) >> 8) + ((tgtG * (255 - origA)) >> 8)
b = ((origB * origA) >> 8) + ((tgtB * (255 - origA)) >> 8)

毫不意外...

现在来看预乘的源图像数据:

srcA = (origA * origA) >> 8
srcR = (origR * origA) >> 8
srcG = (origG * origA) >> 8
srcB = (origB * origA) >> 8

应用于目标时,它是:

a = (srcA >> 8) + ((tgtA * (255 - srcA)) >> 8);
r = (srcR >> 8) + ((tgtR * (255 - srcA)) >> 8);
g = (srcG >> 8) + ((tgtG * (255 - srcA)) >> 8);
b = (srcB >> 8) + ((tgtB * (255 - srcA)) >> 8);

好的,我们已经知道这个了,但是如果我们扩展一下,你会看到其中的区别:

a = (origA * origA) >> 8 + ((tgtA * (255 – ((origA * origA) >> 8))) >> 8);
r = (origR * origA) >> 8 + ((tgtR * (255 - ((origA * origA) >> 8))) >> 8);
g = (origG * origA) >> 8 + ((tgtG * (255 – ((origA * origA) >> 8))) >> 8);
b = (origB * origA) >> 8 + ((tgtB * (255 – ((origA * origA) >> 8))) >> 8);

与非预乘扩展相比,可以进行如下对比:
a = ((origA * origA) >> 8) + ((tgtA * (255 - origA)) >> 8)
r = ((origR * origA) >> 8) + ((tgtR * (255 - origA)) >> 8)
g = ((origG * origA) >> 8) + ((tgtG * (255 - origA)) >> 8)
b = ((origB * origA) >> 8) + ((tgtB * (255 - origA)) >> 8)

您可以立即看到,当应用origA值到目标时,我们会对其进行平方处理,这意味着更多的目标将传递到结果颜色值中。

通过平方处理,您表达了这样一种意思:我希望更多的目标能够传递过来。

这就是为什么预乘可以消除透明块周围的色带数量,因为那些Alpha值较低的像素会比不预乘时获得更多的目标像素,而这是按指数比例发生的。

希望这能让您明白。


1
你好!感谢您提供的精彩解释,现在我真正理解了该方法的内部工作原理。实际上,我之前忘记标记答案了,因为我对我的结果感到满意;是的,正如您所说,它神奇地处理了带状问题,人们可以在我之前发布的两张屏幕截图中看到。您的回答解决了所有问题,因此我认为它值得成为答案。尽管如此,我仍然鼓励大家阅读整个帖子...同时,感谢其他任何有帮助的人 :-))) - aybe

2

你所遇到的问题取决于你是否在预乘过程中将源alpha值与其本身相乘。如果是这样,那么你在目标乘法中使用的srcA就是实际源Alpha的平方,因此需要对该计算取平方根:

originalSrcA = Math.Sqrt(srcA);
a = ((srcA)) + ((tgtA * (255 - originalSrcA)) >> 8);
r = ((srcR)) + ((tgtR * (255 - originalSrcA)) >> 8);
g = ((srcG)) + ((tgtG * (255 - originalSrcA)) >> 8);
b = ((srcB)) + ((tgtB * (255 - originalSrcA)) >> 8);

如果您还没有进行自身预乘(我认为这更有可能),则需要进行自身乘法才能获得与工作结果相同的结果:

a = ((srcA * srcA) >> 8) + ((tgtA * (255 - srcA)) >> 8);
r = ((srcR)) + ((tgtR * (255 - srcA)) >> 8);
g = ((srcG)) + ((tgtG * (255 - srcA)) >> 8);
b = ((srcB)) + ((tgtB * (255 - srcA)) >> 8);

1
一个猜测:你是否在改变混合的数量(srcA)?如果是这样,你必须重新计算位图中的预乘 alpha 值。如果不这样做,你将得到一种类似于加法的效果,这可能就是你所描述的情况。

0

经过多次尝试,我得出了以下结论:

我也预先乘以了 alpha 通道,并保留了我最初发布的第二个公式;这是我得到的最佳结果。

在我找到的最好的文档中,当预先乘以时,它谈到了丑陋的边框消失的问题: http://www.td-grafik.de/ext/xfrog/alpha/index.htmlhttp://blogs.msdn.com/b/shawnhar/archive/2010/04/08/premultiplied-alpha-in-xna-game-studio-4-0.aspx

嗯,传统的 alpha 混合并不是一个真正的参考,我想现在我是正确的,因为它看起来比普通的 alpha 混合更好。

但说实话,我并不完全理解那些内容,(看起来)它能够工作;另一个谜团...

以下是让我认为它没问题的原因:

左:alpha 混合; 右:预乘

非常感谢大家的帮助!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接