3

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!

Community
  • 1
  • 1
Sunay
  • 387
  • 1
  • 5
  • 14
  • what is the error, Please post the crash code. also use breakpoint to know at which line is the crash getting occured. – Saheb Roy Jan 27 '16 at 06:18
  • I am here too @SahebRoy – Vizllx Jan 27 '16 at 07:15
  • are bhai@Vizllx stckovflw te chat hoe? how to add? – Saheb Roy Jan 27 '16 at 07:24
  • Similar [question](http://stackoverflow.com/questions/25146557/how-do-i-get-the-color-of-a-pixel-in-a-uiimage-with-swift) already asked. – MrWaqasAhmed Jan 27 '16 at 07:38
  • The crash code just says EXC_BAD_INSTRUCTION, and nothing else. It crashes on the line "let g = CGFloat(data[pixelInfo]+1) / CGFloat(255.0). And I know a similar question was asked, but I have a different problem than this other question had, even though we are using the same code. – Sunay Jan 28 '16 at 03:24

4 Answers4

6

jasonnoahchoi gave a great answer - if you're having trouble converting to Swift 3.0, try the same code with the following changes:

class PixelExtractor: NSObject {
    
    let image: CGImage
    let context: CGContext?
    
    var width: Int {
        get {
            return image.width
        }
    }
    
    var height: Int {
        get {
            return image.height
        }
    }
    
    init(img: CGImage) {
        image = img
        context = PixelExtractor.createBitmapContext(cgImage: img)
    }
    
    class func createBitmapContext(cgImage: CGImage) -> CGContext {
        
        // Get image width, height
        let pixelsWide = cgImage.width
        let pixelsHigh = cgImage.height
        
        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 = CGSize(width: pixelsWide, height: pixelsHigh)
        UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
        // create bitmap
        let context = CGContext(data: bitmapData, width: pixelsWide, height: pixelsHigh, bitsPerComponent: 8,
                                bytesPerRow: bitmapBytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo.rawValue)
        
        // draw the image onto the context
        let rect = CGRect(x: 0, y: 0, width: pixelsWide, height: pixelsHigh)
        context?.draw(cgImage, in: rect)
        
        return context!
    }
    
    private func colorAt(x: Int, y: Int) -> UIColor {
        
        assert(0<=x && x<width)
        assert(0<=y && y<height)
        
        let data = context!.data!
        
        let offset = 4 * (y * width + x)
        
        let alpha = CGFloat(data.load(fromByteOffset: offset, as: UInt8.self)) / 255.0
        let red = CGFloat(data.load(fromByteOffset: offset + 1, as: UInt8.self)) / 255.0
        let green = CGFloat(data.load(fromByteOffset: offset + 2, as: UInt8.self)) / 255.0
        let blue = CGFloat(data.load(fromByteOffset: offset + 3, as: UInt8.self)) / 255.0
        
        let color = UIColor(red: red, green: green, blue: blue, alpha: alpha)
        
        return color
    }
}
jasonnoahchoi
  • 871
  • 5
  • 16
swillsea
  • 341
  • 4
  • 12
5

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
  }
}
jasonnoahchoi
  • 871
  • 5
  • 16
  • Hey! Thanks for the answer! Like I said, I'm extremely new to all this: I tried your code and it isn't working for me, but that may be due to just a small, technical issue. Would you mind sending me a bit more of your project so I can see what context you used this code in? – Sunay Jan 28 '16 at 03:33
  • The way you would call this is by doing something like this in your view controller: `PixelExtractor(image.CGImage).colorAt(x: 1, y: 2)` // x and y are basically Ints of where your CGPoint might be, so if you have a CGPoint, you'll have to cast the X and Y as an Int like so, `Int(point.x)`, `Int(point.y)` – jasonnoahchoi Jan 28 '16 at 07:24
  • 1
    Hey! Thank you so much for that! This worked perfectly! :) – Sunay Jan 29 '16 at 22:13
  • 1
    @jasonnoahchoi You need to make sure you call `free(uncastedData)` before returning color, since unsafeMutablePointers are not covered under ARC. – swillsea Feb 08 '17 at 00:28
  • 1
    I would also recommend storing data and uncastedData as variables in your init method if you think you might call colorAt() more than once (and free in the class's deinit method). But that's entirely up to the way you've implemented PixelExtractor in your code. – swillsea Feb 08 '17 at 00:29
4

Swift 5 version of jasonnoahchoi answer

class PixelExtractor: NSObject {

    let image: CGImage
    let context: CGContext?

    var width: Int {
        get {
            return image.width
        }
    }

    var height: Int {
        get {
            return image.height
        }
    }

    init(img: CGImage) {
        image = img
        context = PixelExtractor.createBitmapContext(img: img)
    }

    class func createBitmapContext(img: CGImage) -> CGContext {

        // Get image width, height
        let pixelsWide = img.width
        let pixelsHigh = img.height

        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 = CGSize(width: CGFloat(pixelsWide), height: CGFloat(pixelsHigh))
        UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
        // create bitmap
        let context = CGContext(data: bitmapData,
                                width: pixelsWide,
                                height: pixelsHigh,
                                bitsPerComponent: 8,
                                bytesPerRow: bitmapBytesPerRow,
                                space: colorSpace,
                                bitmapInfo: bitmapInfo.rawValue)

        // draw the image onto the context
        let rect = CGRect(x: 0, y: 0, width: pixelsWide, height: pixelsHigh)

        context?.draw(img, in: rect)

        return context!
    }

    func colorAt(x: Int, y: Int)->UIColor {

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

        guard let pixelBuffer = context?.data else { return .white }
        let data = pixelBuffer.bindMemory(to: UInt8.self, capacity: width * height)

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

After struggling for hours I discovered that the RGBA information is stored like BGRA. Check this answer for an extension in SWIFT 3/xcode8/ios10

Community
  • 1
  • 1
MLBDG
  • 1,357
  • 17
  • 23