Skip to content

Instantly share code, notes, and snippets.

@jokester
Last active January 4, 2022 07:27
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jokester/948616a1b881451796d6 to your computer and use it in GitHub Desktop.
Save jokester/948616a1b881451796d6 to your computer and use it in GitHub Desktop.
extract pixel from a CGImage
// extract pixel from a CGImage
/* use case:
let extractor = PixelExtractor(img: UIImage(named: "gauge_vertical")!.CGImage!)
let color = extractor.color_at(x: 10, y: 20)
*/
class PixelExtractor {
// taken from http://stackoverflow.com/questions/24049313/
// and adapted to swift 1.2
let image: CGImage
let context: CGContextRef
var width: Int {
get {
return CGImageGetWidth(image)
}
}
var height: Int {
get {
return CGImageGetHeight(image)
}
}
init(img: CGImage) {
image = img
context = PixelExtractor.create_bitmap_context(img)
}
private class func create_bitmap_context(img: CGImage)->CGContextRef {
// Get image width, height
let pixelsWide = CGImageGetWidth(img)
let pixelsHigh = CGImageGetHeight(img)
// Declare the number of bytes per row. Each pixel in the bitmap in this
// example is represented by 4 bytes; 8 bits each of red, green, blue, and
// alpha.
let bitmapBytesPerRow = pixelsWide * 4
let bitmapByteCount = bitmapBytesPerRow * Int(pixelsHigh)
// Use the generic RGB color space.
let colorSpace = CGColorSpaceCreateDeviceRGB()
// Allocate memory for image data. This is the destination in memory
// where any drawing to the bitmap context will be rendered.
let bitmapData = malloc(bitmapByteCount)
let bitmapInfo = CGBitmapInfo(CGImageAlphaInfo.PremultipliedFirst.rawValue)
// Create the bitmap context. We want pre-multiplied ARGB, 8-bits
// per component. Regardless of what the source image format is
// (CMYK, Grayscale, and so on) it will be converted over to the format
// specified here by CGBitmapContextCreate.
let context = CGBitmapContextCreate(bitmapData, pixelsWide, pixelsHigh, 8,
bitmapBytesPerRow, colorSpace, bitmapInfo)
// draw the image onto the context
let rect = CGRect(x: 0, y: 0, width: pixelsWide, height: pixelsHigh)
CGContextDrawImage(context, rect, img)
return context
}
func color_at(#x: Int, y: Int)->UIColor {
assert(0<=x && x<width)
assert(0<=y && y<height)
let uncasted_data = CGBitmapContextGetData(context)
let data = UnsafePointer<UInt8>(uncasted_data)
let offset = 4 * (y * width + x)
let alpha = data[offset]
let red = data[offset+1]
let green = data[offset+2]
let blue = data[offset+3]
let color = UIColor(red: CGFloat(red)/255.0, green: CGFloat(green)/255.0, blue: CGFloat(blue)/255.0, alpha: CGFloat(alpha)/255.0)
return color
}
}
@nick-merrill
Copy link

Ran into the following issue with Swift 2.0, just FYI.

In line 56, I had to get the rawValue from the bitmapInfo (CGBitmapContextCreate requires a UInt32), so that section now reads:

let context = CGBitmapContextCreate(bitmapData, pixelsWide, pixelsHigh, 8, bitmapBytesPerRow, colorSpace, bitmapInfo.rawValue)

Thanks for the code!!

@stbman
Copy link

stbman commented Aug 26, 2015

Thank you very much for this fix!

@keegho
Copy link

keegho commented Jan 23, 2016

Thanks jokester and thanks cloudrave i had this problem 5 min ago for swift 2.

@helmutkaufmann
Copy link

Good morning,
Thanks a lot for this, helped a lot. There might actually be some room for shortening:

class Bitmap {

let width: Int
let height: Int
let context: CGContextRef

init(img: CGImage) {

    // Set image width, height
    width = CGImageGetWidth(img)
    height = CGImageGetHeight(img)

    // Declare the number of bytes per row. Each pixel in the bitmap in this
    // example is represented by 4 bytes; 8 bits each of red, green, blue, and
    // alpha.
    let bitmapBytesPerRow = width * 4

    // Use the generic RGB color space.
    let colorSpace = CGColorSpaceCreateDeviceRGB()

    let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.PremultipliedFirst.rawValue)

    // Create the bitmap context. We want pre-multiplied ARGB, 8-bits
    // per component. Regardless of what the source image format is
    // (CMYK, Grayscale, and so on) it will be converted over to the format
    // specified here by CGBitmapContextCreate.
    context = CGBitmapContextCreate(nil, width, height, 8, bitmapBytesPerRow, colorSpace, bitmapInfo.rawValue)!

    // draw the image onto the context
    let rect = CGRect(x: 0, y: 0, width: width, height: height)
    CGContextDrawImage(context, rect, img)
}

func color_at(x: Int, y: Int)->(Int, Int, Int, Int) {

    assert(0<=x && x<width)
    assert(0<=y && y<height)

    let uncasted_data = CGBitmapContextGetData(context)
    let data = UnsafePointer<UInt8>(uncasted_data)

    let offset = 4 * (y * width + x)

    let alpha = data[offset]
    let red = data[offset+1]
    let green = data[offset+2]
    let blue = data[offset+3]

    let color = (Int(red), Int(green), Int(blue), Int(alpha))
    return color
}

}

It works really great... unless I use it in a function: In that case, it seems that I get memory leakages (by the way: also with the original code as posted above). Might there be a problem with garbage collection?

@ElliottAnastassios
Copy link

Great thanks for sharing!

Regarding the memory leakage, you need to dstroy and dealloc the bitmapData after using; otherwise, memory is being allocated over and over without being released.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment