-->

Getting Pixel Color from an Image using CGPoint in

2020-07-20 09:14发布

问题:

I am try this PixelExtractor class in Swift 3, get a error; Cannot invoke initializer for type 'UnsafePointer' with an argument list of type '(UnsafeMutableRawPointer?)'

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
}

}

Fix this error.

let data = UnsafePointer<UInt8>(uncastedData)

->

let data = UnsafeRawPointer(uncastedData)

Get other error; 'Type 'UnsafeRawPointer?' has no subscript members'

How to modify this error?

回答1:

You can write something like this when you have an UnsafeRawPointer in your data:

    let alpha = data.load(fromByteOffset: offset, as: UInt8.self)
    let red = data.load(fromByteOffset: offset+1, as: UInt8.self)
    let green = data.load(fromByteOffset: offset+2, as: UInt8.self)
    let blue = data.load(fromByteOffset: offset+3, as: UInt8.self)

Or else, you can get UnsafeMutablePointer<UInt8> from your uncastedData (assuming it's an UnsafeMutableRawPointer):

    let data = uncastedData.assumingMemoryBound(to: UInt8.self)


回答2:

SWIFT 3 (updated March 2017) Xcode 8 / IOS 10

Important: note that return value corresponds to red: b, green:r and blue: r as in the data they are stored backwards

  • First, create the extension (you can copy&paste somewhere in your code)

    extension UIImage {
    
        func getPixelColor(pos: CGPoint) -> UIColor {
    
            if let pixelData = self.cgImage?.dataProvider?.data {
                let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)
    
                let pixelInfo: Int = ((Int(self.size.width) * Int(pos.y)) + Int(pos.x)) * 4
    
                let r = CGFloat(data[pixelInfo+0]) / 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: b, green: g, blue: r, alpha: a)
            } else {
            //IF something is wrong I returned WHITE, but change as needed
                return UIColor.white
            }
        }
    }
    
  • Then just call it as:

    let colorAtPixel : UIColor = (theView.image?.getPixelColor(pos: CGPoint(x: 2, y: 2)))!
    

Although the code returns de exact color, it seems that is not returning the correct one for different CGPoints.

Might it be because of the screen resolution? (x1,x2,x3)?

It would be great if someone can add some light to the mystery...



回答3:

Swift-3 (IOS 10.3)

extension UIImage {

    func getPixelColor(atLocation location: CGPoint, withFrameSize size: CGSize) -> UIColor {
        let x: CGFloat = (self.size.width) * location.x / size.width
        let y: CGFloat = (self.size.height) * location.y / size.height

        let pixelPoint: CGPoint = CGPoint(x: x, y: y)

        let pixelData = self.cgImage!.dataProvider!.data
        let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)

        let pixelIndex: Int = ((Int(self.size.width) * Int(pixelPoint.y)) + Int(pixelPoint.x)) * 4

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

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

}

Usage : -

let color = yourImageView.image!.getPixelColor(atLocation: location, withFrameSize: yourImageView.frame.size)

location is a CGPoint and size is size of your imageView



回答4:

The following section is taken from some Swift 3 code I'm using to sample pixels from an image to get the predominant hue which I use to generate a background for tableView rows. The mechanics for the hue selection process don't apply to your question, so I'm just providing the relevant fragment.

let colorSpace = CGColorSpaceCreateDeviceRGB() //  UIExtendedSRGBColorSpace
let newImage = image.cgImage?.copy(colorSpace: colorSpace)

let pixelData = newImage?.dataProvider!.data
let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)

var hueFrequency = [Int: Double]()
hueFrequency[1] = 1                 // Add one entry so this serves as a default if no hues from the image pass the filters

let nStart = 1
let mStart = 1

for n in nStart...Int(image.size.width / samplingFactor) {
    for m in mStart...Int(image.size.height / samplingFactor) {
        let pixelInfo: Int = ((Int(image.size.width) * m * Int(samplingFactor)) + n * Int(samplingFactor)) * 4 // bytesPerPixel

        let b = CGFloat(data[pixelInfo]) / CGFloat(255.0)           // cgImage bitmapinfo = rawValue 8194 -> BGRA ordering
        let g = CGFloat(data[pixelInfo+1]) / CGFloat(255.0)
        let r = CGFloat(data[pixelInfo+2]) / CGFloat(255.0)
        let a = CGFloat(data[pixelInfo+3]) / CGFloat(255.0)

Also, note that I found the bitmapInfo value (image.cgImage!.bitmapInfo using my parameters) indicated a reordering of the RGBA sequence to BGRA, which I had to deal with in ordering the steps to pick out the data. If your colors are off, you may want to check this.