4

Like in this post:

I'm having a similar problem. The pointer from the malloc in create_bitmap_data_provider is never freed. I've verified that the associated image object is eventually released, just not the provider's allocation. Should I explicitly create a data provider and somehow manage it's memory? Seems like a hack.

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, blah blah blah);
CGColorSpaceRelease(colorSpace);

// ... draw into context

CGImageRef imageRef = CGBitmapContextCreateImage(context);
UIImage * image = [[UIImage alloc] initWithCGImage:imageRef];

CGImageRelease(imageRef);
CGContextRelease(context);

After fbrereto's answer below, I changed the code to this:

- (UIImage *)modifiedImage {
    CGSize size = CGSizeMake(width, height);

    UIGraphicsBeginImageContext(size);
    CGContextRef context = UIGraphicsGetCurrentContext();

    // draw into context   

    UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return image;  // image retainCount = 1
}    

// caller: 
{
    UIImage * image = [self modifiedImage]; 
    _imageView.image = image; // image retainCount = 2
}

// after caller done, image retainCount = 1, autoreleased object lost its scope

Unfortunately, this still exhibits the same issue with a side effect of flipping the image horizontally. It appears to do the same thing with CGBitmapContextCreateImage internally.

I have verified my object's dealloc is called. The retainCount on the _imageView.image and the _imageView are both 1 before I release the _imageView. This really doesn't make sense. Others seem to have this issue as well, I'm the last one to suspect the SDK, but could there be an iPhone SDK bug here???

Community
  • 1
  • 1
Shawn
  • 416
  • 5
  • 18

5 Answers5

4

It looks like the problem is that you are releasing a pointer to the returned CGImage, rather than the CGImage itself. I too was having similar issues before with continual growing allocations and an eventual app crash. I addressed it by allocating a CGImage rather than a CGImageRef. After the changes run your code in Insturments with allocations, and you should not see anymore perpetual memory consumption from malloc. As well if you use the class method imageWithCGImage you will not have to worry about autoreleasing your UIImage later on.

I typed this on a PC so if you drop it right into XCode you may have syntax issue, I appologize in advance; however the principal is sound.

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 
CGContextRef context = CGBitmapContextCreate(NULL, blah blah blah); 
CGColorSpaceRelease(colorSpace);

// ... draw into context  

CGImage cgImage = CGBitmapContextCreateImage(context); 
UIImage * image = [UIImage imageWithCGImage:cgImage];  
CGImageRelease(cgImage); 
CGContextRelease(context);
return image;
beggs
  • 4,185
  • 2
  • 30
  • 30
CaseyB
  • 325
  • 2
  • 7
3

I had this problem and it drove me nuts for a few days. After much digging and noticing a leak in CG Raster Data using Instruments.

The problem seems to lie inside CoreGraphics. My problem was when i was using CGBitmapContextCreateImage in a tight loop it would over a period of time retain some images (800kb each) and this slowly leaked out.

After a few days of tracing with Instruments I found a workaround was to use CGDataProviderCreateWithData method instead. The interesting thing was the output was the same CGImageRef but this time there would be no CG Raster Data used in VM by Core graphics and no leak. Im assuming this is an internal problem or were misusing it.

Here is the code that saved me:

@autoreleasepool {
    CGImageRef cgImage;
    CreateCGImageFromCVPixelBuffer(pixelBuffer,&cgImage);

    UIImage *image= [UIImage imageWithCGImage:cgImage scale:1.0 orientation:UIImageOrientationUp];

    // DO SOMETHING HERE WITH IMAGE

    CGImageRelease(cgImage);
}

The key was using CGDataProviderCreateWithData in the method below.

static OSStatus CreateCGImageFromCVPixelBuffer(CVPixelBufferRef pixelBuffer, CGImageRef *imageOut)
    {
        OSStatus err = noErr;
        OSType sourcePixelFormat;
        size_t width, height, sourceRowBytes;
        void *sourceBaseAddr = NULL;
        CGBitmapInfo bitmapInfo;
        CGColorSpaceRef colorspace = NULL;
        CGDataProviderRef provider = NULL;
        CGImageRef image = NULL;

        sourcePixelFormat = CVPixelBufferGetPixelFormatType( pixelBuffer );
        if ( kCVPixelFormatType_32ARGB == sourcePixelFormat )
            bitmapInfo = kCGBitmapByteOrder32Big | kCGImageAlphaNoneSkipFirst;
        else if ( kCVPixelFormatType_32BGRA == sourcePixelFormat )
            bitmapInfo = kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst;
        else
            return -95014; // only uncompressed pixel formats

        sourceRowBytes = CVPixelBufferGetBytesPerRow( pixelBuffer );
        width = CVPixelBufferGetWidth( pixelBuffer );
        height = CVPixelBufferGetHeight( pixelBuffer );

        CVPixelBufferLockBaseAddress( pixelBuffer, 0 );
        sourceBaseAddr = CVPixelBufferGetBaseAddress( pixelBuffer );

        colorspace = CGColorSpaceCreateDeviceRGB();

        CVPixelBufferRetain( pixelBuffer );
        provider = CGDataProviderCreateWithData( (void *)pixelBuffer, sourceBaseAddr, sourceRowBytes * height, ReleaseCVPixelBuffer);
        image = CGImageCreate(width, height, 8, 32, sourceRowBytes, colorspace, bitmapInfo, provider, NULL, true, kCGRenderingIntentDefault);

        if ( err && image ) {
            CGImageRelease( image );
            image = NULL;
        }
        if ( provider ) CGDataProviderRelease( provider );
        if ( colorspace ) CGColorSpaceRelease( colorspace );
        *imageOut = image;
        return err;
    }

    static void ReleaseCVPixelBuffer(void *pixel, const void *data, size_t size)
    {
        CVPixelBufferRef pixelBuffer = (CVPixelBufferRef)pixel;
        CVPixelBufferUnlockBaseAddress( pixelBuffer, 0 );
        CVPixelBufferRelease( pixelBuffer );
    }
