Search code examples
iosswiftcore-graphicscgcontext

Traversing UnsafeMutableRawPointer image data


I seem to be unable to wrap my head around the methodology behind manually accessing image pixel data in Swift. I am attempting to create an image mask from a CGImage that can later be used on a separate image. I want to identify all pixels of a specific value and convert everything else in the image to black/white or maybe alpha (not really important at the moment however). The code I'm playing with looks like this:

    let colorSpace: CGColorSpace = CGColorSpaceCreateDeviceRGB()
    let contextWidth: Int = Int(snapshot.size.width)
    let contextHeight: Int = Int(snapshot.size.height)
    let bytesPerPixel: Int = 24
    let bitsPerComponent: Int = 8
    let bytesPerRow: Int = bytesPerPixel * contextWidth
    let bitmapInfo: CGBitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.noneSkipLast.rawValue)

    guard let context: CGContext = CGContext(data: nil, width: contextWidth, height: contextHeight, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo.rawValue) else {
        print("Could not create CGContext")
        return
    }

    context.draw(maskCGImage, in: CGRect(x: 0, y: 0, width: contextWidth, height: contextHeight))
    guard let contextDataRaw: UnsafeMutableRawPointer = context.data else {
        print("Could not get UnsafeMutableRawPointer from CGContext")
        return
    }

    let contextData: UnsafeMutablePointer<UInt8> = contextDataRaw.bindMemory(to: UInt8.self, capacity: contextWidth * contextHeight)

    for row in 0..<contextHeight {
        for col in 0..<contextWidth {
            let offset = (col * contextHeight) + row
            let pixelArray = [contextData[offset], contextData[offset + 1], contextData[offset + 2]]
            if pixelArray == [120, 120, 120] {
                contextData[offset] = 0
                contextData[offset + 1] = 0
                contextData[offset + 2] = 0
            }

        }
    }

I have tried various arrangements of the rows and columns trying to identify the correct order, i.e. let offset = (row * contextWidth) + col, let offset = (col * contextHeight) + row, let offset = ((row * contextWidth) + col) * 3, let offset = ((row * contextWidth) + col) * 4.

The output I get looks something like this (Keep in mind that this image IS supposed to look like a blob of random colors):

enter image description here

As my fancy little arrow shows, the black swatch across the top is my edited pixels, and those pixels are indeed supposed to be turned black, however, so are all the other gray pixels (the ones under the arrow for example). The are definitely the same RGB value of 120, 120, 120.

I know the issue is in the order that I'm moving across the array, I just can't seem to figure out what the pattern is. Also, as a note, using copy(maskingColorComponents:) won't do because I want to remove a few specific colors, not a range of them.

Any help is greatly appreciated as always. Thanks in advance!


Solution

  • You're obviously on the right track because you've correctly hit all the pixels in the top left corner. But you don't keep going the rest of the way down the image; clearly you are not surveying enough rows. So the problem might be merely that you are slightly off in your idea of what a row is.

    You are saying

    for row in 0..<contextHeight {
        for col in 0..<contextWidth {
            let offset = (col * contextHeight) + row
    

    as if adding row would in fact get you to that row. But row is just the number of the desired row, not the byte that starts that row; it seems to me that the size of one row jump needs to be the size of all the bytes in one row.