10

Currently I am using this method to loop through every pixel, and insert a value into a 3D array based upon RGB values. I need this array for other parts of my program, however it is extraordinarily slow. When run on a 50 x 50 picture, it is almost instant, but as soon as you start getting into the hundreds x hundreds it takes a long time to the point where the app is useless. Anyone have any ideas on how to speed up my method?

@IBAction func convertImage(sender: AnyObject) {
    if let image = myImageView.image {

        var pixelData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage))
        var data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)

        let height = Int(image.size.height)
        let width = Int(image.size.width)
        var zArry = [Int](count:3, repeatedValue: 0)
        var yArry = [[Int]](count:width, repeatedValue: zArry)
        var xArry = [[[Int]]](count:height, repeatedValue: yArry)

        for (var h = 0; h < height; h++) {
            for (var w = 0; w < width; w++) {
                 var pixelInfo: Int = ((Int(image.size.width) * Int(h)) + Int(w)) * 4
                var rgb = 0
                xArry[h][w][rgb] = Int(data[pixelInfo])
                rgb++
                xArry[h][w][rgb] = Int(data[pixelInfo+1])
                rgb++
                xArry[h][w][rgb] = Int(data[pixelInfo+2])
                }
        }
        println(xArry[20][20][1])
    }
}

Maybe there is a way to convert the UIImage to a different type of image and create an array of pixels. I am open to all suggestions. Thanks!

GOAL: The goal is to use the array to modify the RGB values of all pixels, and create a new image with the modified pixels. I tried simply looping through all of the pixels without storing them, and modifying them into a new array to create an image, but got the same performance issues.

Kendel
  • 1,698
  • 2
  • 17
  • 33
  • Just a suggestion, but you could begin by unwinding the rgb loop. – Nicolas Miari Feb 16 '15 at 01:07
  • Okay that loop is now unwound. Still very slow. Thanks for the suggestion anyway as it probably helped to speed up a slight amount. – Kendel Feb 16 '15 at 01:13
  • A friend of mine would probably suggest using NEON instructions (single instruction / multiple data) to parallelize the reading of data, but I don't know the details and perhaps it's not possible to do from swift (as opposed to C/Objective-C). If it at least serves you as a pointer... – Nicolas Miari Feb 16 '15 at 01:19
  • You could also try and use Profiling in Xcode to see what part is actually slow, to know what you need to optimize. – pteofil Jun 29 '15 at 06:31
  • This question was for OSX but the solution helped me a lot for a similar problem, maybe it will help you: http://stackoverflow.com/questions/31026763/count-colors-in-image-nscountedset-and-coloratx-are-very-slow – Eric Aya Jun 29 '15 at 09:05

1 Answers1

8

Update:

After countless tries I realized I was making my tests on debug configuration.

Switched to release, and now it's so much faster.

Swift seems to be many times slower on the debug configuration.

The difference now between your code and my optimized version is several times faster.

It seems as you have a big slowdown from using image.size.width instead of the local variable width.

Original

I tried to optimize it a bit and come up with this:

@IBAction func convertImage () {

    if let image = UIImage(named: "test") {

        let pixelData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage))
        let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)

        let height = Int(image.size.height)
        let width = Int(image.size.width)
        let zArry = [Int](count:3, repeatedValue: 0)
        let yArry = [[Int]](count:width, repeatedValue: zArry)
        let xArry = [[[Int]]](count:height, repeatedValue: yArry)

        for (index, value) in xArry.enumerate() {
            for (index1, value1) in value.enumerate() {
                for (index2, var value2) in value1.enumerate() {

                    let pixelInfo: Int = ((width * index) + index1) * 4 + index2

                    value2 = Int(data[pixelInfo])
                }
            }
        }
    }
}

However in my tests this is barely 15% faster. What you need is orders of magnitude faster.

Another ideea is use the data object directly when you need it without creating the array like this:

let image = UIImage(named: "test")!

let pixelData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage))
let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)

let width = Int(image.size.width)

// value for [x][y][z]
let value = Int(data[((width * x) + y) * 4 + z])

You didn't say how you use this array in your app, but I feel that even if you find a way to get this array created much faster, you would get another problem when you try to use it, as it would take a long time too..

pteofil
  • 4,133
  • 17
  • 27
  • If you replace multiplications by addition, you will get a huge performance boost. The initialization by `repeatedValue:` is also a loss for performance. – Sulthan Jun 29 '15 at 08:59
  • The time spent with multiplications is around 1% http://i61.tinypic.com/35n7o7l.png The initialization time seems to be negligible. – pteofil Jun 29 '15 at 09:42
  • I will also put this in the original post, but the goal is to use the array to modify the RGB values of all pixels, and create a new image with the modified pixels. I tried simply looping through all of the pixels without storing them, and modifying them into a new array to create an image, but got the same performance issues. – Kendel Jun 29 '15 at 13:19
  • It's that nested loop that's killing you. Can you try and make a hash map to reduce complexity to O(1) or O(n) rather than O(n^2 log n). Or perhaps even attempt to use a deepMap-style function, there's a thread about deep mapping here, should be trivial to convert it to Swift: http://stackoverflow.com/questions/25333918/js-deep-map-function – cjnevin Jul 02 '15 at 07:48
  • Another thought would be to use threads to process the data quicker, simply divide the size of the data (should be O(1) to call size), then spawn X many threads to process pixels in range 0-10000, 10000-20000, 20000-30000, etc.. – cjnevin Jul 02 '15 at 07:54
  • When I get to `let value = Int(data[((width * x) + y) * 4 + z])` I get `Thread 1: EXC_BAD_ACCESS (code=1, address=0x114e97a70)` in the Simulator. When I print the `data` variable I just see `0x0000000114e89880`. I'm on Swift 5. By any chance did `CFDataGetBytePtr()` used to return an array in previous versions for Swift? Would you have any suggestion on how to fix this error? – albertski Feb 16 '20 at 20:13