1

I have 2 pixels in B8G8R8A8 (32) format. Both pixels (top and bottom) has transparency (Alpha channel < 255 )

What is the way (formula) to overlay top pixel on the bottom one ? (without using 3rd parties).

I tried to do something like this

struct FColor
{
public:
    // Variables.
#if PLATFORM_LITTLE_ENDIAN
    #ifdef _MSC_VER
        // Win32 x86
        union { struct{ uint8 B,G,R,A; }; uint32 AlignmentDummy; };
    #else
        // Linux x86, etc
        uint8 B GCC_ALIGN(4);
        uint8 G,R,A;
    #endif
#else // PLATFORM_LITTLE_ENDIAN
    union { struct{ uint8 A,R,G,B; }; uint32 AlignmentDummy; };
#endif
//...
};

FORCEINLINE FColor AlphaBlendColors(FColor pixel1, FColor pixel2)
{
    FColor blendedColor;
    //Calculate new Alpha:
    uint8 newAlpha = 0;
    newAlpha = pixel1.A + pixel2.A * (255 - pixel1.A);

    //get FColor as uint32
    uint32 colora = pixel1.DWColor();
    uint32 colorb = pixel2.DWColor();

    uint32 rb1 = ((0x100 - newAlpha) * (colora & 0xFF00FF)) >> 8;
    uint32 rb2 = (newAlpha * (colorb & 0xFF00FF)) >> 8;
    uint32 g1 = ((0x100 - newAlpha) * (colora & 0x00FF00)) >> 8;
    uint32 g2 = (newAlpha * (colorb & 0x00FF00)) >> 8;
    blendedColor = FColor(((rb1 | rb2) & 0xFF00FF) + ((g1 | g2) & 0x00FF00));


    blendedColor.A = newAlpha;
    return blendedColor;
}

But the result is far not what I want :-)

I looked for some Alpha blending formulas (I did never understand how would I calculate a new alpha of the overlay) -> perhaps I was going in a wrong direction ?

Edit:

Changing the newAlpha to newAlpha = FMath::Min(pixel1.A + pixel2.A, 255); Actually gives a much better result, but is it right to calculate it like this ? Am I missing something here?

Working Example Based On Accepted Answer)

   FORCEINLINE FColor AlphaBlendColors(FColor BottomPixel, FColor TopPixel)
{
    FColor blendedColor;
    //Calculate new Alpha:
    float normA1 = 0.003921568627451f * (TopPixel.A);
    float normA2 = 0.003921568627451f * (BottomPixel.A);

    uint8 newAlpha = (uint8)((normA1 + normA2 * (1.0f - normA1)) * 255.0f);

    if (newAlpha == 0)
    {
        return FColor(0,0,0,0);
    }

    //Going By Straight Alpha formula
    float dstCoef = normA2 * (1.0f - normA1);
    float multiplier = 255.0f / float(newAlpha);

    blendedColor.R = (uint8)((TopPixel.R * normA1 + BottomPixel.R * dstCoef) * multiplier);
    blendedColor.G = (uint8)((TopPixel.G * normA1 + BottomPixel.G * dstCoef) * multiplier);
    blendedColor.B = (uint8)((TopPixel.B * normA1 + BottomPixel.B * dstCoef) * multiplier);

    blendedColor.A = newAlpha;

    return blendedColor;
}
Coldsteel48
  • 3,482
  • 4
  • 26
  • 43
  • 1
    Have you tried using a debugger to solve your issue? From the first inspection, the line `newAlpha = pixel1.A + pixel2.A * (255 - pixel1.A);` looks unsound (e.g. having alpha 100 and 255 would give you `newAlpha = 100 + 255 * (255 - 100) = 39625`) – Zdeněk Jelínek Aug 18 '17 at 08:46
  • @ZdeněkJelínek No, sorry, I haven't tried to debug values (In reality it is not only 2 pixels - I am trying to blend heavy textures), and yes you are right (silly me tried to adopt a float alpha calculation and forgot about the overflow) – Coldsteel48 Aug 18 '17 at 08:52
  • If you can only work with heavy workloads, consider writing a unit test you can then debug that will test on simple-enough data to reproduce the issue. – Zdeněk Jelínek Aug 18 '17 at 08:58

