将灰度NSImage(或CIImage)着色

32

我有一张灰度图像,想用它来绘制Cocoa控件。该图像具有各种灰度级别。在最暗的地方,我希望它以指定的深色色调进行绘制。在源图像为白色的地方,它应该是透明的。

基本上,我想复制在iPhone上看到的UINavigationBar中tintColor的行为。

到目前为止,我已经尝试了几个选项:

  • 使用SourceOver组合将着色色彩绘制在灰度图像上 -> 这需要一个非不透明的着色色彩 -> 结果比预期的要暗得多

  • 使用CIMultiplyCompositing CIFilter来着色图像 -> 我无法使用[CIImage drawAtPoint:fromRect:operation:fraction:]仅绘制图像的一部分,但同样适用于NSImage -> 我偶尔会遇到无法理解的崩溃

  • 将灰度图像转换为掩模。即,黑色应该是不透明的。白色应该是透明的。灰色应该具有中间的alpha值。 -> 这似乎是最好的解决方案 -> 尽管我努力尝试,但无法实现


我已决定将着色和裁剪分为两个连续的步骤。只要我绘制整个图像,CIMultiplyCompositing就可以正常工作。我绘制到一个NSImage中,然后部分地绘制到较小的图像中。 - Pierre Bernard
10个回答

44

上面的解决方案对我没用。但是这个更简单的解决方案对我非常有效。

- (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;
}

4
希望这对未来的某个人有所帮助:如果您在运行此代码时遇到问题,请将复制的图像设置为非模板,即在返回图像之前,[image setTemplate:NO]。有些不符合直觉的是,如果将图像设置为模板,它将完全忽略代码中着色的图像,尽管调试器预览会显示图像正确着色。我个人遇到了这个问题,因为我正在与一个iOS项目共享资产目录,其中所有图像集都被设置为模板。 - user3062913
一切都运作正常,但是我必须在资产目录中关闭模板图片才能使其工作。非常重要的一点,感谢! - Bill Burgess
这个可以运行,但会使我的图像变得模糊。有人遇到过类似的问题吗? - Noah Nuebling
NSImage lockFocus 在 macOS 13.0 版本之后已被弃用。 - HangarRash
@HangarRash 使用imagewithsize:flipped:drawingHandler:,您将获得相同的结果。 - Marek H

15

一个 Swift 实现 bluebamboo 的答案:

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
}

2
感谢您发布Swift翻译。我是即兴翻译的,所以我翻译了第一个答案。下次我可以直接在这里向下滚动并用Swift获取它。太棒了! - Farini
谢谢,这对我非常有效。 顺便提一下(至少在 macOS 上),如果您使用的图像设置为“模板”呈现,则无法正常工作,请选择“默认”呈现。 - Ricardo Barroso
3
对我来说,我必须使用 imageRect.fill(using:) - Jeeter
你需要使用.sourceIn来正确地进行着色,特别是如果你想用较浅的色调替换原始颜色。 - strangetimes
NSImage lockFocus 在 macOS 13.0 中已被弃用。 - HangarRash

9
- (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];
}

2
对于像我这样的新手来说,弄清楚如何使用这段代码确实需要不必要的时间:1-链接QuartzCore 2-导入它 3-添加一个包含这个神奇代码的NSImage类别。 - Mazyod
2
是的,我同意。这段代码很棒...但没有明确表示它是一个类别会让很多人感到困惑。所以对于那些不知道什么是类别的人来说,请先保存这段代码,弄清楚类别是什么,然后再使用它。∀Ⓛ∃✖ - Alex Gray
虽然我理解这段代码,但似乎无法得到任何结果。我使用一张带有全黑图标和透明背景的tiff图像,并尝试使用白色或黑色着色该图像。但它仍然是黑色的。 - glasz
1
我已经将稍微更新的上述代码版本发布到GitHub:https://github.com/gloubibou/NSImage-HHTint - Pierre Bernard
啊,我现在明白了。谢谢你的分享。不过我好像无法弄清如何在白色底图上实现这个。我想我得调整CIMultiplyCompositing滤镜或者替换为另一个滤镜... - glasz
显示剩余2条评论

8
我想使用带有透明度的着色颜色对图像进行着色,而不会看到原始颜色穿透。以下是您可以执行此操作的方法:

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
        }
    }
}

4

以扩展的形式编写的 Swift 版本:

