Pixel Array to UIImage in Swift

2020-01-29 06:47发布

问题:

I've been trying to figure out how to convert an array of rgb pixel data to a UIImage in Swift.

I'm keeping the rgb data per pixel in a simple struct:

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

I've made my way to the following function, but the resulting image is incorrect:

func imageFromARGB32Bitmap(pixels:[PixelData], width: Int, height: Int)-> UIImage {
    let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
    let bitmapInfo:CGBitmapInfo = CGBitmapInfo(CGImageAlphaInfo.PremultipliedFirst.rawValue)
    let bitsPerComponent:Int = 8
    let bitsPerPixel:Int = 32

    assert(pixels.count == Int(width * height))

    var data = pixels // Copy to mutable []
    let providerRef = CGDataProviderCreateWithCFData(
        NSData(bytes: &data, length: data.count * sizeof(PixelData))
    )

    let cgim = CGImageCreate(
        width,
        height,
        bitsPerComponent,
        bitsPerPixel,
        width * Int(sizeof(PixelData)),
        rgbColorSpace,
        bitmapInfo,
        providerRef,
        nil,
        true,
        kCGRenderingIntentDefault
    )
    return UIImage(CGImage: cgim)!
}

Any tips or pointers on how to properly convert an rgb array to an UIImage?

回答1:

Your only problem is that the data types in your PixelData structure need to be UInt8. I created a test image in a Playground with the following:

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

var pixels = [PixelData]()

let red = PixelData(a: 255, r: 255, g: 0, b: 0)
let green = PixelData(a: 255, r: 0, g: 255, b: 0)
let blue = PixelData(a: 255, r: 0, g: 0, b: 255)

for _ in 1...300 {
    pixels.append(red)
}
for _ in 1...300 {
    pixels.append(green)
}
for _ in 1...300 {
    pixels.append(blue)
}

let image = imageFromARGB32Bitmap(pixels, width: 30, height: 30)

Update for Swift 4:

I updated imageFromARGB32Bitmap to work with Swift 4. The function now returns a UIImage? and guard is used to return nil if anything goes wrong.

func imageFromARGB32Bitmap(pixels: [PixelData], width: Int, height: Int) -> UIImage? {
    guard width > 0 && height > 0 else { return nil }
    guard pixels.count == width * height else { return nil }

    let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
    let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue)
    let bitsPerComponent = 8
    let bitsPerPixel = 32

    var data = pixels // Copy to mutable []
    guard let providerRef = CGDataProvider(data: NSData(bytes: &data,
                            length: data.count * MemoryLayout<PixelData>.size)
        )
        else { return nil }

    guard let cgim = CGImage(
        width: width,
        height: height,
        bitsPerComponent: bitsPerComponent,
        bitsPerPixel: bitsPerPixel,
        bytesPerRow: width * MemoryLayout<PixelData>.size,
        space: rgbColorSpace,
        bitmapInfo: bitmapInfo,
        provider: providerRef,
        decode: nil,
        shouldInterpolate: true,
        intent: .defaultIntent
        )
        else { return nil }

    return UIImage(cgImage: cgim)
}


回答2:

Update for Swift 3

struct PixelData {
    var a: UInt8 = 0
    var r: UInt8 = 0
    var g: UInt8 = 0
    var b: UInt8 = 0
}

func imageFromBitmap(pixels: [PixelData], width: Int, height: Int) -> UIImage? {
    assert(width > 0)

    assert(height > 0)

    let pixelDataSize = MemoryLayout<PixelData>.size
    assert(pixelDataSize == 4)

    assert(pixels.count == Int(width * height))

    let data: Data = pixels.withUnsafeBufferPointer {
        return Data(buffer: $0)
    }

    let cfdata = NSData(data: data) as CFData
    let provider: CGDataProvider! = CGDataProvider(data: cfdata)
    if provider == nil {
        print("CGDataProvider is not supposed to be nil")
        return nil
    }
    let cgimage: CGImage! = CGImage(
        width: width,
        height: height,
        bitsPerComponent: 8,
        bitsPerPixel: 32,
        bytesPerRow: width * pixelDataSize,
        space: CGColorSpaceCreateDeviceRGB(),
        bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue),
        provider: provider,
        decode: nil,
        shouldInterpolate: true,
        intent: .defaultIntent
    )
    if cgimage == nil {
        print("CGImage is not supposed to be nil")
        return nil
    }
    return UIImage(cgImage: cgimage)
}