Search code examples
objective-ccocoacore-image

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.


Solution

  • - (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
                            fromRect:bounds
                           operation:NSCompositeCopy
                            fraction:1.0];
    
            [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];
    }