12

In my app I'm creating an image mapping floats to a pixel value and use it as an overlay on Google Maps, but it takes forever to do, the same thing in Android is almost instant. My code looks like this:

private func imageFromPixels(pixels: [PixelData], width: Int, height: Int) -> UIImage? {

    let bitsPerComponent = 8
    let bitsPerPixel = bitsPerComponent * 4
    let bytesPerRow = bitsPerPixel * width / 8

    let providerRef = CGDataProvider(
        data: NSData(bytes: pixels, length: height * width * 4) 
    )

    let cgimage = CGImage(
        width: width,
        height: height,
        bitsPerComponent: bitsPerComponent,
        bitsPerPixel: bitsPerPixel,
        bytesPerRow: bytesPerRow,
        space: CGColorSpaceCreateDeviceRGB(),
        bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue),
        provider: providerRef!,
        decode: nil,
        shouldInterpolate: true,
        intent: .defaultIntent
    )

    if cgimage == nil {
        print("CGImage is not supposed to be nil")
        return nil
    }
    return UIImage(cgImage: cgimage!)
}

Any suggestions on how this can take so long to do? I can see it uses about 96% CPU power.

func fromData(pair: AllocationPair) -> UIImage? {

    let table = pair.table
    let data = pair.data

    prepareColors(allocations: table.allocations)

    let height = data.count
    let width = data[0].count

    var colors = [PixelData]()

    for row in data {
        for val in row {

            if (val == 0.0) {
                colors.append(PixelData(a: 0, r: 0, g: 0, b: 0))
                continue
            }

            if let interval = findInterval(table: table, value: val) {
                if let color = intervalColorDict[interval] {
                    colors.append(PixelData(a: color.a, r: color.r, g: color.g, b: color.b))
                } 
            }
        }
    }

    return imageFromPixels(pixels: colors, width: width, height: height)
}

I've tried to time profile it, and this is the output where it takes time.

enter image description here

Recusiwe
  • 1,594
  • 4
  • 31
  • 54
  • Basically I'm just showing a picture with either green, red or yellow pixels mixed up – Recusiwe Feb 20 '17 at 20:15
  • Are you sure the problem is here and not in the creation of the `[PixelData]` array? In debug builds, manipulating large arrays is very slow. If you do a release build with optimizations turned on, using large arrays is much faster. Make sure the problem is here and not in the creation of that array. And then, of course, make sure that when you show the image that you're doing that on the main queue. – Rob Feb 20 '17 at 20:46
  • 1
    @Recusiwe, have you tried using Instruments to pinpoint where all the CPU is being spent? You can try starting with this tutorial: https://www.raywenderlich.com/97886/instruments-tutorial-with-swift-getting-started – Dave Weston Feb 24 '17 at 16:45
  • 1
    I'd like to second this suggestion "Are you sure the problem is here and not in the creation of the [PixelData] array?" Could you please include the code that creates that buffer? The part of the code you have already posted should run perfectly fine. – Kyle Howells Feb 26 '17 at 01:51

2 Answers2

9

I tried your code and I found out that the problem isn't with your function.

I think that you should use a UInt8 based pixel structure and not a CGFloat based structure.

I translated your code for a cocoa application, and this is the result:

public struct PixelData {
    var a: UInt8
    var r: UInt8
    var g: UInt8
    var b: UInt8
}

func imageFromPixels(pixels: [PixelData], width: Int, height: Int) -> NSImage? {

    let bitsPerComponent = 8
    let bitsPerPixel = bitsPerComponent * 4
    let bytesPerRow = bitsPerPixel * width / 8

    let providerRef = CGDataProvider(
        data: NSData(bytes: pixels, length: height * width * 4)
    )

    let cgimage = CGImage(
        width: width,
        height: height,
        bitsPerComponent: bitsPerComponent,
        bitsPerPixel: bitsPerPixel,
        bytesPerRow: bytesPerRow,
        space: CGColorSpaceCreateDeviceRGB(),
        bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue),
        provider: providerRef!,
        decode: nil,
        shouldInterpolate: true,
        intent: .defaultIntent
    )

    if cgimage == nil {
        print("CGImage is not supposed to be nil")
        return nil
    }
    return NSImage(cgImage: cgimage!, size: NSSize(width: width, height: height))
}

var img = [PixelData]()

for i: UInt8 in 0 ..< 20 {
    for j: UInt8 in 0 ..< 20 {
        // Creating a red 20x20 image.
        img.append(PixelData(a: 255, r: 255, g: 0, b: 0))
    }
}

var ns = imageFromPixels(pixels: img, width: 20, height: 20)

This code is fast and light, here's the debug system impact values:

enter image description here

I think that the problem comes up with the part which loads the pixel data, check it and be sure that it works properly.

aksh1t
  • 5,410
  • 1
  • 37
  • 55
Cristian
  • 654
  • 1
  • 13
  • 30
  • I won't take the NSImage and NSSize? How would it look if it uses UIKit instead of AppKit? – Recusiwe Mar 07 '17 at 16:14
  • @Recusiwe you can use `UIImage(cgImage: cgimage!)` and return `UIImage` instead of `NSImage`, then change `NSData` to `Data` if you're using UIKit. Here's the [documentation](https://developer.apple.com/reference/uikit/uiimage/1624090-init). The other instructions are almost the same... – Cristian Mar 07 '17 at 19:01
1

If you need to access and change the actual bitmap data, you can use CGImage. In other cases use CIImage represented objects. So work with UIImage, convert UIImage to CIImage, make any manipulations on it, and then convert it back to a UIImage (as you did with CGImage in your code).

Here's a post explaining what is what: UIImage vs. CIImage vs. CGImage

Core Image doesn’t actually render an image until it is told to do so. This “lazy evaluation” method allows Core Image to operate as efficiently as possible.

CIImage page in Apple Developer Documentation

Andy Jazz
  • 49,178
  • 17
  • 136
  • 220