Core Image过滤器CISourceOverCompositing没有按预期显示alpha覆盖

时间:2018-02-25 00:59:14

标签: swift ios11 core-image cifilter compositing

我正在使用CISourceOverCompositing在图片顶部叠加文字,当文字图片不完全不透明时,我会收到意想不到的结果。深色不够暗,输出图像中的浅色太亮。

我在simple Xcode project中重新创建了该问题。它创建一个图像,其中橙色,白色,黑色文本用0.3 alpha绘制,看起来是正确的。我甚至将该图像投入草图,将其放在背景图像的顶部,看起来很棒。屏幕底部的图像显示了Sketch中的外观。问题是,在使用CISourceOverCompositing将文本覆盖在背景上之后,白色文本太不透明,好像alpha为0.5,而黑色文本几乎不可见,就好像alpha为0.1。顶部图像显示以编程方式创建的图像。您可以拖动滑块来调整alpha(默认值为0.3),这将重新创建结果图像。

enter image description here

代码当然包含在项目中,但也包含在此处。这将创建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!

3 个答案:

答案 0 :(得分:3)

FOR BLACK-AND-WHITE TEXT

如果您正在使用.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操作: Image1 + Image2

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

.hardLight合成操作的作用为.multiply

  

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

.multiply的公式: Image1 * Image2

.hardLight合成操作的作用为.screen

  

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

.screen的公式1:(图片1 +图片2) - (图片1 *图片2)

.screen的公式2: 1 - (1 - Image1)*(1 - Image2)

.screen操作的结果比.plus更柔和,并且它允许保持alpha不大于1(plus操作会添加Image1和Image2的alphas,因此您可能会得到alpha = 2,如果你有两个alphas)。 .screen合成操作有利于反思。

enter image description here

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重新创建了一个图像。偏移= 0.5这里是Add = 0.5。

我使用了属性Offset=0.5,因为transparency=0.5pivot point合成操作的.hardLight

enter image description here

enter image description here

FOR COLOR TEXT

如果除了B&amp; W文本外还有ORANGE(或任何其他颜色)文本,则需要使用.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)

enter image description here

enter image description here

答案 1 :(得分:1)

最终答案:CISourceOverCompositing中的公式很好。这是正确的事情。

BUT

它在错误的色彩空间中工作。在图形程序中,您最有可能拥有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);
 }

所以要解决这个问题&#39;问题&#39;您必须将文本图像从RGB转换为sRGB。我想你的下一个问题是如何做到这一点;)

重要提示:iOS不支持与设备无关或通用的色彩空间。 iOS应用程序必须使用设备颜色空间。 Apple doc about color spaces

test image with RGB and sRGB color spaces

答案 2 :(得分:1)

经过大量的来回尝试不同的事情,(感谢@andy和@Juraj Antas推动我朝着正确的方向前进)我终于得到了答案。因此,绘制到Core Graphics上下文会产生正确的外观,但使用该方法绘制图像的成本更高。似乎问题出在CISourceOverCompositing,但问题实际上在于,默认情况下,Core Image过滤器在线性空间中工作,而Core Graphics在感知空间中工作,这解释了不同的结果。但是,您可以使用不执行颜色管理的Core Image上下文从Core Image过滤器创建Core Graphics图像,从而匹配Core Graphics方法的输出。所以原始代码很好,只需稍微输出图像。

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