CGContext.init() — NULL color space no longer allowed

大城市里の小女人 提交于 2020-08-05 07:19:06

问题


TL;DR: In legacy Obj-C code, the color space param value was NULL. That is not allowed in the Swift equivalent. What value to use?

I have inherited code that reads:

unsigned char pixel[1] = {0};
CGContextRef context = CGBitmapContextCreate(
    pixel,1, 1, 8, 1, NULL, (CGBitmapInfo)kCGImageAlphaOnly
);

The port to Swift 4 CGContext is straightforward, except for that NULL color space value. Using a plausible value, I am getting nil back from CGContext.init?(). My translation is:

var pixelValue = UInt8(0)
var pixel = Data(buffer: UnsafeBufferPointer(start:&pixelValue, count:1))
let context = CGContext(
    data            : &pixel,
    width           : 1,
    height          : 1,
    bitsPerComponent: 8,
    bytesPerRow     : 1,
    space           : CGColorSpace(name:CGColorSpace.genericRGBLinear)!,
    bitmapInfo      : CGImageAlphaInfo.alphaOnly.rawValue
)! // Returns nil; unwrapping crashes

Q: What is the appropriate value for space? (The value I provide is not returning nil; it's the CGContext() call itself.

Setting the environment variable CGBITMAP_CONTEXT_LOG_ERRORS yields an error log like this:

Assertion failed: (0), function get_color_model_name, 
file /BuildRoot/Library/Caches/com.apple.xbs/Sources/Quartz2D_Sim/
Quartz2D-1129.2.1/CoreGraphics/API/CGBitmapContextInfo.c, line 210.

For some more backstory, the context was used to find the alpha value of a single pixel in a UIImage in the following way:

unsigned char pixel[1] = {0};
CGContextRef context = CGBitmapContextCreate(pixel,1, 1, 8, 1, NULL, (CGBitmapInfo)kCGImageAlphaOnly);
UIGraphicsPushContext(context);
[image drawAtPoint:CGPointMake(-point.x, -point.y)];
UIGraphicsPopContext();
CGContextRelease(context);
CGFloat alpha = pixel[0]/255.0;

(I do have possible alternatives for finding alpha, but in the interest of leaving legacy code alone, would like to keep it this way.)


回答1:


I recently worked with similar topic, maybe this code sample will help someone:

let image = UIImage(named: "2.png")
guard let cgImage = image?.cgImage else {
    fatalError()
}

let width = cgImage.width
let height = cgImage.height
//CGColorSpaceCreateDeviceGray - 1 component, 8 bits
//i.e. 1px = 1byte
let bytesPerRow = width
let bitmapByteCount = width * height

let bitmapData: UnsafeMutablePointer<UInt8> = .allocate(capacity: bitmapByteCount)
defer {
    bitmapData.deallocate()
}
bitmapData.initialize(repeating: 0, count: bitmapByteCount)

guard let context = CGContext(data: bitmapData, width: width, height: height,
                              bitsPerComponent: 8, bytesPerRow: bytesPerRow,
                              space: CGColorSpaceCreateDeviceGray(), bitmapInfo: CGImageAlphaInfo.alphaOnly.rawValue) else {
                                fatalError()
}
//draw image to context
var rect = CGRect(x: 0, y: 0, width: width, height: height)
context.draw(cgImage, in: rect)

// Enumerate through all pixels
for row in 0..<height {
    for col in 0..<width {
        let alphaValue = bitmapData[row * width + col]
        if alphaValue != 0 {
            //visible pixel
        }
    }
}



回答2:


Here’s how to determine whether a pixel is transparent:

    let info = CGImageAlphaInfo.alphaOnly.rawValue
    let pixel = UnsafeMutablePointer<UInt8>.allocate(capacity:1)
    defer {
        pixel.deinitialize(count: 1)
        pixel.deallocate()
    }
    pixel[0] = 0
    let sp = CGColorSpaceCreateDeviceGray()
    let context = CGContext(data: pixel,
        width: 1, height: 1, bitsPerComponent: 8, bytesPerRow: 1,
        space: sp, bitmapInfo: info)!
    UIGraphicsPushContext(context)
    im.draw(at:CGPoint(-point.x, -point.y))
    UIGraphicsPopContext()
    let p = pixel[0]
    let alpha = Double(p)/255.0
    let transparent = alpha < 0.01



回答3:


For the record, here is how I wound up doing it. It hasn't (yet) misbehaved, so on the principle of "If it ain't broke, don't fix it" I'll leave it. (I have added self for clarity.) But you can be sure that I will paste Matt's code right in there, in case I need it in the future. Thanks Matt!

// Note that "self" is a UIImageView; "point" is the point under consideration.

let im = self.image!

// TODO: Why is this clamping necessary? We get points outside our size.
var x = point.x
var y = point.y
if x < 0 { x = 0 } else if x > im.size.width  - 1 { x = im.size.width  - 1 }
if y < 0 { y = 0 } else if y > im.size.height - 1 { y = im.size.height - 1 }

let screenWidth    = self.bounds.width
let intrinsicWidth = im.size.width

x *= im.scale * intrinsicWidth/screenWidth
y *= im.scale * intrinsicWidth/screenWidth

let pixData = im.cgImage?.dataProvider?.data
let data    = CFDataGetBytePtr(pixData!)

let pixIndex = Int(((Int(im.size.width*im.scale) * Int(y)) + Int(x)) * 4)

let r = data?[pixIndex]
let g = data?[pixIndex + 1]
let b = data?[pixIndex + 2]
let α = data?[pixIndex + 3]

let red    = CGFloat(r!)/255
let green  = CGFloat(g!)/255
let blue   = CGFloat(b!)/255
let alpha  = CGFloat(α!)/255


来源:https://stackoverflow.com/questions/48195801/cgcontext-init-null-color-space-no-longer-allowed

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!