Swift 核心图像过滤器CISourceOverCompositing在alpha覆盖中未按预期显示

Swift 核心图像过滤器CISourceOverCompositing在alpha覆盖中未按预期显示,swift,ios11,core-image,cifilter,compositing,Swift,Ios11,Core Image,Cifilter,Compositing,我正在使用cisourceovercomposition将文本覆盖在图像顶部,当文本图像不是完全不透明时,我会得到意想不到的结果。在输出图像中,深色不够深,浅色太浅 我以一种新的方式重新创建了这个问题。它使用0.3 alpha绘制的橙色、白色和黑色文本创建一个图像,看起来很正确。我甚至把那个张图片放到了草图上,放在背景图片的上面,看起来很棒。屏幕底部的图像显示了它在草图中的外观。问题是,在使用cisourceovercomposition将文本覆盖在背景上后,白色文本太不透明,就像alpha为0

我正在使用
cisourceovercomposition
将文本覆盖在图像顶部,当文本图像不是完全不透明时,我会得到意想不到的结果。在输出图像中,深色不够深,浅色太浅

我以一种新的方式重新创建了这个问题。它使用0.3 alpha绘制的橙色、白色和黑色文本创建一个图像,看起来很正确。我甚至把那个张图片放到了草图上,放在背景图片的上面,看起来很棒。屏幕底部的图像显示了它在草图中的外观。问题是,在使用
cisourceovercomposition
将文本覆盖在背景上后,白色文本太不透明,就像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!
接下来,该文本图像将覆盖在背景图像的顶部,而背景图像并不像预期的那样显示

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!

最后的答案:CISOURCEOVERCOMPING中的公式是好的。这是正确的做法

但是

它在错误的颜色空间中工作。在图形程序中,您最有可能拥有sRGB颜色空间。在iOS上,使用通用RGB颜色空间。这就是结果不匹配的原因

使用自定义CIFilter,我重新创建了CISourceOverCompositing筛选器。
s1是文本图像。
s2为背景图像

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)
它的核心是:

 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应用程序必须改用设备颜色空间。


用于黑白文本

如果您使用的是
.normal
合成操作,您肯定会得到与使用
.hardLight
不同的结果。您的图片显示了
.hardLight
操作的结果。

bitmapContext.setBlendMode(.sourceAtop)
。正常的
操作是经典的
操作,其公式为:(Image1*A1)+(Image2*(1–A1))

这是一个预乘文本(RGB*a),所以在这种特殊情况下,RGB模式取决于a的不透明度。文本图像的RGB可以包含任何颜色,包括黑色。如果A=0(黑色alpha)和RGB=0(黑色),并且图像是预乘的–整个图像是完全透明的,如果A=1(白色alpha)和RGB=0(黑色)-图像是不透明的黑色

如果在使用
.normal
操作时文本没有alpha,我将获得
添加
操作:Image1+Image2


要获得所需内容,需要将合成操作设置为
.hardLight

.hardLight
合成操作与
.multiply

如果文本图像的alpha小于50%(A<0.5,图像几乎透明)

.multiply
的公式:Image1*Image2


.hardLight
合成操作与
.screen

如果文本图像的alpha大于或等于50%(A>=0.5,则图像为半透明)

屏幕的公式1
(Image1+Image2)–(Image1*Image2)

屏幕的公式2
1–(1-Image1)*(1-Image2)

.screen
操作的结果比
.plus
要柔和得多,并且它允许保持alpha不大于1(
plus
操作将Image1和Image2的alpha相加,因此如果有两个alpha,则可能得到alpha=2)
.screen
合成操作有利于进行反射

因此,要重新创建此合成操作,您需要以下逻辑:

//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 
}
我使用Foundry NUKE 11合成应用程序重新创建了一幅图像。偏移量=0.5这里是加法=0.5

我使用属性
Offset=0.5
,因为
transparency=0.5
.hardLight
合成操作的
枢轴点

用于彩色文本

如果除了黑白文本之外还有橙色(或任何其他颜色)文本,则需要使用
.sourcetop
合成操作。应用
.sourcetop
.setBlendMode
方法,您可以快速使用背景图像的亮度来确定要显示的内容。或者,您可以使用
CISOURCETOPCompositing
核心图像过滤器,而不是
CISourceOverCompositing

bitmapContext.setBlendMode(.sourceAtop)

.sourcetop
操作具有以下公式:(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)


在反复尝试不同的事情之后,(感谢@andy和@Juraj Antas将我推向正确的方向),我终于找到了答案。因此,在核心图形上下文中绘制会产生正确的外观,但使用这种方法绘制图像的成本更高。问题似乎出在
cisourceovercomposition
上,但问题实际上在于,默认情况下,核心图像过滤器工作在线性空间,而核心图形工作在感知空间,这解释了不同的结果。但是,您可以使用不执行颜色管理的核心图像上下文从核心图像过滤器创建核心图形图像,从而匹配核心图形方法的输出。所以原始代码很好,只是输出的图像有点不同

let ciContext = CIContext(options: [kCIContextWorkingColorSpace: NSNull()])
let outputImage = ciContext.createCGImage(outputCIImage, from: outputCIImage.extent) 
//this image appears as expected

但愿我能帮助你。这个问题确实得到了我的回答