5

Alright I am having a world of difficulty tracking down this memory leak. When running this script I do not see any memory leaking, but my objectalloc is climbing. Instruments points to CGBitmapContextCreateImage > create_bitmap_data_provider > malloc, this takes up 60% of my objectalloc.

This code is called several times with a NSTimer.

 //GET IMAGE FROM RESOURCE DIR
  NSString * fileLocation = [[NSBundle mainBundle] pathForResource:imgMain ofType:@"jpg"];
  NSData * imageData = [NSData dataWithContentsOfFile:fileLocation];
  UIImage * blurMe = [UIImage imageWithData:imageData];

  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    UIImage * scaledImage = [blurMe _imageScaledToSize:CGSizeMake(blurMe.size.width / dblBlurLevel, blurMe.size.width / dblBlurLevel) interpolationQuality:3.0];
    UIImage * labelImage = [scaledImage _imageScaledToSize:blurMe.size interpolationQuality:3.0];
    UIImage * imageCopy = [[UIImage alloc] initWithCGImage:labelImage.CGImage];

  [pool drain]; // deallocates scaledImage and labelImage

  imgView.image = imageCopy;
  [imageCopy release];

Below is the blur function. I believe the objectalloc issue is located in here. Maybe I just need a pair of fresh eyes. Would be great if someone could figure this out. Sorry it is kind of long... I'll try and shorten it.

    @implementation UIImage(Blur)
    - (UIImage *)blurredCopy:(int)pixelRadius
    {
        //VARS
        unsigned char *srcData, *destData, *finalData;
        CGContextRef    context = NULL;
        CGColorSpaceRef colorSpace;
        void *          bitmapData;
        int             bitmapByteCount;
        int             bitmapBytesPerRow;

        //IMAGE SIZE
        size_t pixelsWide = CGImageGetWidth(self.CGImage);
        size_t pixelsHigh = CGImageGetHeight(self.CGImage);
        bitmapBytesPerRow   = (pixelsWide * 4);
        bitmapByteCount     = (bitmapBytesPerRow * pixelsHigh);

        colorSpace = CGColorSpaceCreateDeviceRGB();
        if (colorSpace == NULL) { return NULL; }

        bitmapData = malloc( bitmapByteCount );
        if (bitmapData == NULL) { CGColorSpaceRelease( colorSpace ); }

        context = CGBitmapContextCreate (bitmapData,
                         pixelsWide,
                         pixelsHigh,
                         8,    
                         bitmapBytesPerRow,
                         colorSpace,
                         kCGImageAlphaPremultipliedFirst );
        if (context == NULL) { free (bitmapData); }

        CGColorSpaceRelease( colorSpace );
        free (bitmapData);

        if (context == NULL) { return NULL; }

        //PREPARE BLUR
        size_t width = CGBitmapContextGetWidth(context);
        size_t height = CGBitmapContextGetHeight(context);
        size_t bpr = CGBitmapContextGetBytesPerRow(context);
        size_t bpp = (CGBitmapContextGetBitsPerPixel(context) / 8);
        CGRect rect = {{0,0},{width,height}}; 

        CGContextDrawImage(context, rect, self.CGImage); 

        // Now we can get a pointer to the image data associated with the bitmap
        // context.
        srcData = (unsigned char *)CGBitmapContextGetData (context);
        if (srcData != NULL)
        {

            size_t dataSize = bpr * height;
            finalData = malloc(dataSize);
            destData = malloc(dataSize);
            memcpy(finalData, srcData, dataSize);
            memcpy(destData, srcData, dataSize);

            int sums[5];
            int i, x, y, k;
            int gauss_sum=0;
            int radius = pixelRadius * 2 + 1;
            int *gauss_fact = malloc(radius * sizeof(int));

            for (i = 0; i < pixelRadius; i++)
            {
            .....blah blah blah...
                THIS IS JUST LONG CODE THE CREATES INT FIGURES
                ........blah blah blah......
                }


            if (gauss_fact) { free(gauss_fact); }
        }

        size_t bitmapByteCount2 = bpr * height;
       //CREATE DATA PROVIDER
        CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, srcData, bitmapByteCount2, NULL);

       //CREATE IMAGE
        CGImageRef cgImage = CGImageCreate(
                               width, 
                               height, 
                               CGBitmapContextGetBitsPerComponent(context),
                               CGBitmapContextGetBitsPerPixel(context), 
                               CGBitmapContextGetBytesPerRow(context),
                               CGBitmapContextGetColorSpace(context), 
                               CGBitmapContextGetBitmapInfo(context), 
                               dataProvider, 
                               NULL, 
                               true, 
                               kCGRenderingIntentDefault
                                     );

       //RELEASE INFORMATION
        CGDataProviderRelease(dataProvider);
        CGContextRelease(context); 

        if (destData) { free(destData); }
        if (finalData) { free(finalData); }
        if (srcData) { free(srcData); }


        UIImage *retUIImage = [UIImage imageWithCGImage:cgImage];
        CGImageRelease(cgImage);

        return retUIImage;
  }

