Andrew Duncan
Andrew Duncan

Reputation: 3753

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

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.)

Upvotes: 2

Views: 1749

Answers (3)

Andrew Duncan
Andrew Duncan

Reputation: 3753

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

Upvotes: 0

matt
matt

Reputation: 535086

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

Upvotes: 0

Kirow
Kirow

Reputation: 1210

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
        }
    }
}

Upvotes: 2

Related Questions