Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 619
Pierre Bernard Avatar asked Sep 11 '09 20:09

Pierre Bernard


4 Answers

The above solution didn't work for me. But this much easier solution works great for me

- (NSImage *)imageTintedWithColor:(NSColor *)tint
{
    NSImage *image = [self copy];
    if (tint) {
        [image lockFocus];
        [tint set];
        NSRect imageRect = {NSZeroPoint, [image size]};
        NSRectFillUsingOperation(imageRect, NSCompositeSourceIn);
        [image unlockFocus];
    }
    return image;
}
like image 79
bluebamboo Avatar answered Oct 13 '22 19:10

bluebamboo


A Swift implementation of bluebamboo's answer:

func tintedImage(_ image: NSImage, tint: NSColor) -> NSImage {
    guard let tinted = image.copy() as? NSImage else { return image }
    tinted.lockFocus()
    tint.set()

    let imageRect = NSRect(origin: NSZeroPoint, size: image.size)
    NSRectFillUsingOperation(imageRect, .sourceAtop)

    tinted.unlockFocus()
    return tinted
}
like image 34
Jeff Hay Avatar answered Oct 13 '22 18:10

Jeff Hay


- (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];
}
like image 9
Pierre Bernard Avatar answered Oct 13 '22 18:10

Pierre Bernard


I wanted to tint an image with a tint color that had alpha without seeing the original colors of the image show through. Here's how you can do that:

extension NSImage {
    func tinting(with tintColor: NSColor) -> NSImage {
        guard let cgImage = self.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return self }

        return NSImage(size: size, flipped: false) { bounds in
            guard let context = NSGraphicsContext.current?.cgContext else { return false }

            tintColor.set()
            context.clip(to: bounds, mask: cgImage)
            context.fill(bounds)

            return true
        }
    }
}
like image 8
Sam Soffes Avatar answered Oct 13 '22 18:10

Sam Soffes