The only thing I can think of that is holding up the objectalloc is this UIImage *retUIImage = [UIImage imageWithCGImage:cgImage];...but how to do I release that after it has been returned? Hopefully someone can help please.

bbullis21
  • 741
  • 3
  • 9
  • 21
  • 1
    I have no idea if it's related to the leak, but you shouldn't be calling `free(bitmapData)` before you've released the context. Blocks of memory allocated with `malloc()` don't have retain counters, so if you call `free()` it is immediately freed, even if the CGContext is using it. – benzado Dec 30 '09 at 20:37
  • Are you targeting iPhone OS 3.1 or higher? It might be a bug in the 3.0 or earlier framework. If you're on 3.1, you don't need to allocate the bitmapData for `CGBitmapContextCreate`. Also `_imageScaledToSize` is an undocumented call. Please don't use it. Some people might not be willing to answer your question if you're using undocumented calls. – lucius Jan 12 '10 at 03:57
  • hey bbullis21 does your blur code is working fine on device?? – Leena Jan 05 '12 at 14:24

8 Answers8

4

I have used Quartz many times. Every time I do is a nightmare. As far as I noticed, and I have filled a bug on Apple sending a project as proof of crash, one of 4 things is true:

  1. Quartz leaks as hell
  2. It takes too long to release the memory
  3. it uses too much memory unnecessarily or
  4. a combination of all them.

I once created a simple project to prove that. The project had a button. Every time the button was pressed a small image (100x100 pixels) was added to the screen. The image was composed by two layers, the image itself and an additional layer containing a dashed line drawn around the image border. This drawing was done in Quartz. Pressing 8 times the button made the application crash. You can say: pressing the button 8 times and 16 images were added to the screen, this is the reason for crashing, right?

Instead of drawing the border using Quartz, I decided to have the border pre-draw on a PNG and just add it as a layer to the object. The same two layers per object created. Right?

I clicked 100 times, adding 200 images to the screen and no crash. The memory never went above 800 Kb. I could continue clicking... the app continued snappier and fast. No memory warning, no crash.

Apple has to review Quartz urgently.

Duck
  • 34,902
  • 47
  • 248
  • 470
1

Get clang and run your project through it. Not only will it find your (static) leaks, it will help you understand more about the reference-counting rules -- at least it did for me.

sehugg
  • 3,615
  • 5
  • 43
  • 60
  • 1
    Or run "Build & Analyze" in Xcode 3.2 if you are running Snow Leopard. It is clang and does a nice job of diagramming the flow of execution leading to the problem. – Steve Madsen Mar 29 '10 at 14:55
0

I've seen similar behavior and read several other similar posts. Creating an autorelease pool doesn't seem to help. It looks like the library is either leaking the memory or storing it in a higher level auto-release pool so its not getting released until too late. I suspect there's a bug in the framework but can't prove it.

joelm
  • 8,741
  • 1
  • 19
  • 17
0

it helps if you actually wrap the image allocation and release in the pool object existence cycle,

pool = [[NSAutoreleasePool alloc] init];

