swift - speed improvement in UIView pixel per pixel drawing

Viewed 1629

is there a way to improve the speed / performance of drawing pixel per pixel into a UIView?

The current implementation of a 500x500 pixel UIView, is terribly slow.

class CustomView: UIView {

public var context = UIGraphicsGetCurrentContext()

public var redvalues = [[CGFloat]](repeating: [CGFloat](repeating: 1.0, count: 500), count: 500)

public var start = 0
{
didSet{
    self.setNeedsDisplay()
}
}

override func draw(_ rect: CGRect
{
super.draw(rect)
context = UIGraphicsGetCurrentContext()
for yindex in 0...499{
    for xindex in 0...499 {
        context?.setStrokeColor(UIColor(red: redvalues[xindex][yindex], green: 0.0, blue: 0.0, alpha: 1.0).cgColor)

        context?.setLineWidth(2)
        context?.beginPath()
        context?.move(to: CGPoint(x: CGFloat(xindex), y: CGFloat(yindex)))
        context?.addLine(to: CGPoint(x: CGFloat(xindex)+1.0, y: CGFloat(yindex)))
        context?.strokePath()
    }
}
}
}

Thank you very much

3 Answers

I took the answer from Manuel and got it working in Swift 5. The main sticking point here was to clear the dangling pointer warning now in Xcode 12.

var image:CGImage?
        
pixelData.withUnsafeMutableBytes( { (rawBufferPtr: UnsafeMutableRawBufferPointer) in
    if let rawPtr = rawBufferPtr.baseAddress {
        let bitmapContext = CGContext(data: rawPtr,
                                     width: width,
                                    height: height,
                          bitsPerComponent: 8,
                               bytesPerRow: 4*width,
                                     space: colorSpace,
                                bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)
        image = bitmapContext?.makeImage()
     }
})

I did have to move away from the rgba struct approach for front loading the data and moved to direct UInt32 values derived from rawValues in the enum. The 'append' or 'replaceInRange' approach to updating an existing array took hours (my bitmap was LARGE) and ended up exhausting swap space on my computer.

enum Color: UInt32 { // All 4 bytes long with full opacity
    case red = 4278190335 // 0xFF0000FF
    case yellow = 4294902015
    case orange = 4291559679
    case pink = 4290825215
    case violet = 4001558271
    case purple = 2147516671
    case green = 16711935
    case blue = 65535 // 0x0000FFFF
}

With this approach I was able to quickly build a Data buffer with that data amount via:

func prepareColorBlock(c:Color) -> Data {
    var rawData = withUnsafeBytes(of:c.rawValue) { Data($0) }
    rawData.reverse() // Byte order is reveresed when defined
    var dataBlock = Data()
    dataBlock.reserveCapacity(100)
    for _ in stride(from: 0, to: 100, by: 1) {
        dataBlock.append(rawData)
    }
    return dataBlock
}

With that I just appended each of these blocks into my mutable Data instance 'pixelData' and we are off. You can tweak how the data is assembled, as I just wanted to generate some color bars in a UIImageView to validate the work. For a 800x600 view, it took about 2.3 seconds to generate and render the whole thing.

Again, hats off to Manuel for pointing me in the right direction.

Related