预乘 alpha 合成

Ayb*_*ybe 4 c# graphics colors

我正在尝试实现预乘 alpha 混合。在此页面上:什么是颜色混合?,它们确实解释了标准 alpha 混合,但不解释预乘值。

Alpha 混合 : (source × Blend.SourceAlpha) + (destination × Blend.InvSourceAlpha)

根据公式,它转化为:

  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);
Run Code Online (Sandbox Code Playgroud)

它有效,显然......

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

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

由于它已被预乘,我丢弃了第一项中的乘法......对吧!?但结果是在 alpha 混合和加法混合之间,更倾向于加法。最后它看起来并不太混合。这可能是错误的,因为它看起来应该与经典的 alpha 混合完全一样;或者这是预期的行为?

谢谢你。

Aar*_*oyd 5

预乘工作的原因是因为它实际上在将源图像添加到目标之前最终将目标的 alpha 平方

例如。没有预先乘法,我们得到了源图像数据:

srcA = origA
srcR = origR
srcG = origG
srcB = origB
Run Code Online (Sandbox Code Playgroud)

当应用于目标时,我们为结果图像得到了这个:

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)
Run Code Online (Sandbox Code Playgroud)

展开我们得到:

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)
Run Code Online (Sandbox Code Playgroud)

那里没有惊喜...

现在对于预乘的源图像数据,我们得到:

srcA = (origA * origA) >> 8
srcR = (origR * origA) >> 8
srcG = (origG * origA) >> 8
srcB = (origB * origA) >> 8
Run Code Online (Sandbox Code Playgroud)

其中,当应用于目标时是:

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);
Run Code Online (Sandbox Code Playgroud)

好的,所以我们知道这一点,但是如果我们将其展开,您将看到不同之处:

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);
Run Code Online (Sandbox Code Playgroud)

将其与非预乘扩展进行比较:

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)
Run Code Online (Sandbox Code Playgroud)

您可以立即看到我们在将 origA 值应用于目标时对其进行平方,这意味着更多的目标将通过结果颜色值。

通过平方它你是说,我希望更多的目标通过。

这就是为什么在预乘时它会去除透明块周围的条带数量,因为那些具有较低 Alpha 值的像素获得的目标像素多于没有预乘时的目标像素,并且这种情况以指数方式发生。

我希望这能解决问题。