views:

1412

answers:

2

I have created an context like this (simplified):

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

CGContextRef context = CGBitmapContextCreate (bitmapData,
  pixWide,
  pixHeigh,
  8, // bits per component
  bitmapBytesPerRow,
  colorSpace,
  kCGImageAlphaPremultipliedFirst);

Now, when I try to extract the data for the first pixel in my PNG with Alphatransparency, it has very weird alpha values. I have an simple PNG that's a square. On each edge I cut off 10x10 pixel and made them totally transparent. Alpha shouldn't be something like 153 there.

There's an kCGImageAlphaOnly declared in CGImage.h. The doc says:

kCGImageAlphaOnly There is no color data, only an alpha channel.

Ok, so that actually sounds good, because I only need Alpha data, and nothing else. But this raises some question marks in my head. If I do habe a fully equipped PNG with a bunch of colors + alpha: Would this constant make sure that my PNG is converted to match that color space? Or would I have to provide an PNG that matches that specified color space?

Edit: I tried using kCGImageAlphaOnly, but I get this error:

<Error>: CGBitmapContextCreate: unsupported parameter combination: 8 integer bits/component; 24 bits/pixel; 0-component colorspace; kCGImageAlphaOnly; 55 bytes/row.

What may be the problem here? I specified this before:

size_t pixelsWide = CGImageGetWidth(inImage);
size_t pixelsHigh = CGImageGetHeight(inImage);
bitmapBytesPerRow = (pixelsWide * 1); // not * 4, because I just want alpha
bitmapByteCount = (bitmapBytesPerRow * pixelsHigh);

Edit: I've been reading this a minute ago:

PNG's which are added to XCode are optimized by 'pngcrush' during compilation. This does some byte-swapping (from RGBA to BRGA) and pre-multiplication of alpha.

I assume that this pre-multiplication of alpha makes trouble.

Edit: The alpha channel keeps intact after pngcrunch did the byte-swapping stuff to the PNG. Since I don't care about colors, just alpha, that pre-multiplication shouldn't be a too big problem, I think.

My PNG's have been 24bit PNG bevor I added them to Xcode.

+2  A: 

Are you sure you're looking only at alpha values?

If you're expecting all the alpha components to come first, then all the red components, etc.: That's planar layout, and I don't think Quartz supports it natively—it only supports all the components together in each pixel (ARGBARGBARGBARGB…, not AAAA…RRRR…GGGG…BBBB…). So if you're just marching straight into the data treating every byte as alpha, that's your problem: you're looking at red, green, and blue components and treating them as alpha.

As for premultiplication, that doesn't affect the alpha channel, it affects the color channels. The formula for raster compositing (putting one raster image over another) is:

dst.r = src.r * src.a + dst.r * (1.0 - src.a);
dst.g = src.g * src.a + dst.g * (1.0 - src.a);
dst.b = src.b * src.a + dst.b * (1.0 - src.a);

Premultiplication cuts out the first multiplication expression:

dst.r = src.r′ + dst.r * (1.0 - src.a);
dst.g = src.g′ + dst.g * (1.0 - src.a);
dst.b = src.b′ + dst.b * (1.0 - src.a);

This works because the source color components are already multiplied by the alpha component—hence the name “premultiplied”. It doesn't need to multiply them now, because it already has the results.

It's an optimization, and presumably an important one on the iPhone (all those multiplication operations add up when you do a million or two of them). But it doesn't affect the layout of the components: interleaved remains interleaved, whether the RGB components are premultiplied or not.

Peter Hosey
Thanks. But I still didn't get the point, how I can extract the correct alpha. There seems to be an alpha separately, but it's incorrect. It matches just sometimes, where everything else in the image is solid blue. For the colors I do get correct values. Just alpha is strange.
Thanks
Well, your sample code shows you extracting the *last* byte, not the *first*. So, yeah, of course you're getting blue components instead of alpha components. The safest way (taking endianness into account) is to treat it as a byte pointer and increment your index by 4 at a time.
Peter Hosey
+3  A: 

You can't do this:

bitmapBytesPerRow = (pixelsWide * 1); // not * 4, because I just want alpha

The function you're calling will always return all the image data. The kCGImageAlphaOnly constant is used to tell YOU that an image only contains an alpha channel, no colour information.

You'll need to use pixelsWide * 4 for the bytesPerRow. Also note that the bitmapData argument to CGBitmapContextCreate() is used to provide storage space explicitly, rather than having it drawn for you.

Possibly what you want to do is this (untested code, just typed from memory):

CGImageRef image = GetMyImageFromWhereverItIs();
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
CGContextRef ctx = CGBitmapContextCreate( NULL, CGImageGetWidth(image),
        CGImageGetHeight(image), CGImageGetBitsPerComponent(image),
        CGImageGetBytesPerRow(image), space,
        kCGBitmapByteOrderRGBA | kCGImageAlphaLast );
CGColorSpaceRelease( space );

// now draw the image into the context
CGRect rect = CGRectMake( 0, 0, CGImageGetWidth(image), CGImageGetHeight(image) );
CGContextDrawImage( ctx, rect, image );
UInt32 * pixels = CGBitmapContextGetData( ctx );

// now we can iterate through the data & read the alpha values
int i, count = CGBitmapContextGetBytesPerRow(ctx) * CGBitmapContextGetHeight(ctx);
for ( i = 0; i < count; i++ )
{
    UInt8 alpha = pixels[i] & 0x000000ff;
    // do with the alpha what you will
}
Jim Dovey
Thanks. The constant kCGBitmapByteOrderRGBA is undeclared...
Thanks
What's the supposed to do in the third line from bottom?
Thanks
I tried that, but it doesn't work. It gives me the same strange results. Although most of the time alpha is okay, sometimes it is 190 where a pixel is definitely fully transparent. But that's the same result I am getting with my code, too...
Thanks
For an image with 55 x 50 pixels, NSLog(@"count:%i",count); is returning 11.000. Not sure, but the foor loop looks like if it would iterate over all 11.000 values. I expect 2750 aplha values.
Thanks
Jim Dovey
I'm getting this: <Error>: CGBitmapContextCreate: unsupported parameter combination: 8 integer bits/component; 32 bits/pixel; 3-component colorspace; kCGImageAlphaLast; 224 bytes/row.
Thanks
0x000000ff is masking out the bits which *are* part of the alpha value, leaving only blue (remember, alpha first—ARGB). I noted this in a comment on my own answer. At the very least, you need to change it to 0xff000000; a better solution, which I suggested in that comment, is to iterate on bytes instead of UInts32.
Peter Hosey
Strange; I thought pngcrush stored the data in BGRA format, and that kCGImageAlphaLast would ensure Alpha was the last item in each host-endian 32-bit word... *shrugs*
Jim Dovey
pngcrush has nothing to do with what's in the bitmap context. A bitmap context holds pure pixels, completely divorced from any and all external representations of the image.
Peter Hosey