inessential by Brent Simmons

Workaround for CIImage to NSImage memory issue

This is another of those things posted for the benefit of other Mac developers using Google to research a problem...

NetNewsWire 3 uses Core Image to do a few things, most noticeably to tint the feed and folder icons to match the subscriptions list background.

Core Image, as I was pleased to discover, makes doing stuff like this really, really easy, and it’s fun to use. It takes just a few lines of code to run a filter on an image.

Now, the only slightly tricky part is I’m working with NSImage everywhere in NetNewsWire, but Core Image uses CIImage. So I have to take an NSImage, create a CIImage based on it, run a filter on the CIImage (the tinting or whatever)—and then create an NSImage from the altered CIImage. (The NSImage is what gets displayed.)

Both Dan Wood and Scott Stevenson have written about doing the conversions, so you can see that it’s not particularly hard.

Anyway...

I’ve been working on NetNewsWire’s memory use by using leaks, ObjectAlloc, and MallocDebug. And I saw that my CIImage to NSImage conversion was using a lot of memory. It wasn’t reported as a leak but as an allocation by MallocDebug—and I saw that memory use kept going up and up when I triggered it.

I discovered that creating an NSImage from a CIImage—if the CIImage has had a filter run on it—can cause unnecessary memory use on some systems. On my development machine, for instance, but not on the PowerBook sitting next to me.

So... after a couple days of doing a bunch of research, emailing with people, pulling hair, reporting a bug to Apple (rdar://5046037), getting help reporting a bug to Apple, complaining on Twitter, complaining to the private testers, explaining the issue to my cat, I finally found a workaround.

Here’s an app (with source) that shows the leak.

What it does is create an NSImage from a CIImage over and over. You can watch the app’s memory use go up in top or Activity Monitor. (Unless it doesn’t, because it doesn’t leak on every system.)

CIImageLeakDemo

It also includes the workaround, which is, in a nutshell, to use the software renderer, rather than the GPU.

For any case where performance is an issue, this workaround isn’t usable. But in my case it’s fine.

The leaking code (re-formatted for the web) looks like this:

NSImage *image = [[[NSImage alloc] initWithSize:
	NSMakeSize([ciImage extent].size.width,
	[ciImage extent].size.height)]
	autorelease];
[image lockFocus];
[[[NSGraphicsContext currentContext] CIContext]
	drawImage:ciImage atPoint:CGPointMake(0, 0)
	fromRect:[ciImage extent]];
	/*This appears to be the line that leaks*/
[image unlockFocus];

The non-leaking code looks like this:

NSImage *image = [[[NSImage alloc] initWithSize:
	NSMakeSize([ciImage extent].size.width,
	[ciImage extent].size.height)]
	autorelease];
[image lockFocus];
CGContextRef contextRef =
	[[NSGraphicsContext currentContext]
	graphicsPort];
CIContext *ciContext =
	[CIContext contextWithCGContext:contextRef
	options:[NSDictionary dictionaryWithObject:
	[NSNumber numberWithBool:YES]
	forKey:kCIContextUseSoftwareRenderer]];
[ciContext drawImage:ciImage
	atPoint:CGPointMake(0, 0) fromRect:[ciImage extent]];
	/*Does not leak when using the software renderer!*/
[image unlockFocus];

My hope, of course, is that this workaround is needed temporarily, that it will get fixed. Obviously, using the software renderer defeats much of what’s cool about Core Image. But, for now, hey, cool, no leak, at least.