Search code examples
iosswiftuicolorcgimage

Getting Pixel Color from an Image using CGPoint


I'm experiencing a great deal of difficulty just getting the pixel color of a certain pixel (specified by a CGPoint) in Swift. Here is my code thus far:

@IBOutlet weak var graphImage: UIImageView!

var image : UIImage?

override func viewDidLoad() {
    super.viewDidLoad()
}

func getPixelColor(pos: CGPoint) -> UIColor {
    var pixelData = CGDataProviderCopyData(CGImageGetDataProvider(self.graphImage.image!.CGImage))
    var data : UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)

    var pixelInfo : Int = ((Int(graphImage.image!.size.width) * Int(pos.y) + Int(pos.x)*4))

    let r = CGFloat(data[pixelInfo]) / CGFloat(255.0)
    let g = CGFloat(data[pixelInfo]+1) / CGFloat(255.0)
    let b = CGFloat(data[pixelInfo]+2) / CGFloat(255.0)
    let a = CGFloat(data[pixelInfo]+3) / CGFloat(255.0)

    return UIColor(red: r, green: g, blue: b, alpha: a)
}

@IBAction func playTapped(sender: AnyObject) {

    getPixelColor(CGPoint(x: 100.0, y: 100.0))

}

My app crashes, after tapping play, on the line "let g = CGFloat..." I don't see any errors at all in this, though I am quite new to anything to do with images. I'm wondering if I need to use a different type of image, or whether something in my pixelInfo variable is wrong. By the way, I got a majority of this code from How do I get the color of a pixel in a UIImage with Swift?. Can anyone point to what may be the problem? Any help would be greatly appreciated!


Solution

  • I've done something like this before. What I did was create a separate class called PixelExtractor.

    class PixelExtractor: NSObject {
    
      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.createBitmapContext(img)
      }
    
      class func createBitmapContext(img: CGImage) -> CGContextRef {
    
          // Get image width, height
          let pixelsWide = CGImageGetWidth(img)
          let pixelsHigh = CGImageGetHeight(img)
    
          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(rawValue: CGImageAlphaInfo.PremultipliedFirst.rawValue)
          let size = CGSizeMake(CGFloat(pixelsWide), CGFloat(pixelsHigh))
          UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
          // create bitmap
          let context = CGBitmapContextCreate(bitmapData, pixelsWide, pixelsHigh, 8,
            bitmapBytesPerRow, colorSpace, bitmapInfo.rawValue)
    
          // draw the image onto the context
          let rect = CGRect(x: 0, y: 0, width: pixelsWide, height: pixelsHigh)
          CGContextDrawImage(context, rect, img)
    
          return context!
      }
    
      func colorAt(x x: Int, y: Int)->UIColor {
    
          assert(0<=x && x<width)
          assert(0<=y && y<height)
    
          let uncastedData = CGBitmapContextGetData(context)
          let data = UnsafePointer<UInt8>(uncastedData)
    
          let offset = 4 * (y * width + x)
    
          let alpha: UInt8 = data[offset]
          let red: UInt8 = data[offset+1]
          let green: UInt8 = data[offset+2]
          let blue: UInt8 = 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
      }
    }