iOS PNG Alpha channel is solid when converting UIImage to CGImage for OpenGL

2.5k views Asked by At

I have several PNG images with alpha transparency. These display properly when used for general Quartz stuff such as in the UI. However when I go to take the image and get it into OpenGL I find that the data I get has a solid alpha of 255. I am using what seems to be the usual UIImage convert to CGImage and render into a context. I'll post relevant bits of code below.

I have done a lot of searching and finally stumbled upon a way around this and I wanted to share on here, but it is slow as it has to loop over every pixel, and copy that raw alpha into the alpha channel. I'd love to know a way around this. I haven't switched any PNG options in my build (not sure where to set that) to see if it helps..

thanks

First I get a UIImage from my app that I added like this:

UIImage *image = [UIImage imageNamed:imageName];

then i convert to a CGImageRef and I do the usual context create like this relevant line only:

CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height,
                                             bitsPerComponent, bytesPerRow,
                                             CGImageGetColorSpace(spriteImage),
                                                   kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);

Then these next two lines are the magic to get me at the REAL png raw data… I probably should check the RGBA vs other formats but I am lazy… After that I draw the image, and then go back and fix up the alpha… yes I know I could do this in a single loop and skipping bytes this is more readable for now...

// THIS data actually HAS the alpha!!
CFDataRef data = CGDataProviderCopyData(CGImageGetDataProvider(spriteImage));
GLubyte *pixels = (GLubyte *)CFDataGetBytePtr(data);

// 3
CGContextDrawImage(spriteContext, CGRectMake(0, 0, width, height), spriteImage);


// Sadly we now have to loop and fix the ALPHA directly from the raw PNG source
// data as otherwise we get wrong/missing alpha...
for (int y=0; y < height; ++y) {
    for (int x=0; x < width; ++x) {
        int byteIndex = (bytesPerRow * y) + (x * bytesPerPixel);

        spriteData[byteIndex + 3] = pixels[byteIndex +3 ];
    }
}

then I do the usual glTexture calls etc… and it works fine.. If I leave out the copy loop above I get solid sprites with no alpha. If I do the above, my sprites draw correctly with transparency.

I am aware of the whole pre-multiplied stuff iOS does but this seems broken… At least hopefully the above might help someone but there must be a better way???

Thoughts? Thanks!

2

There are 2 answers

0
mcomet On

Ok I guess I will answer the Q myself based on my finding and surmising…

First, the data provided by UIImage and CGImage will have the alpha premultiplied. If you are using PNGs which get optimized that is how it is. It seems at that point the alpha is useless. Based the sample GLImageProcessing code provided directly by Apple, it seems the proper way to get at raw PNG alpha is to use the CGImageGetDataProvider method as in their example. This specifically gets at the image without any processing. One note is that it is likely the data is in BGR rather than RGB order which makes sense as they do that for optimization. Their sample code has conversion if needed. In my case I do this:

CGImageRef spriteImage = image.CGImage;
if (!spriteImage) {
    NSLog(@"Failed to load image");
    exit(1);
}

CFDataRef data = CGDataProviderCopyData(CGImageGetDataProvider(CGImage));
pixels = (GLubyte *)CFDataGetBytePtr(data);

// … do other GL calls here like gl Gen and Bind…

// NOTE the reverse BGRA here for the 7th parameter only!:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (int)width, (int)height, 0, GL_BGRA, GL_UNSIGNED_BYTE, pixels);

// Don't forget to release BOTH the CF data and the CGImage or you will get memory leaks!
CFRelease(data);        // Release the CF data

This works for me, I get proper alpha, it's fast and correct. One note I don't have to rescale my image…if you care about that then you would want to do the usual CGBitmapContextCreate and ContextDraw stuff like usual, which would still work with the data provided properly. Don't forget to release as well… I forgot this as was leaking memory..heh.

Hope this helps someone…

0
pkirill On

It see that the CGImageGetDataProvider(CGImage) does alpha premultiplication. I have a PNG with red=255 and alpha=128, and the loaded pixel has red=128. Which is sad....