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.
<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.
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
Thank you all for your help !
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.