Sunay
Sunay

Reputation: 417

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!

Upvotes: 3

Views: 5183

Answers (4)

swillsea
swillsea

Reputation: 341

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

Upvotes: 6

Alex Shoshiashvili
Alex Shoshiashvili

Reputation: 489

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

Upvotes: 4

MLBDG
MLBDG

Reputation: 1367

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

Upvotes: 0

jasonnoahchoi
jasonnoahchoi

Reputation: 871

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

Upvotes: 5

Related Questions