1 Answers1

1

Start by assuming that there is a third pixel below that happens to be opaque.

For the further notations, I will assume that alpha values are in [0,1].

Given: three pixels with the first one being on top, colors c_1, c_2, c_3, alpha values a_1, a_2, a_3 = 1

Then the resulting alpha value is obviously 1 and the color is

(a_1)*c_1 + (1-a_1)(*a_2)*c_2 + (1-a_1)*(1-a_2)*c_3

Now, we want to find some values c_k, a_k so that the formula above equates

(a_k)*c_k + (1-a_k)*c_3

We can solve this in two steps:

(1-a_k) = (1-a_1)*(1-a_2)
->
a_k = 1-(1-a_1)*(1-a_2)

and

(a_k)*c_k = (a_1)*c_1 + (1-a_1)(*a_2)*c_2
->
c_k = [(a_1)*c_1 + (1-a_1)(*a_2)*c_2] / a_k

Use those formulas (with a different range for your alpha values) and you get your desired color.

(Don't forget to catch a_k = 0)

edit: Explanation of the third pixel:

When you use your two pixels in any way, that is doing something that results it in being used to display something, they will be put over some other existing color that is opaque. For example, this might be the background color, but it could also be some color that is the result of applying many more transparent pixels on some background color.

What I now do to combine your two colors is to find a color that behaves just like those two colors. That is, putting it on top of some opaque color should result in the same as putting the original two colors on top of it. This is what I demand of the new color, resulting in the formula I use.

The formula is nothing than the result of applying two colors in succession on the third one.

Aziuth
  • 3,652
  • 3
  • 18
  • 36
  • Hey, emm I didn't get the point of third pixel could you please add some theoretical explanation ? – Coldsteel48 Aug 18 '17 at 09:16
  • That starts to get interesting - I still don't fully get the third :-( I am not about to render these pixels actually - I want to merge 2 textures (they both has gradient alpha channels) in 1 texture to cull of pixel overdraw on main thread. Or is the third pixel still counts. – Coldsteel48 Aug 18 '17 at 09:31
  • I think i will give it a try tomorrow, to tired for now. – Coldsteel48 Aug 18 '17 at 09:36
  • Again, this is nothing but creating a color that behaves like the other two colors combined. I define behavior by interaction with other colors. I simply stated a formula that will create a transparent colored foil that will have the same effect when you look through it as two other foils have when combined. I simply used the formula of combining them and the formula of the new foil, with it's values unknown, and then equated those formulas. – Aziuth Aug 18 '17 at 10:00
  • Is it the formula that you are using ? https://wikimedia.org/api/rest_v1/media/math/render/svg/a92cffa85057fafdd90b31202ce44690958b8cb9 – Coldsteel48 Aug 18 '17 at 10:11
  • @ColdSteel Yes, this is identical with mine. In my variable names, the first line of the formulas you linked is `a_k = a_1 + a_2*(1-a_1)` which is the same as `a_k = 1-(1-a_1)*(1-a_2)` if one multiplies things out. But notice that all you really need here is the standard blending formula: the transparent color c_t, a_t over an opaque color c_o results in the new opaque color `a_t*c_t + (1-a_t)*c_o`. All other formulas are derived from that one. That is the one important standard of alpha values. – Aziuth Aug 18 '17 at 10:31
  • Yes I think this is my missing link - after blend operation it suppose to be an opaque color - while I want it to retain transparency. maybe to blend an opaque color + transparent color and then apply newAlpha is the way to go ? Or to blend colors as opaque and then apply alpha ? Interesting problem I suppose. – Coldsteel48 Aug 18 '17 at 22:58
  • Thank you for opening my eyes mate :) Update the question with working code – Coldsteel48 Aug 19 '17 at 06:59
  • If you are doing a "translucent over translucent" type of blending, take a look at this [answer](https://stackoverflow.com/questions/43786855/rgb-value-of-a-pixel-combined-from-2-overlaying-pixels/43788181#43788181). – Joseph Artsimovich Aug 20 '17 at 07:35