Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Speed up pixel iteration

The problem I'm having is not being able to iterate over all the pixels in a UIImage quickly enough, checking each colour against a particular one (green). The iteration must be processed as soon as the user initiates it. At the moment there's a ~0.5s delay.

I'm using this to create a screenshot of the view:

func screenshot() -> UIImage { 

    UIGraphicsBeginImageContextWithOptions(view.bounds.size, false, UIScreen.main.scale)
    view.layer.render(in: UIGraphicsGetCurrentContext()!)
    let screenShot = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return screenShot!  
}

This to obtain the colour of a single pixel:

extension UIImage {

    func getPixelColor(pos: CGPoint) -> UIColor {

        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]) / 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)
    }
}

And this loop to check each pixel of the image:

testImage = screenshot()
greenPresent = false

for yCo in 0 ..< Int(testImage.size.height) {
    for xCo in 0 ..< Int(testImage.size.width) {

        if testImage.getPixelColor(pos: CGPoint(x: xCo, y: yCo)) == UIColor(red: 0, green: 1, blue: 0, alpha: 1) {
            greenPresent = true
            greenCount += 1
        }
    }
}

Any suggestions would be appreciated.

EDIT

Performance takes a hit on a scale factor of 0.0; I am looking to accommodate this.

like image 530
Tim Avatar asked Apr 24 '17 01:04

Tim


2 Answers

Here's a vastly more efficient version of the code you posted:

testImage = imageFromView()
var greenCount = 0
if let cfdata = testImage.cgImage?.dataProvider?.data {
    let data = cfdata as Data
    for i in stride(from: 0, to: data.count, by: 4) {
        let r = data[i]
        let g = data[i+1]
        let b = data[i+2]
        if g == 255 && r == 0 && b == 0 {
            greenCount += 1
        }
    }
}

This code eliminates many inefficiencies in the original code. The data is only obtained once. No points are used. The data bytes are accessed directly. No UIColor creation is needed. No conversion to CGFloat is needed.

like image 60
rmaddy Avatar answered Sep 29 '22 06:09

rmaddy


I would recommend caching the data pointer:

let data: UnsafePointer<UInt8> = ...

as opposed to computing it from scratch every single time you call the getPixelColor method.

Also: consider creating a simple struct Color instead of using the standard class UIColor. Creating (and deallocating) that many class instances might get expensive quickly!


By the way, you should also add a simple break after finding the desired pixel. Of course, assuming your final algorithm stays the same ;-)

like image 22
Paulo Mattos Avatar answered Sep 29 '22 06:09

Paulo Mattos