2012-02-14

It is funny that there are three similar but different color systems in OS X. Cocoa provides NSColor and NSColorSpace which play well with the AppKit and Foundation frameworks. Quartz provides CGColor and CGColorSpace which play well with Core Foundation and Core Animation. Core Image provides its own CIColor class which uses Quartz's CGColorSpace. There are several convenience methods for moving around this triangle of formats, but some parts are missing.

The missing methods would be nice to have when working with CGColors in Core Animation but storing NSColor values in an NSDictionary. The NSColor+ColorSystemConversions category below provides the missing conversion from CIColor to CGColor and implements the remaining side of the triangle by converting the other formats through CIColor. The #if 0 blocks show how to do the conversion directly by extracting the component arrays of the source color.

@interface NSColor (ColorSystemConversions)
+(CGColorRef) CIColorToCGColor: (CIColor *) ciColor;
+(CGColorRef) NSColorToCGColor: (NSColor *) nsColor;
+(NSColor *) CGColorToNSColor: (CGColorRef) cgColor;
@end

// --------------------------------------------------------------------------------

@implementation NSColor (ColorSystemConversions)
+(CGColorRef) CIColorToCGColor: (CIColor *) ciColor {
	CGColorSpaceRef colorSpace = [ciColor colorSpace];
	const CGFloat *components = [ciColor components];
	CGColorRef cgColor = CGColorCreate (colorSpace, components);
	CGColorSpaceRelease(colorSpace);
	return cgColor;
}
+(CGColorRef) NSColorToCGColor: (NSColor *) nsColor {
	CIColor *ciColor = [[CIColor alloc] initWithColor: nsColor];
	CGColorRef cgColor = [NSColor CIColorToCGColor: ciColor];
	[ciColor release];
	return cgColor;
}
+(NSColor *) CGColorToNSColor: (CGColorRef) cgColor {
	return [NSColor colorWithCIColor: [CIColor colorWithCGColor: cgColor]];
}
@end

An alternative implementation would access the color components directly. My current application is not sensitive to the extra allocation, so I have not done any time tests.

+(CGColorRef) NSColorToCGColor: (NSColor *) nsColor {
	CGColorSpaceRef colorSpace = [[nsColor colorSpace] CGColorSpace];
	NSInteger noc = [nsColor numberOfComponents];
	CGFloat *components = malloc( sizeof(CGFloat)*(1+noc));
	[nsColor getComponents: components];
	
	CGColorRef cgColor = CGColorCreate (colorSpace, components);
	free(components);
	return cgColor;
}
+(NSColor *) CGColorToNSColor: (CGColorRef) cgColor {
	CGColorSpaceRef cgColorSpace = CGColorGetColorSpace( cgColor );
	size_t numberOfComponents = CGColorGetNumberOfComponents (cgColor);
	const CGFloat *components = CGColorGetComponents (cgColor);
	
	NSColorSpace *space = [[NSColorSpace alloc] initWithCGColorSpace: cgColorSpace];
	NSColor *color = [NSColor colorWithColorSpace: space components: components count: numberOfComponents];
	[space release];

	return color;
}

For some applications, you may not need to convert the colors at all. You could save a CGColorRef in an NSMutableDictionary by wrapping it in an NSValue. Then you would have to remember to insert CGColorRetain() and CGColorRelease() calls at the appropriate points.