Pixel Array to UIImage in Swift

前端 未结 2 1348
长情又很酷
长情又很酷 2020-11-30 08:12

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:

<         


        
相关标签:
2条回答
  • 2020-11-30 08:30

    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)
    }
    
    0 讨论(0)
  • 2020-11-30 08:38

    Note: This is a solution for iOS creating a UIImage. For a solution for macOS and NSImage, see this answer.

    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: 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)
    }
    

    Making it a convenience initializer for UIImage:

    This function works well as a convenience initializer for UIImage. Here is the implementation:

    extension UIImage {
        convenience init?(pixels: [PixelData], width: Int, height: Int) {
            guard width > 0 && height > 0, pixels.count == width * height else { return nil }
            var data = pixels
            guard let providerRef = CGDataProvider(data: Data(bytes: &data, count: data.count * MemoryLayout<PixelData>.size) as CFData)
                else { return nil }
            guard let cgim = CGImage(
                width: width,
                height: height,
                bitsPerComponent: 8,
                bitsPerPixel: 32,
                bytesPerRow: width * MemoryLayout<PixelData>.size,
                space: CGColorSpaceCreateDeviceRGB(),
                bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue),
                provider: providerRef,
                decode: nil,
                shouldInterpolate: true,
                intent: .defaultIntent)
            else { return nil }
            self.init(cgImage: cgim)
        }
    }
    

    Here is an example of its usage:

    // Generate a 500x500 image of randomly colored pixels
    
    let height = 500
    let width = 500
    
    var pixels: [PixelData] = .init(repeating: .init(a: 0, r: 0, g: 0, b: 0), count: width * height)
    for index in pixels.indices {
        pixels[index].a = 255
        pixels[index].r = .random(in: 0...255)
        pixels[index].g = .random(in: 0...255)
        pixels[index].b = .random(in: 0...255)
    }
    let image = UIImage(pixels: pixels, width: width, height: height)
    
    0 讨论(0)
提交回复
热议问题