核心图像过滤器 CISourceOverCompositing 未按预期显示与 alpha 叠加
Posted
技术标签:
【中文标题】核心图像过滤器 CISourceOverCompositing 未按预期显示与 alpha 叠加【英文标题】:Core Image filter CISourceOverCompositing not appearing as expected with alpha overlay 【发布时间】:2018-08-04 18:30:45 【问题描述】:我正在使用CISourceOverCompositing
将文本覆盖在图像之上,当文本图像不完全不透明时,我得到了意想不到的结果。输出图像中深色不够暗,浅色太亮。
我在simple Xcode project 中重新创建了该问题。它使用 0.3 alpha 绘制的橙色、白色、黑色文本创建图像,并且看起来正确。我什至将该图像放入 Sketch 中,将其放在背景图像的顶部,它看起来很棒。屏幕底部的图像显示了它在 Sketch 中的外观。问题是,在使用CISourceOverCompositing
覆盖背景上的文本后,白色文本太不透明,好像 alpha 为 0.5,黑色文本几乎不可见,好像 alpha 为 0.1。顶部图像显示了以编程方式创建的图像。您可以拖动滑块来调整 alpha(默认为 0.3),这将重新创建结果图像。
代码当然包含在项目中,但也包含在这里。这将创建具有 0.3 alpha 的文本叠加层,并按预期显示。
let colorSpace = CGColorSpaceCreateDeviceRGB()
let alphaInfo = CGImageAlphaInfo.premultipliedLast.rawValue
let bitmapContext = CGContext(data: nil, width: Int(imageRect.width), height: Int(imageRect.height), bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace, bitmapInfo: alphaInfo)!
bitmapContext.setAlpha(0.3)
bitmapContext.setTextDrawingMode(CGTextDrawingMode.fill)
bitmapContext.textPosition = CGPoint(x: 20, y: 20)
let displayLineTextWhite = CTLineCreateWithAttributedString(NSAttributedString(string: "hello world", attributes: [.foregroundColor: UIColor.white, .font: UIFont.systemFont(ofSize: 50)]))
CTLineDraw(displayLineTextWhite, bitmapContext)
let textCGImage = bitmapContext.makeImage()!
let textImage = CIImage(cgImage: textCGImage)
接下来,该文本图像覆盖在背景图像之上,但未按预期显示。
let combinedFilter = CIFilter(name: "CISourceOverCompositing")!
combinedFilter.setValue(textImage, forKey: "inputImage")
combinedFilter.setValue(backgroundImage, forKey: "inputBackgroundImage")
let outputImage = combinedFilter.outputImage!
【问题讨论】:
希望我能帮助你。这个问题确实引起了我的注意。 Apple 文档建议检查它使用的公式 (keithp.com/~keithp/porterduff/p253-porter.pdf)。你检查过吗?特别是第 4 页,第 4.3 节?我目前对 CI 的使用有点“希腊”,但也许它对你有帮助?似乎 multiplying alpha 可能会意外发生? 感谢@dfd,这是个好主意,但我不明白怎么做。如果您有什么特别之处,我已经用更多细节和示例项目更新了这个问题! @Joey 你不会相信但我有同样的问题所以我尝试了很多但没有得到任何合适的解决方案所以我尝试了一个技巧我只是在图像后面放了一个白色的 UIView 所以它工作得很好。所以尝试一下可能会有所帮助:) 【参考方案1】:在反复尝试不同的事情之后,(感谢@andy 和@Juraj Antas 将我推向正确的方向)我终于有了答案。
因此,在 Core Graphics 上下文中绘制会产生正确的外观,但使用该方法绘制图像需要更多资源。似乎问题出在CISourceOverCompositing
,但问题实际上在于,默认情况下,Core Image 过滤器在线性 SRGB 空间中工作,而 Core Graphics 在感知 RGB 空间中工作,这解释了不同的结果 - sRGB 在保留深黑色,linearSRGB 更擅长保留亮白色。所以原代码是没问题的,但是你可以用不同的方式输出图像,得到不同的外观。
您可以使用不执行颜色管理的 Core Image 上下文从 Core Image 过滤器创建 Core Graphics 图像。这实质上会导致它“错误地”将颜色值解释为设备 RGB(因为这是没有颜色管理的默认值),这可能会导致标准颜色范围中的红色显示为例如宽颜色范围中的更多红色。但这解决了最初对 alpha 合成的担忧。
let ciContext = CIContext(options: [.workingColorSpace: NSNull()])
let outputCGImage = ciContext.createCGImage(outputCIImage, from: outputCIImage.extent)
可能更希望保持启用颜色管理并将工作颜色空间指定为 sRGB。这也解决了问题并导致“正确”的颜色解释。请注意,如果合成的图像是 Display P3,您需要将 displayP3 指定为工作颜色空间以保留宽色域。
let workingColorSpace = CGColorSpace(name: CGColorSpace.sRGB)!
let ciContext = CIContext(options: [.workingColorSpace: workingColorSpace])
let outputCGImage = ciContext.createCGImage(outputCIImage, from: outputCIImage.extent)
【讨论】:
我还添加了outputColorSpace
。对于 Swift 4:let ciContext = CIContext(options: [CIContextOption.outputColorSpace: NSNull(), CIContextOption.workingColorSpace: NSNull()])
【参考方案2】:
用于黑白文本
如果您使用.normal
合成操作,您肯定会得到与使用.hardLight
不同的结果。您的图片显示了.hardLight
操作的结果。
.normal
操作是经典的OVER
运算,公式为:(Image1 * A1) + (Image2 * (1 – A1))。
这是一个预乘文本 (RGB*A),因此在这种特殊情况下,RGB 模式取决于 A 的不透明度。文本图像的 RGB 可以包含任何颜色,包括黑色。如果 A=0(黑色 alpha)和 RGB=0(黑色)并且您的图像是预乘的——整个图像是完全透明的,如果 A=1(白色 alpha)和 RGB=0(黑色)——图像是不透明的黑色。
如果你使用.normal
操作时你的文字没有alpha,我会得到ADD
op: Image1 + Image2。
要得到你想要的,你需要设置一个合成操作到.hardLight
。
.hardLight
合成操作为.multiply
如果文本图像的 alpha 小于 50%(A
.multiply
的公式:Image1 * Image2
.hardLight
合成操作为.screen
如果文本图像的 alpha 大于或等于 50%(A >= 0.5,则图像是半透明的)
.screen
的公式 1:(Image1 + Image2) – (Image1 * Image2)
.screen
的公式 2:1 – (1 – Image1) * (1 – Image2)
.screen
操作的结果比.plus
柔和得多,并且它允许保持 alpha 不大于 1(plus
操作添加 Image1 和 Image2 的 alpha,所以如果你有两个,你可能会得到 alpha = 2阿尔法)。 .screen
合成操作很适合做反射。
func editImage()
print("Drawing image with \(selectedOpacity) alpha")
let text = "hello world"
let backgroundCGImage = #imageLiteral(resourceName: "background").cgImage!
let backgroundImage = CIImage(cgImage: backgroundCGImage)
let imageRect = backgroundImage.extent
//set up transparent context and draw text on top
let colorSpace = CGColorSpaceCreateDeviceRGB()
let alphaInfo = CGImageAlphaInfo.premultipliedLast.rawValue
let bitmapContext = CGContext(data: nil, width: Int(imageRect.width), height: Int(imageRect.height), bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace, bitmapInfo: alphaInfo)!
bitmapContext.draw(backgroundCGImage, in: imageRect)
bitmapContext.setAlpha(CGFloat(selectedOpacity))
bitmapContext.setTextDrawingMode(.fill)
//TRY THREE COMPOSITING OPERATIONS HERE
bitmapContext.setBlendMode(.hardLight)
//bitmapContext.setBlendMode(.multiply)
//bitmapContext.setBlendMode(.screen)
//white text
bitmapContext.textPosition = CGPoint(x: 15 * UIScreen.main.scale, y: (20 + 60) * UIScreen.main.scale)
let displayLineTextWhite = CTLineCreateWithAttributedString(NSAttributedString(string: text, attributes: [.foregroundColor: UIColor.white, .font: UIFont.systemFont(ofSize: 58 * UIScreen.main.scale)]))
CTLineDraw(displayLineTextWhite, bitmapContext)
//black text
bitmapContext.textPosition = CGPoint(x: 15 * UIScreen.main.scale, y: 20 * UIScreen.main.scale)
let displayLineTextBlack = CTLineCreateWithAttributedString(NSAttributedString(string: text, attributes: [.foregroundColor: UIColor.black, .font: UIFont.systemFont(ofSize: 58 * UIScreen.main.scale)]))
CTLineDraw(displayLineTextBlack, bitmapContext)
let outputImage = bitmapContext.makeImage()!
topImageView.image = UIImage(cgImage: outputImage)
因此,要重新创建此合成操作,您需要以下逻辑:
//rgb1 – text image
//rgb2 - background
//a1 - alpha of text image
if a1 >= 0.5
//use this formula for compositing: 1–(1–rgb1)*(1–rgb2)
else
//use this formula for compositing: rgb1*rgb2
我使用合成应用程序 The Foundry NUKE 11 重新创建了一个图像。这里的 Offset=0.5 是 Add=0.5。
我使用属性Offset=0.5
,因为transparency=0.5
是pivot point
的.hardLight
合成操作。
彩色文本
您需要使用.sourceAtop
合成操作,以防除了黑白文本之外还有橙色(或任何其他颜色)文本。应用.sourceAtop
的.setBlendMode
方法,您可以让Swift 使用背景图像的亮度来确定要显示的内容。或者,您可以使用CISourceAtopCompositing
核心图像过滤器而不是CISourceOverCompositing
。
bitmapContext.setBlendMode(.sourceAtop)
或
let compositingFilter = CIFilter(name: "CISourceAtopCompositing")
.sourceAtop
运算公式如下:(Image1 * A2) + (Image2 * (1 – A1))。如您所见,您需要两个 Alpha 通道:A1 是文本的 Alpha,A2 是背景图像的 Alpha。
bitmapContext.textPosition = CGPoint(x: 15 * UIScreen.main.scale, y: (20 + 60) * UIScreen.main.scale)
let displayLineTextOrange = CTLineCreateWithAttributedString(NSAttributedString(string: text, attributes: [.foregroundColor: UIColor.orange, .font: UIFont.systemFont(ofSize: 58 * UIScreen.main.scale)]))
CTLineDraw(displayLineTextOrange, bitmapContext)
【讨论】:
几乎是正确的,但这取决于图像的颜色而不是 alpha 使用哪种混合模式。 在这种情况下它取决于两者:RGB 和 A。应用于 RGB 的数学,alpha 控制不透明度。因为这里是 RGB*A 模式(预乘图像)。 你知道数学中的 A 是如何计算的吗? 你介意把它写在这里吗?我正在自定义 CIFilter 中进行测试。 具体要写什么?【参考方案3】:最终答案:CISourceOverCompositing 中的公式很好。这是正确的做法。
但是
它在错误的色彩空间中工作。在图形程序中,您很可能拥有 sRGB 色彩空间。在 ios 上使用通用 RGB 颜色空间。这就是结果不匹配的原因。
使用自定义 CIFilter 我重新创建了 CISourceOverCompositing 过滤器。 s1 是文字图片。 s2 是背景图片。
它的内核是这样的:
kernel vec4 opacity( __sample s1, __sample s2)
vec3 text = s1.rgb;
float textAlpha = s1.a;
vec3 background = s2.rgb;
vec3 res = background * (1.0 - textAlpha) + text;
return vec4(res, 1.0);
因此,要解决此颜色“问题”,您必须将文本图像从 RGB 转换为 sRGB。我想你的下一个问题将是如何做到这一点;)
重要提示:iOS 不支持独立于设备或通用颜色空间。 iOS 应用程序必须改用设备色彩空间。 Apple doc about color spaces
【讨论】:
感谢您的回答!不幸的是,虽然使用我进行的 50% 灰度测试,这看起来符合预期,但由于叠加混合模式,它在真实照片上看起来并不正确。我更新了项目以使用更清楚地展示问题的真实照片,并添加了您提出的更改,您可以取消注释以使用真实背景图像进行尝试。 尝试将 .overlay 与 .hardLight 交换。结果是你追求的吗?如果不仅其他方式是自定义 CIFilter。 Hm 强光适用于黑白文本,但尝试橙色文本时,它显示为红色。 我正在测试自定义 CIFilter,我需要为“正常”混合模式找到好的数学公式。使用那里的公式:simplefilter.de/en/basics/mixmods.html 使用自定义 CIFilter 我得到了与使用 CISourceOverCompositing 相同的结果。 CISourceOverCompositing公式为:vec3 res = background * (1.0 - textAlpha) + text; vec3 是 rgb 颜色向量。现在的问题仍然是图形程序用于“正常”混合模式的正确合成公式是什么。很难找到它。以上是关于核心图像过滤器 CISourceOverCompositing 未按预期显示与 alpha 叠加的主要内容,如果未能解决你的问题,请参考以下文章