views:

1110

answers:

6

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.

A: 

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
A: 

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.

mcfrei
You shouldn't `-release` after `-drain` (which is equivalent to `-release` on non-garbage collected runtimes) as that causes double release.
KennyTM
+1  A: 

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
Steve Madsen
A: 

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
A: 

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
A: 

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.

Digital Robot