AtelierClockwork

Swift vs Pointers

January 14, 2016

the Best and Worst of Both Worlds

Trying to get the raw data out of an image in Swift for further processing ended up being a special kind of hard to understand because it requires the strictness of a real type system, and all of the uncertainty of working with pointers.

private extension UIImage {

    struct RGBA: Equatable, Comparable {
        let red: UInt8
        let green: UInt8
        let blue: UInt8
        let alpha: UInt8

        static let empty = UIImage.RGBA(red: 0, green: 0, blue: 0, alpha: 0)
        static let full = UIImage.RGBA(red: UInt8.max, green: UInt8.max, blue: UInt8.max, alpha: UInt8.max)

    }

}

The first thing I did was make a simple Swift struct for storing 8 bit per channel RGBA pixels in an array. This means that when I read the colors from the pointer they came out associated with the right channels rather than having read sets of 4 UInt8 values and associate them with the right channel.

After that, I had to take a CGIImage, and calculate the width, height, number of bytes, then allocate a pointer to the right size, and render the CGIImage into that pointer. The really fun part is that the image takes in a pointer full of Void, so the initial pointer state has to be one that the data can’t be read from in any way. A new pointer needs to be created with a useful type from the data, and then that can be read into a buffer pointer, which can in turn be used to create an array of the desired output type.

private extension UIImage {

    private static func imageARGB(inImage: CGImageRef?) -> [RGBA]? {
        guard let inImage = inImage else {
            return nil
        }

        let width = CGImageGetWidth(inImage)
        let height = CGImageGetHeight(inImage)

        let size = CGSize(width: width, height: height)

        let bytesPerRow = CGImageGetBytesPerRow(inImage)
        let totalBytes = bytesPerRow * height

        guard let colorSpace = CGColorSpaceCreateDeviceRGB() else {
            return nil
        }

        let pointer = UnsafeMutablePointer<Void>.alloc(totalBytes)

        guard let context = CGBitmapContextCreate(pointer, width, height, 8, bytesPerRow, colorSpace, CGImageAlphaInfo.PremultipliedLast.rawValue) else {
            return nil
        }
        CGContextDrawImage(context, CGRect(origin: CGPointZero, size: size), inImage)

        let data = UnsafeMutablePointer<RGBA>(CGBitmapContextGetData(context))
        defer {
            pointer.destroy()
            data.destroy()
        }
        return Array(UnsafeBufferPointer(start: data, count: width * height))

    }

}

As a fun minor issue that I ran into, the buffer pointer’s count is not going to be the byte count of the original pointer, because the buffer pointer wants the number of objects stored in the pointer, not the total number of bytes.

On the plus side, using defer makes cleaning up the pointers easier, no more temporary storage of the return value, cleanup, then returning. I also got to use guard to explicitly return nil if anything goes wrong, particularly if allocation of any of the pointers fails.