我正在使用CISourceOverCompositing
在图片顶部叠加文字,当文字图片不完全不透明时,我会收到意想不到的结果。深色不够暗,输出图像中的浅色太亮。
我在simple Xcode project中重新创建了该问题。它创建一个图像,其中橙色,白色,黑色文本用0.3 alpha绘制,看起来是正确的。我甚至将该图像投入草图,将其放在背景图像的顶部,看起来很棒。屏幕底部的图像显示了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!
答案 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
合成操作有利于反思。
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.5
是pivot point
合成操作的.hardLight
。
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)
答案 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
答案 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