1

I have a PNG (complete with alpha channel) that I'm looking to composite onto a CGContextRef using CGContextDrawImage. I'd like the RBG channels to be composited, but I'd also like for the source images alpha channel to be copied over as well.

Ultimately I'll be passing the final CGContextRef (in the form of a CGImageRef) to GLKit where I'm hoping to manipulate the alpha channel for colour tinting purposes using a fragment shader.

Unfortunately I'm running into issues when it comes to creating my texture atlas using Core Graphics. It appears that the final CGImageRef fails to copy over the alpha channel from my source image and is non-transparent. I've attached my current compositing code, and a copy of my test image below:

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
UInt8 * m_PixelBuf = malloc(sizeof(UInt8) * atlasSize.height * atlasSize.width * 4);

NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * atlasSize.width;
NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(m_PixelBuf,
                                            atlasSize.width,
                                            atlasSize.height,
                                            bitsPerComponent,
                                            bytesPerRow,
                                            colorSpace
                                            kCGImageAlphaPremultipliedFirst);

CGContextDrawImage(context, CGRectMake(x, y, image.size.width, image.size.height), image.CGImage);

CGImageRef imgRef = CGBitmapContextCreateImage(context);
CGColorSpaceRelease(colorSpace);
CGContextRelease(context);

enter image description here

genpfault
  • 51,148
  • 11
  • 85
  • 139
ndg
  • 2,585
  • 2
  • 33
  • 58

1 Answers1

2

Where do you people find this procedures of using CGBitmapContextCreate as this is one of the most common issues: kCGImageAlphaPremultipliedFirst will set the alpha to 1 and PREMULTIPLY RGB with the alpha value.

If you are using Xcode pleas command-click kCGImageAlphaPremultipliedFirst and find an appropriate replacement such as kCGImageAlphaLast.

Adding an example of using alpha as last channel:

+ (UIImage *)generateRadialGradient {
    int size = 256;
    uint8_t *buffer = malloc(size*size*4);
    memset(buffer, 255, size*size*4);
    for(int i=0; i<size; i++) {
        for(int j=0; j<size; j++) {
            float x = ((float)i/(float)size)*2.0f - 1.0f;
            float y = ((float)j/(float)size)*2.0f - 1.0f;
            float relativeRadius = x*x + y*y;
            if(relativeRadius >= 0.0 && relativeRadius < 1.0) { buffer[(i*size + j)*4 + 3] = (uint8_t)((1.0-sqrt(relativeRadius))*255.0); }
            else buffer[(i*size + j)*4 + 3] = 0;
        }
    }

    CGDataProviderRef provider = CGDataProviderCreateWithData(NULL,
                                                              buffer,
                                                              size*size*4,
                                                              NULL);

    int bitsPerComponent = 8;
    int bitsPerPixel = 32;
    int bytesPerRow = 4*size;
    CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
    CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Big|kCGImageAlphaLast;
    CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
    CGImageRef imageRef = CGImageCreate(size,
                                        size,
                                        bitsPerComponent,
                                        bitsPerPixel,
                                        bytesPerRow,
                                        colorSpaceRef,
                                        bitmapInfo,
                                        provider,
                                        NULL,
                                        NO,
                                        renderingIntent);
    /*I get the current dimensions displayed here */
    return [UIImage imageWithCGImage:imageRef];
}

So this code creates a radial gradient from code. The inner part is full opaque while it gets transparent when it gets further from center.

We could also use kCGImageAlphaFirst which results in yellowish gradient. Alpha is always at 1 and only the first (red) channel is being decreased. The result is being white in the middle and as the red channel is decreased the yellow color starts showing.

