swift - 从灰度矩阵创建 CGImage/UIImage

标签 swift image-processing uiimage core-graphics

我有一个灰度图像像素矩阵,例如:

[ [0, 0, 125],
  [10, 50, 255],
  [90, 0, 255] ]

我的目标是对其应用色调 (UIColor) 并从包含它的结构中导出 CGImage/UIImage

public typealias Pixel = UInt8

extension UIColor {
    var red: Float { return Float(CIColor(color: self).red * 255) }
    var green: Float { return Float(CIColor(color: self).green * 255) }
    var blue: Float { return Float(CIColor(color: self).blue * 255) }
    var alpha: Float { return Float(CIColor(color: self).alpha * 255) }
}

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

public struct Map {
    let pixelCount: UInt
    let pixels: [Pixel] //all pixels of an image, linear
    let dimension: UInt //square root of pixel count
    let tintColor: UIColor = UIColor(red: 9/255, green: 133/255, blue: 61/255, alpha: 1)

    public var image: UIImage? {
        var pixelsData = [PixelData]()
        pixelsData.reserveCapacity(Int(pixelCount) * 3)
        let alpha = UInt8(tintColor.alpha)
        let redValue = tintColor.red
        let greenValue = tintColor.green
        let blueValue = tintColor.blue
        let red: [PixelData] = pixels.map {
            let redInt: UInt8 = UInt8((Float($0) / 255.0) * redValue)
            return PixelData(r: redInt, g: 0, b: 0, a: alpha)
        }
        let green: [PixelData] = pixels.map {
            let greenInt: UInt8 = UInt8((Float($0) / 255.0) * greenValue)
            return PixelData(r: 0, g: greenInt, b: 0, a: alpha) }
        let blue: [PixelData] = pixels.map {
            let blueInt: UInt8 = UInt8((Float($0) / 255.0) * blueValue)
            return PixelData(r: 0, g: 0, b: blueInt, a: alpha) }
        pixelsData.append(contentsOf: red)
        pixelsData.append(contentsOf: green)
        pixelsData.append(contentsOf: blue)
        let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
        let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue)
        let bitsPerComponent = 8
        let bitsPerPixel = 32
        let dimension: Int = Int(self.dimension)
        var data = pixelsData
        guard let providerRef = CGDataProvider(
            data: NSData(bytes: &data, length: data.count * MemoryLayout<PixelData>.size)
            ) else { return nil }
        if let cgim = CGImage(
            width: dimension,
            height: dimension,
            bitsPerComponent: bitsPerComponent,
            bitsPerPixel: bitsPerPixel,
            bytesPerRow: dimension * MemoryLayout<PixelData>.size,
            space: rgbColorSpace,
            bitmapInfo: bitmapInfo,
            provider: providerRef,
            decode: nil,
            shouldInterpolate: true,
            intent: .defaultIntent
            ) {
            return UIImage(cgImage: cgim)
        }
        return nil
    }
}

问题是输出看起来乱码。我用过this tutorialthis SO thread但没有成功。 Playground 上的结果是:

enter image description here

(输出在那里,只是勉强可见)

感谢任何帮助!

最佳答案

有两个关键问题。

  1. 代码计算每个灰度像素的所有红色值并为每个创建四字节 PixelData(即使只填充红色 channel )并将其添加到 pixelsData 数组。然后它对绿色值重复该操作,然后对蓝色值重复该操作。结果是图像所需数据的三倍,而且只使用了红色 channel 数据。

    相反,我们应该计算一次 RGBA 值,为每个值创建一个 PixelData,然后逐个像素地重复这个过程。

  2. premultipliedFirst 表示 ARGB。但是您的结构使用的是 RGBA,因此您需要 premultipliedLast

因此:

func generateTintedImage(completion: @escaping (UIImage?) -> Void) {
    DispatchQueue.global(qos: .userInitiated).async {
        let image = self.tintedImage()
        DispatchQueue.main.async {
            completion(image)
        }
    }
}

private func tintedImage() -> UIImage? {
    let tintRed = tintColor.red
    let tintGreen = tintColor.green
    let tintBlue = tintColor.blue
    let tintAlpha = tintColor.alpha

    let data = pixels.map { pixel -> PixelData in
        let red = UInt8((Float(pixel) / 255) * tintRed)
        let green = UInt8((Float(pixel) / 255) * tintGreen)
        let blue = UInt8((Float(pixel) / 255) * tintBlue)
        let alpha = UInt8(tintAlpha)
        return PixelData(r: red, g: green, b: blue, a: alpha)
    }.withUnsafeBytes { Data($0) }

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

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

    return UIImage(cgImage: cgImage)
}

我还重命名了一些变量,使用 stride 而不是 size,将 dimension 替换为 widthheight 这样我就可以处理非方形图像等。

我还建议不要对这种计算密集型的任何东西使用计算属性,所以我给了它一个异步方法,您可以按如下方式使用它:

let map = Map(with: image)
map.generateTintedImage { image in
    self.tintedImageView.image = image
}

无论如何,上面的结果如下,其中最右边的图像是您的着色图像:

enter image description here


不用说,要将您的矩阵转换为您的像素数组,您只需展平数组的数组即可:

let matrix: [[Pixel]] = [
    [0, 0, 125],
    [10, 50, 255],
    [90, 0, 255]
]
pixels = matrix.flatMap { $0 }

这是一个并行化的再现,它在内存缓冲区方面也稍微更有效:

private func tintedImage() -> UIImage? {
    let tintAlpha = tintColor.alpha
    let tintRed = tintColor.red / 255
    let tintGreen = tintColor.green / 255
    let tintBlue = tintColor.blue / 255

    let alpha = UInt8(tintAlpha)

    let colorSpace = CGColorSpaceCreateDeviceRGB()
    let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue
    let bitsPerComponent = 8
    let bytesPerRow = width * MemoryLayout<PixelData>.stride

    guard
        let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo),
        let data = context.data
    else {
        return nil
    }

    let buffer = data.bindMemory(to: PixelData.self, capacity: width * height)

    DispatchQueue.concurrentPerform(iterations: height) { row in
        let start = width * row
        let end = start + width
        for i in start ..< end {
            let pixel = pixels[i]
            let red = UInt8(Float(pixel) * tintRed)
            let green = UInt8(Float(pixel) * tintGreen)
            let blue = UInt8(Float(pixel) * tintBlue)
            buffer[i] = PixelData(r: red, g: green, b: blue, a: alpha)
        }
    }

    return context.makeImage()
        .flatMap { UIImage(cgImage: $0) }
}

关于swift - 从灰度矩阵创建 CGImage/UIImage,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56427766/

相关文章:

Swift 协议(protocol)嵌套在一个类中

c# - 更快的 c# 图像处理

iphone - iOS : Looping over the pixels of an Image

ios - Redux:最佳实践(使用 Swift)

iphone - 如何在iphone中缓存图像

ios - Cocoapods + 无法为 'x' 加载底层模块

ios - 如何为具有命名参数的完成处理程序创建类型别名

ios - 如何调用依赖于 reactivecocoa4 中不同信号生成器的信号生成器

image-processing - 标签在图像分割中的工作原理 [SegNet]

python-3.x - 如何修复使用 sklearn.mixture.GaussianMixture 拟合 GMM 时的 ValueError?