Pierre Bernard Pierre Bernard - 11 months ago 166
Objective-C Question

Tinting a grayscale NSImage (or CIImage)

I have a grayscale image which I want to use for drawing Cocoa controls. The image has various levels of gray. Where it is darkest, I want it to draw a specified tint color darkest. I want it to be transparent where the source image is white.

Basically, I want to reproduce the behavior of tintColor seen in UINavigationBar on the iPhone.

So far, I have explored several options:

  • Draw the tint color over the grayscale image using SourceOver composition
    -> This requires a non-opaque tint color
    -> The result comes out much darker than desired

  • Use a CIMultiplyCompositing CIFilter to tint the image
    -> I can't [CIImage drawAtPoint:fromRect:operation:fraction:] to draw only part of the image. The same works fine with NSImage
    -> I get occasional crashes which I cannot make sense of

  • Transform the grayscale image into a mask. I.e. Black should be opaque. White should be transparent. Gray should have intermediate alpha values.
    -> This would seem to be the best solution
    -> Try as I might, I cannot achieve this.

Answer Source
- (NSImage *)imageTintedWithColor:(NSColor *)tint 
    if (tint != nil) {
    	NSSize size = [self size];
    	NSRect bounds = { NSZeroPoint, size };
    	NSImage *tintedImage = [[NSImage alloc] initWithSize:size];

    	[tintedImage lockFocus];

    	CIFilter *colorGenerator = [CIFilter filterWithName:@"CIConstantColorGenerator"];
    	CIColor *color = [[[CIColor alloc] initWithColor:tint] autorelease];

    	[colorGenerator setValue:color forKey:@"inputColor"];

    	CIFilter *monochromeFilter = [CIFilter filterWithName:@"CIColorMonochrome"];
    	CIImage *baseImage = [CIImage imageWithData:[self TIFFRepresentation]];

    	[monochromeFilter setValue:baseImage forKey:@"inputImage"];		
    	[monochromeFilter setValue:[CIColor colorWithRed:0.75 green:0.75 blue:0.75] forKey:@"inputColor"];
    	[monochromeFilter setValue:[NSNumber numberWithFloat:1.0] forKey:@"inputIntensity"];

    	CIFilter *compositingFilter = [CIFilter filterWithName:@"CIMultiplyCompositing"];

    	[compositingFilter setValue:[colorGenerator valueForKey:@"outputImage"] forKey:@"inputImage"];
    	[compositingFilter setValue:[monochromeFilter valueForKey:@"outputImage"] forKey:@"inputBackgroundImage"];

    	CIImage *outputImage = [compositingFilter valueForKey:@"outputImage"];

    	[outputImage drawAtPoint:NSZeroPoint

    	[tintedImage unlockFocus];  

    	return [tintedImage autorelease];
    else {
    	return [[self copy] autorelease];

- (NSImage*)imageCroppedToRect:(NSRect)rect
    NSPoint point = { -rect.origin.x, -rect.origin.y };
    NSImage *croppedImage = [[NSImage alloc] initWithSize:rect.size];

    [croppedImage lockFocus];
    	[self compositeToPoint:point operation:NSCompositeCopy];
    [croppedImage unlockFocus];

    return [croppedImage autorelease];