Matic Oblak
  • 16,318
  • 3
  • 24
  • 43
  • According to Apple's [Core Graphics documentation](https://developer.apple.com/library/ios/DOCUMENTATION/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_context/dq_context.html#//apple_ref/doc/uid/TP30001066-CH203-BCIBHHBB) `kCGImageAlphaLast` is currently unsupported on iOS, leaving only `kCGImageAlphaPremultipliedFirst` or `kCGImageAlphaPremultipliedLast` as alternatives. – ndg Jun 12 '14 at 10:49
  • I have no idea where you got that and couldn't find it in docs.. Anyway I have been using that for quite some time and am still using it on daily bases. So I guess if you are sure about this not working for you you will have to find an alternative way to get the raw RGBA image data. – Matic Oblak Jun 12 '14 at 11:00
  • Maybe even defaults are RGBA so maybe removing that argument would get you the expected result... – Matic Oblak Jun 12 '14 at 11:01
  • You can see the supported pixel formats under "Supported Pixel Formats" at the [link I provided](https://developer.apple.com/library/ios/DOCUMENTATION/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_context/dq_context.html#//apple_ref/doc/uid/TP30001066-CH203-BCIBHHBB). – ndg Jun 12 '14 at 11:37
  • Well it seems someone forgot to update the list of supported pixel formats which still does not make the items not present on the list unsupported.. Anyway if you click on any of those in the list it will take you to their description and on THAT list you can also find: "kCGImageAlphaLast The alpha component is stored in the least significant bits of each pixel. For example, non-premultiplied RGBA. Available in iOS 2.0 and later. Declared in CGImage.h." Now I'd say that made it quite supported... – Matic Oblak Jun 12 '14 at 11:58
  • kCGImageAlphaLast is not available anymore as per 2019 :D Just can't find an alternative method to get non-premultiplied RGBA data. – atereshkov Jan 21 '19 at 14:43
  • @atereshkov What language are you using now? In Swift it seems this is now `CGImageAlphaInfo.first`. As for ObjectiveC it seems to be the same `kCGImageAlphaFirst`. – Matic Oblak Jan 21 '19 at 14:46
  • @MaticOblak I'm using ObjC and kCGImageAlphaFirst and I get unsupported parameter combination error: CGBitmapContextCreateImage: invalid context 0x0. It means that the error is related to kCGImageAlphaFirst. It's not available anymore starting from iOS 8 I guess (just got it from https://stackoverflow.com/a/26365180/5969121). Thanks for the reply! Glad to see it after almost 5 years. Just wanted to get some replacement to get it works. – atereshkov Jan 21 '19 at 14:53
  • 1
    @atereshkov I believe you might actually have some other issue. Maybe missing byte order. Please see the example I added in this answer. It is working correctly on laters iOS version. – Matic Oblak Jan 22 '19 at 06:58
  • @MaticOblak ok, thanks man, helped me a lot. One more thing to consider is, let's say I have RGBA image in UIImage format, how can I get the raw data from it? (like your buffer array, I just want to change some value of pixels). I've tried to get it using CGImageData provider and CFDataGetBytePrt, but it's still returning me RGB values instead of RGBA. UInt8 * buffer = (UInt8 *) CFDataGetBytePtr(rawData); – atereshkov Jan 22 '19 at 11:26
  • @atereshkov This is not as trivial task as it may seem. UIImage supports many formats so if you want to have a specific RGBA data you need to redraw it to a new context with using your own data provider. Try this answer here https://stackoverflow.com/a/14363385/526828. See that at first data is allocated that is large enough to fit all pixels. Then a context is created with pointer to this data instead of nil. You can do whatever in with this context (draw to it with paths even) and the end you will get the data in the given buffer. (You will need to change some settings though). – Matic Oblak Jan 22 '19 at 11:32
  • @MaticOblak that is the case of the problem because of pre-multiplying of Alpha channel. But anyway I guess I'm on the right way, thanks for the help, will grind further. – atereshkov Jan 22 '19 at 11:52
  • @atereshkov I see your point. I had to try it and I honestly see no way on how to create a context that normally supports alpha channel. From all other threads it seems this is simply an iOS bug and for some weird reason it hasn't been fixed since ever. Anyway, it is possible to access raw pixel data. Via `CFDataGetBytePtr(CGDataProviderCopyData(CGImageGetDataProvider(image)))`. Be sure to check if any memory management is needed here though... As for resizing and orientation I guess it is still best to use image view and create a snapshot of it. – Matic Oblak Jan 22 '19 at 13:08