对灰度 NSImage(或 CIImage)进行着色

问题描述 投票:0回答:9

我有一个灰度图像,我想用它来绘制 Cocoa 控件。图像具有不同的灰度级别。在最暗的地方,我希望它绘制最暗的指定色调。我希望它在源图像是白色的地方是透明的。

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

到目前为止,我已经探索了几种选择:

  • 使用 SourceOver 合成在灰度图像上绘制色调颜色 -> 这需要非不透明的色调 -> 结果比预期的要暗得多

  • 使用 CIMultiplyCompositing CIFilter 对图像进行着色 -> 我无法 [CIImage drawAtPoint:fromRect:operation:fraction:] 只绘制图像的一部分。 NSImage 同样可以正常工作 -> 我偶尔会遇到无法理解的崩溃

  • 将灰度图像转换为蒙版。 IE。黑色应该是不透明的。白色应该是透明的。灰色应该具有中间的 alpha 值。 -> 这似乎是最好的解决方案 -> 尽管我尽我所能,但我无法实现这一点。

objective-c cocoa core-image
9个回答
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;
}

15
投票

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
}

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

8
投票

我想用具有 alpha 的色调颜色对图像进行着色,但看不到图像的原始颜色显示出来。您可以这样做:

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
投票

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];

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

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
}

0
投票

为了更细粒度,您可以使用多项式颜色方法,使用我在下面建议的方法:如何以不同的颜色显示旋转的 NSProgressIndicator?


0
投票

自 2023 年起,

lockFocus
已弃用。 我使用符号配置让它像这样工作(对不起,在 Swift 中):

let config = NSImage.SymbolConfiguration(paletteColors: [.systemTeal, .systemGray])
let nuImage = image.withSymbolConfiguration(config)
© www.soinside.com 2019 - 2024. All rights reserved.