extension NSImage {
    func tintedImageWithColor(color:NSColor) -> NSImage {
        let size        = self.size
        let imageBounds = NSMakeRect(0, 0, size.width, size.height)
        let copiedImage = self.copy() as! NSImage

        copiedImage.lockFocus()
        color.set()
        NSRectFillUsingOperation(imageBounds, .CompositeSourceIn)
        copiedImage.unlockFocus()

        return copiedImage
    }
}

根据新的Swift API指南,您可以将定义更改为:func tinted(color: NSColor) -> NSImage - Juul
谢谢,我会检查代码是否在Swift 3.0下编译,并更新答案。 - CryingHippo
lockFocus方法在macOS 13.0版本之后已被弃用。 - HangarRash

4
CIMultiplyCompositing滤镜绝对是实现这一目的的方式。如果它崩溃了,您能否发布一下堆栈跟踪?我经常使用CIFilters,但没有崩溃问题。
//assume inputImage is the greyscale CIImage you want to tint

CIImage* outputImage = nil;

//create some green
CIFilter* greenGenerator = [CIFilter filterWithName:@"CIConstantColorGenerator"];
CIColor* green = [CIColor colorWithRed:0.30 green:0.596 blue:0.172];
[greenGenerator setValue:green forKey:@"inputColor"];
CIImage* greenImage = [greenGenerator valueForKey:@"outputImage"];

//apply a multiply filter
CIFilter* filter = [CIFilter filterWithName:@"CIMultiplyCompositing"];
[filter setValue:greenImage forKey:@"inputImage"];
[filter setValue:inputImage forKey:@"inputBackgroundImage"];
outputImage = [filter valueForKey:@"outputImage"];

[outputImage drawAtPoint:NSZeroPoint fromRect:NSRectFromCGRect([outputImage extent]) operation:NSCompositeCopy fraction:1.0];

1
你真的需要使用“CIConstantColorGenerator”吗?为什么不使用CIImage *greenImage = [CIImage imageWithColor: green];? - cocoafan
可以的。那种方法只是在10.5中引入的,而当我写下这个答案时,老虎仍然很常见。 - Rob Keniger
代码的最后一行的目的是什么?输出图像不是已经在前一行生成了吗? - Duck
这只是一个绘制CIImage的示例,实际上并不需要获取图像本身。正如您所指出的那样,您已经从前一行代码中获得了实际图像。 - Rob Keniger

2

Swift 5 版本还可以处理色调颜色的 alpha 分量。

我使用这个功能来支持多种图标状态的黑暗模式,通过将模板图标转换为不同的颜色和透明度级别。例如,您可以传递 NSColor(white: 0, alpha: 0.5) 来获取一个在浅色模式下变暗的图标版本,以及 NSColor(white: 1, alpha: 0.5) 来获取一个在深色模式下变暗的图标版本。

func tintedImage(_ image: NSImage, color: NSColor) -> NSImage {
    let newImage = NSImage(size: image.size)
    newImage.lockFocus()

    // Draw with specified transparency
    let imageRect = NSRect(origin: .zero, size: image.size)
    image.draw(in: imageRect, from: imageRect, operation: .sourceOver, fraction: color.alphaComponent)

    // Tint with color
    color.withAlphaComponent(1).set()
    imageRect.fill(using: .sourceAtop)

    newImage.unlockFocus()
    return newImage
}

1
只是将已弃用的 - (void)lockFocus; 重写为 imageWithSize:flipped:drawingHandler:
@interface NSImage(Additions)
- (NSImage *)imageTintedWithColor:(NSColor *)tint;
@end

@implementation NSImage(Additions)

- (NSImage *)imageTintedWithColor:(NSColor *)tint
{
    NSImage *copy = [self copy]; // we need to break block retain cycle
    NSImage *image = [NSImage imageWithSize:copy.size flipped:NO drawingHandler:^BOOL(NSRect dstRect) {
        [tint set];
        NSRect imageRect = {NSZeroPoint, [copy size]};
        [copy drawInRect:imageRect];
        NSRectFillUsingOperation(imageRect, NSCompositingOperationSourceIn);
        return YES;
    }];
    return image;
}

0

0
从macOS 13.0开始,lockFocus已被弃用。 我使用符号配置使其正常工作。
let config = NSImage.SymbolConfiguration(paletteColors: [.systemTeal, .systemGray])
let nuImage = image.withSymbolConfiguration(config)

文档中关于已弃用的lockFocus方法的说明展示了应该使用的替代方法。 - HangarRash

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接