I have been stumped by this for a few days now and can't find a solution. I have a UIImageView with a transparent background. It is on top of another view (in the example just a UIView with blue background color). For some reason it will blend a color even if it has an alpha value of 0 (in this case full red no alpha values comes out pink). The desired output is that it will show clear just like black with no alpha value.
If over white it behaves as intended (it is clear and shows white through). I have tried different ways of creating the CGContext and different CALayer blend modes and can't get it to not blend colors that don't have alpha values on them.
Here is sample code to replicate the issue.
import UIKit
import PlaygroundSupport
import CoreGraphics
class MyViewController : UIViewController {
var pixelPointer: UnsafeMutablePointer<UInt8>!
let imageWidth = 20
let imageHeight = 20
override func loadView() {
let view = UIView()
view.backgroundColor = .blue
let viewSize: CGFloat = 150
// Test Image
pixelPointer = UnsafeMutablePointer<UInt8>.allocate(capacity: imageWidth * imageWidth * 4)
let ctx = CGContext(data: pixelPointer,
width: imageWidth,
height: imageHeight,
bitsPerComponent: 8,
bytesPerRow: 4 * imageWidth,
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)!
for x in 0 ..< imageWidth {
for y in 0 ..< imageHeight {
if y == 7 || y == 8 || y == 9 {
// Red No Alpha -> Expected to be transparent
setColorFor(255, g: 0, b: 0, a: 0, x: x, y: y)
} else if y == 10 || y == 11 || y == 12 {
// Black No Alpha -> Is transparent
setColorFor(0, g: 0, b: 0, a: 0, x: x, y: y)
} else {
// Red
setColorFor(255, g: 0, b: 0, a: 255, x: x, y: y)
}
}
}
let cgImage = ctx.makeImage()!
// ImageView with Clear Background
let imageViewClearBackground = UIImageView()
imageViewClearBackground.backgroundColor = .clear
imageViewClearBackground.frame.origin = CGPoint(x: 10, y: 10)
imageViewClearBackground.layer.borderColor = UIColor.black.cgColor
imageViewClearBackground.layer.borderWidth = 1
imageViewClearBackground.frame.size = CGSize(width: viewSize, height: viewSize)
imageViewClearBackground.layer.magnificationFilter = CALayerContentsFilter.nearest
imageViewClearBackground.image = UIImage(cgImage: cgImage)
view.addSubview(imageViewClearBackground)
// ImageView with White Background
let imageViewWhiteBackground = UIImageView()
imageViewWhiteBackground.layer.borderColor = UIColor.black.cgColor
imageViewWhiteBackground.layer.borderWidth = 1
imageViewWhiteBackground.backgroundColor = .white
imageViewWhiteBackground.frame.size = CGSize(width: viewSize, height: viewSize)
imageViewWhiteBackground.frame.origin = CGPoint(x: viewSize 20, y: 10)
imageViewWhiteBackground.layer.magnificationFilter = CALayerContentsFilter.nearest
imageViewWhiteBackground.image = UIImage(cgImage: cgImage)
view.addSubview(imageViewWhiteBackground)
self.view = view
}
func setColorFor(_ r: Int, g: Int, b: Int, a: Int, x: Int, y: Int) {
let offset = (y * Int(imageWidth) * 4) x * 4
pixelPointer[offset 0] = UInt8(r)
pixelPointer[offset 1] = UInt8(g)
pixelPointer[offset 2] = UInt8(b)
pixelPointer[offset 3] = UInt8(a)
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
CodePudding user response:
The problem is that this:
setColorFor(255, g: 0, b: 0, a: 0, x: x, y: y)
is not a valid .premultipliedLast
color value. Premultiplied means that the colors have already been multiplied by the alpha. If you multiply 255 by 0 you get 0, so that is the correct red value here — as the result in rows 10-12 demonstrates.
You would probably confuse yourself a lot less if you would just construct the image using UIGraphicsImageRenderer in the normal way rather than a raw bitmap context. But of course if your use case precludes that, then by all means use the bitmap — but then there is a lot more room for you to use it incorrectly.