image-processingalphablendingconvolutionpremultiplied-alpha

How to use pre-multiplied during image convolution to solve alpha bleed problem?


i'm trying to apply a box blur to an transparent image, and i'm getting a "dark halo" around the edges.

Jerry Huxtable has a short mention of the problem, and a very good demonstration showing the problem happen:

enter image description here

But i, for the life of me, cannot understand how "pre-multiplied alpha" can fix the problem. Now for a very simple example. i have a 3x3 image, containing one red and one green pixel:

enter image description here

In reality the remaining pixels are transparent:

enter image description here

Now we will apply a 3x3 Box Blur to the image. For simplicities sake, we'll only calculate the new value of the center pixel. The way a box blur works is that since we have a 9 position square (3x3, called the kernel) we take 1/9th of each pixels in the kernel, and add it up:

enter image description here

So

finalRed =   1/9 * red1 + 1/9 * red2 + 1/9 * red3+ ... + 1/9 * red9
finalGreen = 1/9*green1 + 1/9*green2 + 1/9*green3+ ... + 1/9*green9
finalBlue =  1/9* blue1 + 1/9* blue2 + 1/9* blue3+ ... + 1/9* blue9
finalAlpha = 1/9*alpha1 + 1/9*alpha2 + 1/9*alpha3+ ... + 1/9*alpha9

In this very simplified example, the calculations become very simple:

finalRed =   1/9 * 255
finalGreen = 1/9 * 255
finalBlue =  0
finalAlpha = 1/9*255 + 1/9*255

This gives me a final color value of:

finalRed =   28
finalGreen = 28
finalBlue =  0
finalAlpha = 56 (22.2%)

enter image description here

This color is too dark. When i perform a 3px Box blur on the same 3x3 pixel image in Photoshop, i get what i expect:

enter image description here

Which is clearer when displayed over white:

enter image description here


In reality i'm performing a box blur on a bitmap containing transparent text, and the text gains the tell-tale dark around the fringes:

enter image description here

i'm starting with a GDI+ Bitmap that is in PixelFormat32bppARGB format


How do i use "pre-multiplied alpha" when applying 3x3 convolution kernel?

Any answer will have to include new forumla, since:

final = 1/9*(pixel1+pixel2+pixel3...+pixel9)

Is getting me the wrong answer.


Edit: A simpler example is:

i'll perform this math with color and alpha values in the range of 0..1:

enter image description here

i'm going to apply the box blur convolution filter to the middle pixel:

ARGB'
      = 1/9 * (0,1,0,1) + 1/9 * (0,0,0,0) + 1/9 * (0,0,0,0) + 
        1/9 * (0,1,0,1) + 1/9 * (0,0,0,0) + 1/9 * (0,0,0,0) + 
        1/9 * (0,1,0,1) + 1/9 * (0,0,0,0) + 1/9 * (0,0,0,0);

      = (0, 0.11, 0, 0.11) + (0,0,0,0) + (0,0,0,0) +
        (0, 0.11, 0, 0.11) + (0,0,0,0) + (0,0,0,0) +
        (0, 0.11, 0, 0.11) + (0,0,0,0) + (0,0,0,0)

      = (0, 0.33, 0, 0.33)

Which gives a fairly transparent dark green.

enter image description here

Which is not what i expect to see. And by comparison Photoshop's Box Blur is:

enter image description here

If i assume (0, 0.33, 0, 0.33) is pre-multiplied alpha, and un-multiply it, i get:

(0, 1, 0, 0.33)

enter image description here

Which looks right for my all-opaque example; but i don't know what to do when i begin to involve partially transparent pixels.

See also


Solution

  • tkerwin has already provided the correct answer, but it seems to need further explanation.

    The math you've shown in your question is absolutely correct, right up until the end. It is there that you're missing a step - the results are still in a pre-multiplied alpha mode, and must be "unmultiplied" back to the PixelFormat32bppARGB format. The opposite of a multiply is a divide, thus:

    finalRed = finalRed * 255 / finalAlpha;
    finalGreen = finalGreen * 255 / finalAlpha;
    finalBlue = finalBlue * 255 / finalAlpha;
    

    You've expressed a concern that the divide might create a result that is wildly out of range, but that won't happen. If you trace the math, you'll notice that the red, green, and blue values can't be greater than the alpha value, because of the pre-multiplication step. If you were using a more complicated filter than a simple box blur it might be a possibility, but that would be the case even if you weren't using alpha! The correct response is to clamp the result, turning negative numbers into 0 and anything greater than 255 into 255.