Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pre-multiplied alpha compositing

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 convert 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.

like image 782
aybe Avatar asked Feb 25 '23 05:02

aybe


1 Answers

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.

like image 127
Aaron Murgatroyd Avatar answered Mar 08 '23 13:03

Aaron Murgatroyd