10

CISourceOverCompositing用来在图像顶部覆盖文本,当文本图像不完全不透明时,我得到了意想不到的结果。深色不够暗,输出图像中的浅色太亮。

我在一个简单的 Xcode 项目中重新创建了这个问题。它使用 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!
4

3 回答 3

5

在反复尝试不同的事情之后,(感谢@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)
于 2018-03-24T19:12:54.453 回答
4

对于黑白文本

如果您使用.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,我会得到ADDop: Image1 + Image2


为了得到你想要的,你需要设置一个合成操作到.hardLight.

.hardLight合成操作作为.multiply

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

公式.multiplyImage1 * Image2


.hardLight合成操作作为.screen

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

公式 1 .screen(Image1 + Image2) – (Image1 * Image2)

公式 2 .screen1 – (1 – Image1) * (1 – Image2)

.screen操作的结果比 更柔和.plus,并且它允许保持 alpha 不大于 1(plus操作添加 Image1 和 Image2 的 alpha,因此如果您有两个 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如果除了黑白文本之外还有橙色(或任何其他颜色)文本,则需要使用合成操作。应用您让 Swift 使用背景图像的亮度来确定要显示的内容.sourceAtop的方法案例。.setBlendMode或者,您可以使用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)

在此处输入图像描述

在此处输入图像描述

于 2018-03-06T12:10:32.777 回答
1

最终答案: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 文档

使用 RGB 和 sRGB 颜色空间测试图像

于 2018-03-05T15:49:44.827 回答