views:

56

answers:

1

I'm trying to write an iPhone app that takes PNG tilesets and displays segments of them on-screen, and I'm trying to get it to refresh the whole screen at 20fps. Currently I'm managing about 3 or 4fps on the simulator, and 0.5 - 2fps on the device (an iPhone 3G), depending on how much stuff is on the screen.

I'm using Core Graphics at the moment and currently trying to find ways to avoid biting the bullet and refactoring in OpenGL. I've done a Shark time profile analysis on the code and about 70-80% of everything that's going on is boiling down to a function called copyImageBlockSetPNG, which is being called from within CGContextDrawImage, which itself is calling all sorts of other functions with PNG in the name. Inflate is also in there, accounting for 37% of it.

Question is, I already loaded the image into memory from a UIImage, so why does the code still care that it was a PNG? Does it not decompress into a native uncompressed format on load? Can I convert it myself? The analysis implies that it's decompressing the image every time I draw a section from it, which ends up being 30 or more times a frame.

Solution

-(CGImageRef)inflate:(CGImageRef)compressedImage
{
    size_t width = CGImageGetWidth(compressedImage);
    size_t height = CGImageGetHeight(compressedImage);

    CGContextRef    context = NULL;
    CGColorSpaceRef colorSpace;
    int             bitmapByteCount;
    int             bitmapBytesPerRow;

    bitmapBytesPerRow   = (width * 4);
    bitmapByteCount     = (bitmapBytesPerRow * height);

    colorSpace = CGColorSpaceCreateDeviceRGB();
    context = CGBitmapContextCreate (NULL,
                                 width,
                                 height,
                                 8,
                                 bitmapBytesPerRow,
                                 colorSpace,
                                 kCGImageAlphaPremultipliedLast);
    CGColorSpaceRelease( colorSpace );

    CGContextDrawImage(context, CGRectMake(0, 0, width, height), compressedImage);
    CGImageRef result = CGBitmapContextCreateImage(context);
    CFRelease(context);
    return result;
}

It's based on zneak's code (so he gets the big tick) but I've changed some of the parameters to CGBitmapContextCreate to stop it crashing when I feed it my PNG images.

+1  A: 

To answer your last questions, your empirical case seems to prove they're not uncompressed once loaded.

To convert them into uncompressed data, you can draw them (once) in a CGBitmapContext and get a CGImage out of it. It should be well enough uncompressed.

Off my head, this should do it:

CGImageRef Inflate(CGImageRef compressedImage)
{
    size_t width = CGImageGetWidth(compressedImage);
    size_t height = CGImageGetHeight(compressedImage);

    CGContextRef context = CGBitmapContextCreate(
        NULL,
        width,
        height,
        CGImageGetBitsPerComponent(compressedImage),
        CGImageGetBytesPerRow(compressedImage),
        CGImageGetColorSpace(compressedImage),
        CGImageGetBitmapInfo(compressedImage)
    );

    CGContextDrawImage(context, CGRectMake(0, 0, width, height), compressedImage);
    CGImageRef result = CGBitmapContextCreateImage(context);
    CFRelease(context);
    return result;
}

Don't forget to release the CGImage you get once you're done with it.

zneak
My app now crashes with a runtime error - <Error>: CGBitmapContextCreate: unsupported parameter combination: 8 integer bits/component; 32 bits/pixel; 3-component color space; kCGImageAlphaLast; 5376 bytes/row. Any ideas?
p.g.l.hall
@p.g.l.hall Internet says CGBitmapContexts support only premultiplied alpha. Try using `kCGImageAlphaPremultipliedLast` instead of `CGImageGetBitmapInfo(...)`.
zneak
Ok, thank you for all of this :) I've tried that, and I also had to change the colour space to CGColorSpaceCreateDeviceRGB() to stop it telling me it was unsupported - now I'm getting another error: <Error>: CGBitmapContextCreate: invalid data bytes/row: should be at least 768 for 8 integer bits/component, 3 components, kCGImageAlphaPremultipliedLast.
p.g.l.hall
p.g.l.hall: That error message is telling you exactly what you're doing wrong. Read it, then fix it. (Note: Hard-coding 768 is the wrong solution.)
Peter Hosey
@Peter Hosey Where do you see he hardcoded it? Did you read the "Solution" part of the question? (Don't forget the leading @ if you want people to be notified of your comments.)
zneak
zneak: No, I hadn't. I was anticipating. Also, I've found that I get notified with or without the @, as long as my name is included. (It's possible that I have this wrong, though; I don't think I've ever seen anything about it in the SO documentation.)
Peter Hosey