开发者

Pre-multiplied alpha compositing

开发者 https://www.devze.com 2023-02-07 19:31 出处:网络
I am trying to implement pre-multiplied alpha blending. On this page : What Is Color Blending?, they do explain standard alpha blending but not for pre-multiplied values.

I am trying to implement pre-multiplied alpha blending. On this page : What Is Color Blending?, they do explain standard alpha blending but not for pre-multiplied values.

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

According formula, it translates to this :

  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);

It works, obviously ...

Now how do I co开发者_JS百科nvert this to process pre-multiplied values ?

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

Since it has been pre-multiplied, I discard the multiplication in the first term ... right !? But the result is between alpha blending and additive blending, tending more to additive. In the end it doesn't really look too blended. It's probably wrong since it should look exactly like classic alpha blending; or is this expected behavior ?

Thank you.


The reason pre-multiplying works is because it actually ends up squaring the alpha for the target before it adds the source image to the target

eg. Without pre multiplying, we get this for the source image data:

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

And we get this for the resulting image when applied to a target:

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)

Expanding this out we get:

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)

No surprises there...

Now for the pre-multiplied source image data we get:

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

Which, when applied to a target is:

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);

Ok, so we know this, but if we expand this out you will see the difference:

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);

Compare that to the NON Pre-Multiplied expansion of:

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)

And straight away you can see that we are squaring the origA value when applying it to the target, this means that more of the target will come through to the resulting color values.

By squaring it you are saying, I want more of the target to come through.

This is why when pre-multiplying it removes the amount of banding around transparent blocks, because those pixels with lower Alpha values get more of the target pixels than you would if you didn't pre-multiply and this happens on an exponential scale.

I hope this clears it up.


The problem you've got is depends on whether or not you pre-multiplied your source alpha value by itself as part of your pre-multiplication. If you did, then the srcA you're using in the target multiplications is the square of the real source Alpha, so you need to take the square root for that calculation:

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);

If you haven't pre-multiplied by itself (which I think is more likely), you will need to multiply by itself to get the same result as the working one:

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);


A wild guess: are you varying the amount of blending (srcA)? If so you must recalculate your pre-multiplied alpha values in the bitmap. If you don't, you will get an add-like effect which could be what you describe.


After many tries, here's what I came up with :

I pre-multiply the alpha channel as well and I keep my 2nd formula I posted first; it's the best result I got.

In the best docs I found about it talks of the ugly borders disappearing when pre-multiplying : http://www.td-grafik.de/ext/xfrog/alpha/index.html and http://blogs.msdn.com/b/shawnhar/archive/2010/04/08/premultiplied-alpha-in-xna-game-studio-4-0.aspx

Well, conventional alpha blending was not really a reference to compare with, I guess I am right now since it looks better than normal alpha-blending.

But to be honest, I don't really understand 100% of that, (looks like) it works; another mystery ...

Here's what makes me think it's okay;

Left : alpha blending; Right : pre-multiplied

Pre-multiplied alpha compositing

Thank you all for your help !

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号