iosswiftcvpixelbuffer

CVPixelBuffer resulting into garbage image on the device, while working as expected on the simulator


I am trying to create an image out of artificially created data and want to use CVPixelBuffer:

    private func RGBAImage(width w: Int, height h: Int) -> UIImage? {

        let width = w * Int(UIScreen.main.scale)
        let height = h * Int(UIScreen.main.scale)

        // Prepare artificial data

        let dataPtr = UnsafeMutablePointer<UInt8>.allocate(capacity: width * height * 4)

        for i in 0..<width {
            for j in 0..<height {
                dataPtr[4 * (i + j * width)] = UInt8(sin(Double(i) * 0.01 * .pi / Double(UIScreen.main.scale)) * 127 + 127)
                dataPtr[4 * (i + j * width) + 1] = UInt8(255)
                dataPtr[4 * (i + j * width) + 2] = UInt8(0)
                dataPtr[4 * (i + j * width) + 3] = UInt8(0)
            }
        }

        // Convert data into CVPixelBuffer

        var pxBuffer: CVPixelBuffer?

        CVPixelBufferCreateWithBytes(
            kCFAllocatorDefault,
            width,
            height,
            kCVPixelFormatType_32ARGB,
            dataPtr,
            width * 4,
            nil,
            nil,
            [kCVPixelBufferIOSurfacePropertiesKey: [:]] as CFDictionary,
            &pxBuffer
        )

        dataPtr.deallocate()

        guard let cvPxBuffer = pxBuffer else {
            return nil
        }

        // Generate image from CVPixelBuffer

        let ciImage = CIImage(cvImageBuffer: cvPxBuffer)

        return UIImage(ciImage: ciImage, scale: UIScreen.main.scale, orientation: .up)
    }

The code works fine on simulator and shows as this :

enter image description here

But the same code shows garbage results on the device :

enter image description here

What am I missing here? Any suggestion is welcome.


Solution

  • Figured out myself. I still don't know why CVPixelBufferCreateWithBytes doesn't work, but I was able to make it work by creating the pixel buffer with CVPixelBufferCreate and setting value of each RGB address one by one. This should be a better approach as well since I don't need to create an array first.

    Here is the working code for both device and simulator:

        private func RGBAImage(width w: Int, height h: Int) -> UIImage? {
    
            let width = w * Int(UIScreen.main.scale)
            let height = h * Int(UIScreen.main.scale)
            let bytesPerPixel = 4
    
            // Create CVPixelBuffer with artificial data
    
            var pxBuffer: CVPixelBuffer?
    
            CVPixelBufferCreate(
                kCFAllocatorDefault,
                width,
                height,
                kCVPixelFormatType_32ARGB,
                nil,
                &pxBuffer)
    
            guard let cvPxBuffer = pxBuffer else {
                return nil
            }
    
            CVPixelBufferLockBaseAddress(cvPxBuffer, CVPixelBufferLockFlags(rawValue: 0))
            let bufferWidth = Int(CVPixelBufferGetWidth(cvPxBuffer))
            let bufferHeight = Int(CVPixelBufferGetHeight(cvPxBuffer))
            let bytesPerRow = CVPixelBufferGetBytesPerRow(cvPxBuffer)
    
            guard let baseAddress = CVPixelBufferGetBaseAddress(pxBuffer!) else {
                return nil
            }
    
            for row in 0..<bufferHeight {
                var pixel = baseAddress + row * bytesPerRow
                for col in 0..<bufferWidth {
                    let alpha = pixel
                    alpha.storeBytes(of: UInt8(sin(Double(col) * 0.01 * .pi / Double(UIScreen.main.scale)) * 127 + 127), as: UInt8.self)
    
                    let red = pixel + 1
                    red.storeBytes(of: 255, as: UInt8.self)
    
                    let green = pixel + 2
                    green.storeBytes(of: 0, as: UInt8.self)
    
                    let blue = pixel + 3
                    blue.storeBytes(of: 0, as: UInt8.self)
    
                    pixel += bytesPerPixel;
                }
            }
    
            CVPixelBufferUnlockBaseAddress(cvPxBuffer, CVPixelBufferLockFlags(rawValue: 0))
    
            // Generate image from CVPixelBuffer
            let ciImage = CIImage(cvImageBuffer: cvPxBuffer)
    
            return UIImage(ciImage: ciImage, scale: UIScreen.main.scale, orientation: .up)
        }