Vlad
  • 5,727
  • 3
  • 38
  • 59
0

Instead of manually creating your CGContextRef I'd suggest you leverage UIGraphicsBeginImageContext as demonstrated in this post. More details on that set of routines can be found here. I trust it'll help to resolve this issue, or at the very least give you less memory you have to manage yourself.

UPDATE:

Given the new code, the retainCount of the UIImage as it comes out of the function will be 1, and assigning it to the imageView's image will cause it to bump to 2. At that point deallocating the imageView will leave the retainCount of the UIImage to be 1, resulting in a leak. It is important, then, after assigning the UIImage to the imageView, to release it. It may look a bit strange, but it will cause the retainCount to be properly set to 1.

Community
  • 1
  • 1
fbrereto
  • 35,429
  • 19
  • 126
  • 178
  • Added to the question after this answer. Didn't help. – Shawn Sep 16 '09 at 19:26
  • Added to my answer after you added to your question. Hopefully it'll help. – fbrereto Sep 16 '09 at 20:14
  • It has to be my code, I can't blame the SDK (3.0) yet. But the retainCount from the function will be an _autoreleased_ 1. As soon as the scope of the caller is gone, that will decrement by 1. So after the set on the imageView, it will be 2, but as soon as the caller's scope is done, it goes to one. In the dealloc, I've verified in the release of the object both the imageView and the imageView.image both have a retainCount of 1. – Shawn Sep 16 '09 at 21:13
  • If the object is autoreleased then you have to make sure to drain the pool in order to deallocate the image. Are you doing that? – fbrereto Sep 16 '09 at 21:26
  • The application eventually runs out of memory because of this. I shouldn't have to drain the pool for all my autoreleased objects. It should eventually free itself like all the other autoreleased objects I have. This little beast doesn't want to relinquish itself for some reason. I could understand if the thought behind draining it if you wanted to check the memory immediately, but I run this code over and over and the memory keeps growing and growing. – Shawn Sep 16 '09 at 21:43
  • In that case I'd create an autorelease pool for the scope where you're running this code repeatedly and drain it. The autorelease pool is not like a garbage collector IIUC- it won't drain automatically when memory starts to pile up. Even if it does, you're better off controlling the memory yourself with a second pool that you explicitly drain. – fbrereto Sep 16 '09 at 21:49
  • It's not in a tight loop. The logic occurs on load of the view, so when I pop the view, I expect the UIKit's autorelease pool to do it's job. This is from the SDK: The Application Kit automatically creates a pool at the beginning of an event cycle (or event-loop iteration), such as a mouse down event, and releases it at the end, so your code normally does not have to worry about them. My code doesn't fall into the exceptions. Something else is going on here... – Shawn Sep 16 '09 at 23:34
  • This answer is incorrect after UPDATE:. Reference count is fine. It's not required to drain the pool. – Shawn Sep 17 '09 at 14:27
0

You're not the only one with this problem. I've had major problems with CGBitmapContextCreateImage(). When you turn on Zombie mode, it even warns you that memory is released twice (when it's not the case). There's definitely a problem when mixing CG* stuff with UI* stuff. I'm still trying to figure out how to code around this issue.

Side note: calling UIGraphicsBeginImageContext is not thread-safe. Be careful.

Philippe Leybaert
  • 168,566
  • 31
  • 210
  • 223
0

This really helped me! Here's how I used it to fix that nasty leak problem:

    CGImage *cgImage = CGBitmapContextCreateImage(context);
    CFDataRef dataRef = CGDataProviderCopyData(CGImageGetDataProvider(cgImage));
    CGImageRelease(cgImage);
    image->imageRef = dataRef;
    image->image = CFDataGetBytePtr(dataRef);

Notice, I had to store the CFDataRef (for a CFRelease(image->imageRef)) in my ~Image function. Hopefully this also helps others...JR