Fitter Man
Fitter Man

Reputation: 682

Generating thumbnails causes leak (MacOS, Obj C)

I'm working on a MacOS program in Objective C that needs to produce in-memory thumbnails to send to a server. The following code is used to perform this operation. As the program runs, a leak of about 40mb is induced each time this method is called. I'm missing something really basic, I suspect, but I don't see the source of the problem.

I should add that I've also tried creating one context to use over the life of the program and the problem, if anything, seems somewhat worse.

When I run Instruments, the allocations for the category "VM: ImageIO_JPEG_Data" are growing by one allocation of 40mb each time it's called. The responsible library is "ImageIO" and the responsible caller is "ImageIO_Malloc".

- (void) createPhotoThumbnail
{
    NSURL* fileURL = [NSURL fileURLWithPath : _imagePath];

    CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
    CGContextRef bitmapContext = CGBitmapContextCreate(NULL, MAX_THUMB_DIM, MAX_THUMB_DIM, 8, 0,
                                                       colorspace, (CGBitmapInfo)kCGImageAlphaNoneSkipLast);
    CIContext *ciContext = [CIContext contextWithCGContext: bitmapContext options: @{}];

    if (fileURL)
    {
        CIImage *image = [[CIImage alloc] initWithContentsOfURL: fileURL];

        if (image)
        {
            // scale the image
            CIFilter *scaleFilter = [CIFilter filterWithName: @"CILanczosScaleTransform"];
            [scaleFilter setValue: image forKey: @"inputImage"];
            NSNumber *scaleFactor = [[NSNumber alloc] initWithFloat: ((float) MAX_THUMB_DIM) /
                                     ((float)MAX(_processedWidth, _processedHeight))];
            [scaleFilter setValue: scaleFactor forKey: @"inputScale"];
            [scaleFilter setValue: @1.0 forKey: @"inputAspectRatio"];
            CIImage *scaledImage = [scaleFilter valueForKey: @"outputImage"];

            NSMutableData* thumbJpegData = [[NSMutableData alloc] init];
            CGImageDestinationRef dest = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)thumbJpegData,
                                                                          (__bridge CFStringRef)@"public.jpeg",
                                                                          1,
                                                                          NULL);
            if (dest)
            {
                CGImageRef img = [ciContext createCGImage:scaledImage
                                                  fromRect:[scaledImage extent]];
                CGImageDestinationAddImage(dest, img, nil);
                if (CGImageDestinationFinalize(dest))
                {
                    // encode it as a string for later
                    _thumbnail = [thumbJpegData base64EncodedStringWithOptions: 0];
                }
                else
                {
                    DDLogError(@"Failed to generate photo thumbnail");
                }
                CGImageRelease(img);
                CFRelease(dest);
            }
            else
            {
                DDLogError(@"Failed to finalize photo thumbnail image");
            }
            thumbJpegData = nil;
        }
    }

    CGContextRelease(bitmapContext);
    CGColorSpaceRelease(colorspace);
    ciContext = nil;

}

UPDATE: I switched the code to use a CGAffineTransform instead of the filter with "CILanczosScaleTransform" and the symptom did not change. Next I used a completely new method (snippet below) and yet the problem persists.

NSImage *thumbnail = [[NSImage alloc] initWithSize: newSize];

[thumbnail lockFocus];
[sourceImage setSize: newSize];
[[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
[sourceImage compositeToPoint: NSZeroPoint operation: NSCompositeCopy];
[thumbnail unlockFocus];

NSData *tiff = [thumbnail  TIFFRepresentation];
NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData: tiff];
NSDictionary *imageProps = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:0.9] forKey:NSImageCompressionFactor];
NSData *thumbJpegData = [imageRep representationUsingType:NSJPEGFileType properties:imageProps];

This is making me think the problem is perhaps related to something inherent in the way I'm doing this. I find it hard to believe two different methods of image scaling are going to exhibit the same sort of leak.

Upvotes: 2

Views: 314

Answers (2)

Fitter Man
Fitter Man

Reputation: 682

Thanks to this answer I was able to identify the need for an autorelease pool, something I was completely unaware of. The code in the question is one of a series of methods that are called repeatedly from inside a tight loop. This apparently prevents the OS from having a chance to do some cleanup. The block now looks like this:

@autoreleasepool {
    [self findRelevantAdjustments];
    [self adjustForStraightenCrop];
    [self moveFacesRelativeToTopLeftOrigin];
    [self createPhotoThumbnail];
    [self sendPhotoToServer];
}

Moral of the story: even with ARC there are more things to pay attention to when it comes to the memory lifecycle.

Upvotes: 1

Rob
Rob

Reputation: 438152

The problem is not in the CGImageDestinationRef logic, because it still leaks even if you replace that with something far simple, such as:

NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithCIImage:scaledImage];
NSData *data = [rep representationUsingType:NSJPEGFileType properties:nil];

Digging a little further, it would appear that the problem appears to be an issue within CILanczosScaleTransform. If you use an inputScale of @1.0, then the leak disappears. But use something less than @1.0 (even @0.5) and it leaks.

I'd suggest you consider finding a different method for resizing the image.

Upvotes: 0

Related Questions