【问题标题】:Tinting a grayscale NSImage (or CIImage)为灰度 NSImage(或 CIImage)着色
【发布时间】:2010-11-27 15:23:16
【问题描述】:

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

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

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

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

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

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

【问题讨论】:

  • 我已经决定在两个连续的步骤中进行着色和裁剪。只要我绘制整个图像,CIMultiplyCompositing 就可以正常工作。我绘制到一个 NSImage,然后我部分地绘制到一个较小的图像。

标签: objective-c cocoa core-image


【解决方案1】:

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
}

【讨论】:

    【解决方案2】:

    上述解决方案对我不起作用。但是这个更简单的解决方案对我很有效

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

    【讨论】:

    • 希望这对将来的某人有用:如果您在使此代码正常工作时遇到问题,请将复制的图像设置为不是模板,即在返回图像之前,[image setTemplate:NO] .有点违反直觉,如果图像被设置为模板,它将完全忽略为图像着色的代码,即使调试器预览会显示图像被正确着色。我个人遇到了这个问题,因为我正在与一个 iOS 项目共享一个资产目录,其中图像集都设置为模板。
    • 一切正常,但我必须关闭资产目录中的模板图像才能正常工作。非常重要的一点,谢谢!
    • 这可行,但它使我的图像模糊。有没有人有类似的问题?
    【解决方案3】:

    扩展形式的 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
    • 谢谢,我会检查代码是否在swift 3.0下编译并更新答案。
    【解决方案4】:

    我想用具有 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
            }
        }
    }
    

    【讨论】:

      【解决方案5】:

      bluebamboo 答案的 Swift 实现:

      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
      }
      

      【讨论】:

      • 感谢您发布 Swift 翻译。我是即时完成的,所以我翻译了第一个答案。下次我可以在这里向下滚动并在 Swift 中获取它。太棒了!
      • 感谢这对我来说就像一个魅力。只是一个注释快速注释(至少在 macOS 上)如果您使用的图像设置为呈现为“模板”,则它不起作用,请选择呈现为“默认”。
      • 对我来说,我不得不使用imageRect.fill(using:)
      • 您需要使用.sourceIn 使其正确着色,尤其是当您尝试用较浅的色调替换原始颜色时
      【解决方案6】:

      对于更细粒度,您可以使用多项式颜色方法,使用我在以下建议的方法:How can I display the spinning NSProgressIndicator in a different color?

      【讨论】:

        【解决方案7】:
        - (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];
        }
        

        【讨论】:

        • 对于像我这样的 n00bs 来说,弄清楚如何使用这段代码确实需要不必要的时间:1- 链接 QuartzCore 2- 导入它 3- 添加一个 NSImage 类别,里面有这个很棒的代码。
        • 是的,我同意.. 这段代码很震撼……但没有含蓄地说 这是一个类别 会让很多人目瞪口呆。对于所有愚蠢的人.. 如果您不知道类别是什么.. 保存此代码,找出类别是什么,然后回来使用它。 ∀Ⓛ∃✖
        • 虽然我理解代码,但我似乎无法从中得到任何结果。我给它一个全黑图标和透明背景的 tiff 图像,并尝试用白色或黑色着色。但它保持黑色。
        • 上面的代码会将灰色像素转换为同样深色调的着色像素。如果您的源图像是黑色的,那么结果也将是黑色的。
        • 我还没有将上述代码的稍微更新的版本发布到 GitHub:github.com/gloubibou/NSImage-HHTint
        【解决方案8】:

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

        【讨论】:

        • 你真的需要使用“CIConstantColorGenerator”吗?为什么不 CIImage *greenImage = [CIImage imageWithColor: green]; ?
        • 是的,你可以。该方法仅在 10.5 和 2009 年我写此答案时才引入,Tiger 仍在常用。
        • 最后一行代码的目的是什么?之前的线上不是已经产生了outputImage吗?
        • 这只是一个如何绘制CIImage的例子,实际上并不需要获取图像本身。正如您所指出的,您已经从上一行的代码中获得了实际图像。
        猜你喜欢
        • 1970-01-01
        • 2011-11-23
        • 1970-01-01
        • 1970-01-01
        • 2011-09-01
        • 2012-05-28
        • 2017-07-31
        • 2018-02-08
        相关资源
        最近更新 更多