[imageView setImage: [UIImage imageNamed:"image1.png"]];
...
[imageView setImage: [UIImage imageNamed:"image2.png"]];
...
[imageView setImage: [UIImage imageNamed:"image3.png"]];
....
[pool drain];
[pool release];

In this example image3.png WILL NOT be released, but image1 and image2 will be.

benzado
  • 82,288
  • 22
  • 110
  • 138
mcfrei
  • 1
  • You shouldn't `-release` after `-drain` (which is equivalent to `-release` on non-garbage collected runtimes) as that causes double release. – kennytm Jan 14 '10 at 05:34
0

Some ideas/thoughts:

For one, this chunk of code clearly has problems since you can free bitmapData twice if the context fails to allocate.

    if (context == NULL) { free (bitmapData); }

    CGColorSpaceRelease( colorSpace );
    free (bitmapData);

    if (context == NULL) { return NULL; }

I also agree with benzado that you bitmapData until you are done with the context... though it isn't crashing which is puzzling. lucky perhaps.

Note that I'm pretty sure the data provider and the bits is refers to are going to become part of the cgImage returned from CGImageCreate:

provider The source of data for the bitmap. For information about supported data formats, see the discussion below. Quartz retains this object; on return, you may safely release it.

So, that means that until the UIImage you return is released, I don't think the cgImage or the bits data provider are going to go away. So, perhaps the issue is that the UIImage isn't every going away?

Why aren't you just using CGBitmapContextCreateImage() to create the resulting image? (you may need to call CGContextFlush() to force drawing to the bitmap, but it looks like you're doing all the pixel changing manually, so possibly not needed).

Based on the vague documentation, I don't think you should be free()'ing the pointer returned by CGBitmapContextGetData (but that's a bit of a guess due to the vagueness of the docs).

also:

you aren't initializing srcData, destData & finalData to NULL so your test prior to free()'ing them seems risky.

all that said (and do try some of those things), we had a leak in our Mac App at one point because in 10.5.6 and earlier some aspect of CIImage imageWithCGImage, imageByApplyingTransofrm, imageByCroppingToRect, NSBitmapImageRep initWithCIImage or NSBitmapImageRep CGImage leaked. This was fixed in 10.5.7 so it's possible that something similar exists in the iPhone OS version you are testing against.

Dad
  • 6,388
  • 2
  • 28
  • 34
0

Actually if I recall [UIImage imageNamed] creates its own cache, which is outside of your control. That would account for the memory usage you are seeing. You can read more here - http://www.alexcurylo.com/blog/2009/01/13/imagenamed-is-evil/.

Larry Borsato
  • 394
  • 3
  • 5
0

I had same problem when using CGBitmapContextCreate(..) with leaks happening randomly and building up.

I solved this by creating the UIImage from the bitmap data using different function:

Try:

CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);

or

    provider = CGDataProviderCreateWithData( (void *)pixelBuffer, sourceBaseAddr, sourceRowBytes * height, ReleaseCVPixelBufferFunction);


CGImageRef imageRef = CGImageCreate(cvMat.cols,                                     // Width
                                    cvMat.rows,                                     // Height
                                    8,                                              // Bits per component
                                    8 * cvMat.elemSize(),                           // Bits per pixel
                                    cvMat.step,                                     // Bytes per row
                                    colorSpace,                                     // Colorspace
                                    kCGImageAlphaNone | kCGBitmapByteOrderDefault,  // Bitmap info flags
                                    provider,                                       // CGDataProviderRef
                                    NULL,                                           // Decode
                                    false,                                          // Should interpolate
                                    kCGRenderingIntentDefault);                     // Intent
Vlad
  • 5,727
  • 3
  • 38
  • 59
0

If you're using garbage collection, use CFMakeCollectable(posterFrame). If you're using traditional memory management, it's very straightforward:

return (CGImageRef)[(id)posterFrame autorelease];

You cast the CFTypeRef (in this case, a CGImageRef) to an Objective-C object pointer, send it the -autorelease message, and then cast the result back to CGImageRef. This pattern works for (almost) any type that's compatible with CFRetain() and CFRelease().

How to Autorelease a CGImageRef?

Community
  • 1
  • 1
rbbtsn0w
  • 75
  • 11