How to reconstruct grayscale image from intensity values?

空扰寡人 提交于 2021-01-04 12:37:12

问题


It is commonly required to get the pixel data from an image or reconstruct that image from pixel data. How can I take an image, convert it to an array of pixel values and then reconstruct it using the pixel array in Swift using CoreGraphics?

The quality of the answers to this question have been all over the place so I'd like a canonical answer.


回答1:


Get pixel values as an array

This function can easily be extended to a color image. For simplicity I'm using grayscale, but I have commented the changes to get RGB.

func pixelValuesFromImage(imageRef: CGImage?) -> (pixelValues: [UInt8]?, width: Int, height: Int)
{
    var width = 0
    var height = 0
    var pixelValues: [UInt8]?
    if let imageRef = imageRef {
        let totalBytes = imageRef.width * imageRef.height
        let colorSpace = CGColorSpaceCreateDeviceGray()
        
        pixelValues = [UInt8](repeating: 0, count: totalBytes)
        pixelValues?.withUnsafeMutableBytes({
            width = imageRef.width
            height = imageRef.height
            let contextRef = CGContext(data: $0.baseAddress, width: width, height: height, bitsPerComponent: 8, bytesPerRow: width, space: colorSpace, bitmapInfo: 0)
            let drawRect = CGRect(x: 0.0, y:0.0, width: CGFloat(width), height: CGFloat(height))
            contextRef?.draw(imageRef, in: drawRect)
        })
    }

    return (pixelValues, width, height)
}

Get image from pixel values

I reconstruct an image, in this case grayscale 8-bits per pixel, back into a CGImage.

func imageFromPixelValues(pixelValues: [UInt8]?, width: Int, height: Int) ->  CGImage?
{
    var imageRef: CGImage?
    if let pixelValues = pixelValues {
        let bitsPerComponent = 8
        let bytesPerPixel = 1
        let bitsPerPixel = bytesPerPixel * bitsPerComponent
        let bytesPerRow = bytesPerPixel * width
        let totalBytes = width * height
        let unusedCallback: CGDataProviderReleaseDataCallback = { optionalPointer, pointer, valueInt in }
        let providerRef = CGDataProvider(dataInfo: nil, data: pixelValues, size: totalBytes, releaseData: unusedCallback)

        let bitmapInfo: CGBitmapInfo = [CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue), CGBitmapInfo(rawValue: CGImageByteOrderInfo.orderDefault.rawValue)]
        imageRef = CGImage(width: width,
                           height: height,
                           bitsPerComponent: bitsPerComponent,
                           bitsPerPixel: bitsPerPixel,
                           bytesPerRow: bytesPerRow,
                           space: CGColorSpaceCreateDeviceGray(),
                           bitmapInfo: bitmapInfo,
                           provider: providerRef!,
                           decode: nil,
                           shouldInterpolate: false,
                           intent: .defaultIntent)
    }

    return imageRef
}
    

Demoing the code in a Playground

You'll need an image copied into the Playground's Resources folder and then change the filename and extension below to match. The result on the last line is a UIImage constructed from the CGImage.

import Foundation
import CoreGraphics
import UIKit
import PlaygroundSupport

let URL = playgroundSharedDataDirectory.appendingPathComponent("zebra.jpg")
print("URL \(URL)")

var image: UIImage? = nil
if FileManager().fileExists(atPath: URL.path) {
    do {
        try NSData(contentsOf: URL, options: .mappedIfSafe)
    } catch let error as NSError {
        print ("Error: \(error.localizedDescription)")
    }
    image = UIImage(contentsOfFile: URL.path)
} else {
    print("File not found")
}

let (intensityValues, width, height) = pixelValuesFromImage(imageRef: image?.cgImage)
let roundTrippedImage = imageFromPixelValues(pixelValues: intensityValues, width: width, height: height)
let zebra = UIImage(cgImage: roundTrippedImage!)



回答2:


I was having trouble getting Cameron's code above to work, so I wanted to test another method. I found Vacawama's code, which relies on ARGB pixels. You can use that solution and convert each grayscale value to an ARGB value by simply mapping on each value:

/// Assuming grayscale pixels contains floats in the range 0...1
let grayscalePixels: [Float] = ...
let pixels = grayscalePixels.map { 
  let intensity = UInt8(round($0 / Float(UInt8.max)))
  return PixelData(a: UInt8.max, r: intensity, g: intensity, b: intensity)
}
let image = UIImage(pixels: pixels, width: width, height: height)


来源:https://stackoverflow.com/questions/34677133/how-to-reconstruct-grayscale-image-from-intensity-values

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!