I'm in the process of coming up with a few functions that deal with rendering SVG data (vector paths, none of the cruft) to PNG files. To make life a bit easier and consistent I'm using Cairo to draw, then saving the 32-bits per pixel surface into a PNG file manually.
The thing is, as Cairo expects to composite 32-bit surfaces, the pixels use pre-multiplied alpha. PNG doesn't (Apple made the mistake of doing what they liked at this point).
It's surprisingly tricky to find concrete information on conversion between these two formats, so I've winged it a bit and come up with:-
It's probably quite inefficient, this is intended to be used in a process that focuses on quality over speed anyway.
unsigned char nAlpha, *pPtr;
// current format in memory: B G R A
// assume pPtr is pointing to B of a pixel
nAlpha = *(pPtr + 3);
if (nAlpha > 0)
d = ((*pPtr) * 255.0) + (nAlpha / 2.0);
*pPtr = (unsigned char)(d / nAlpha);
// repeat for G and R, then swap B and R as PNG pixels are R G B A
But is it right? :o
Well to de-premultiply you will have to decide what color you want your background color to have.
The normal way to composite a color over another is Ca+B(1-a) (in this case you composite color C over B, with alpha a). With premultiplied you substitute Ca with A, thus you get A+B(1-a), but since what you want is C we will work with that instead.
So you have Ca + B(1-a) = D, where C is original color (what you want), B is background (decided by you) and D is the resulting color (the one Cairo gives you). This gives you
Ca + B - Ba = D
Ca - Ba = D - B
a(C-B) = D-B
C-B = (D-B)/a
C = B + ((D-B)/a)
This is what i could infer from this: [cairo] Compositing strategies and pre-multiplied alpha
Now the real question is, why aren't you using cairo_surface_write_to_png ()? PNG Support
Premultiplied alpha means exactly what it sounds like. To "un-pre-multiply" it you divide the R, G, B components by alpha:
This loses information, but the information was already lost when it was pre-multiplied in the first place.
int new_r = 255 * r / alpha;
int new_g = 255 * g / alpha;
int new_b = 255 * b / alpha;
Eh? Surely the point of having an alpha channel, pre-multiplied or no, is that you are storing a factor to mix with "nothingness", i.e. the background colour is whatever the initial colour of the surface is before you draw. RGBA (255, 255, 255, 0) is not white.
Originally Posted by Shakti
A technicality: PNG support in Cairo requires libpng. Outputting only 32bpp images with no compression applied means that I can generate compliant PNGs while adding, oooh... 2KB to the release build? ;)
Originally Posted by Shakti
My code is broadly similar to what Brewbuck has posted, only I can't explain the (nAlpha / 2.0) bit in mine. :o