Spook
Spook

Reputation: 25927

Why IPP Alpha blending returns wrong results for transparent background?

I'm trying to use Intel's IPP library to speed up alpha blending in my project.

I have successfully replaced the call to my function with IPP and the application has sped up around 15-20%, but I've started getting wrong result images.

I figured that IPP (for some reason) wrongly blends images onto fully transparent image.

I managed to implement a small example:

inline void alphaBlend(unsigned char * baseColor, unsigned char * targetColor)
{
    // Old algorithm (R, G, B are floats ranging from 0 to 255, A is a float ranging from 0 to 1)
    // 
    // float newAlpha = (1 - targetColor.A) * baseColor.A + targetColor.A;
    // baseColor.R = ((1 - targetColor.A) * baseColor.A * baseColor.R + targetColor.A * targetColor.R) / newAlpha;
    // baseColor.G = ((1 - targetColor.A) * baseColor.A * baseColor.G + targetColor.A * targetColor.G) / newAlpha;
    // baseColor.B = ((1 - targetColor.A) * baseColor.A * baseColor.B + targetColor.A * targetColor.B) / newAlpha;
    //
    // The code below represents converting the above to unsigned int RGBA ranging from 0 to 255.
    // Instances of A were replaced by (A / 255) and then formulas were transformed so that number
    // of divisions was minimized and operations fit inside unsigned int.
    //
    // Binary shift was introduced as a replacement for division/multiplying by 255 (that is *not* 
    // equivalent, but difference is acceptable)

    unsigned int bA = baseColor[3];
    unsigned int bR = baseColor[2];
    unsigned int bG = baseColor[1];
    unsigned int bB = baseColor[0];

    unsigned int tA = targetColor[3];
    unsigned int tR = targetColor[2];
    unsigned int tG = targetColor[1];
    unsigned int tB = targetColor[0];

    unsigned int a = (((bA + tA) << 8) - tA * bA) >> 8;

    if (a > 0)
    {
        unsigned int divisor = a << 8;

        unsigned int baseAR = bA * bR;
        baseColor[2] = (((tA * tR + baseAR) << 8) - (baseAR * tA)) / divisor;

        unsigned int baseAG = bA * bG;
        baseColor[1] = (((tA * tG + baseAG) << 8) - (baseAG * tA)) / divisor;

        unsigned int baseAB = bA * bB;
        baseColor[0] = (((tA * tB + baseAB) << 8) - (baseAB * tA)) / divisor;

        baseColor[3] = a;
    }
    else
    {
        baseColor[2] = 0;
        baseColor[1] = 0;
        baseColor[0] = 0;
        baseColor[3] = 0;
    }
}

void displayBackground(unsigned char* background)
{
    for (int i = 0; i < 4; i++)
    {
        for (int j = 0; j < 4; j++)
        {
            printf("%d ", background[i * 4 + j]);
        }
        printf("\r\n");
    }
}

int main(int argc, char * argv[])
{   
    unsigned char* background = new unsigned char[4 * 4];
    unsigned char* image = new unsigned char[4 * 4];

    image[0 * 4 + 0] = 0;
    image[0 * 4 + 1] = 0;
    image[0 * 4 + 2] = 255;
    image[0 * 4 + 3] = 255;

    image[1 * 4 + 0] = 0;
    image[1 * 4 + 1] = 0;
    image[1 * 4 + 2] = 255;
    image[1 * 4 + 3] = 192;

    image[2 * 4 + 0] = 0;
    image[2 * 4 + 1] = 0;
    image[2 * 4 + 2] = 255;
    image[2 * 4 + 3] = 128;

    image[3 * 4 + 0] = 0;
    image[3 * 4 + 1] = 0;
    image[3 * 4 + 2] = 255;
    image[3 * 4 + 3] = 32;

    // IPP

    memset(background, 0, 4 * 4);

    IppiSize size;
    size.width = 2;
    size.height = 2;
    ippiAlphaComp_8u_AC4IR(image, 8, background, 8, size, ippAlphaOver);

    printf("IPP:");

    displayBackground(background);

    // Manual

    memset(background, 0, 4 * 4);

    for (int i = 0; i < 4; i++)
        alphaBlend(background + i * 4, image + i * 4);

    printf("Manual:");

    displayBackground(background);

    delete background;
    delete image;

    getchar();
}

I've simulated blending an image with four red pixels having different alpha values onto fully transparent background (0, 0, 0, 0). The pixels are kept in memory as in Windows' bitmap (BGRA).

The results are following:

IPP:
0 0 255 255
0 0 192 192
0 0 128 128
0 0 32 32

Manual:
0 0 255 255
0 0 255 192
0 0 255 128
0 0 255 32

It looks like IPP is using a little bit different formula than I do:

IPP documentation:

Color components: αA * A + (1 - αA) * αB * B
Alpha: αA + (1 - αA) * αB

I use one from Wikipedia (yielding valid results)

Alpha: αA + αB * (1 - αA)
Color components: (A * αA + B * αB * (1 - αA)) / αO

Why does the IPP miss the "\ aO" part? How should I correctly use IPP to properly alpha-blend two semi-transparent images?

Upvotes: 0

Views: 89

Answers (1)

Mark Ransom
Mark Ransom

Reputation: 308158

There are two different ways of treating alpha in a pixel value. You can turn to Wikipedia again for an explanation.

The first way, called straight or unassociated alpha, means that the color channels and alpha are independent. A fully red pixel at 50% opacity will be 255,0,0,128. To convert it to fully opaque you just need to set the alpha to 100%, or 255.

The second way is called premultiplied alpha where the color values are multiplied by the alpha value. That fully red pixel at 50% opacity is now 128,0,0,128. The telltale sign is when none of the channel values are larger than the alpha. To convert it to fully opaque you need to divide each channel by the alpha, or for the typical range of 0-255 you should multiply by 255/alpha.

The math for blending with premultiplied alpha is simpler, giving it a speed advantage in demanding applications particularly where many blending operations will be performed on a single pixel. It's not surprising to see it used in a library like IPP.

Notice that both your formulas for blending alpha are identical, just algebraically rearranged. Alpha calculations aren't affected by the form you use.

Upvotes: 2

Related Questions