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

[ [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但没有成功。操场上的结果是:
swift - 从灰度矩阵创建CGImage/UIImage-LMLPHP
(输出就在那里,几乎看不见)
感谢任何帮助!

最佳答案

有两个关键问题。
代码正在计算每个灰度像素的所有红色值,并为每个像素创建4字节的PixelData(即使只填充了红色通道),并将其添加到pixelsData数组中。然后对绿色值重复该操作,对蓝色值重复该操作。这将产生三倍于图像所需的数据,并且只使用红色通道数据。
相反,我们应该计算一次RGBA值,为每个值创建一个PixelData,然后逐像素重复此操作。
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,用dimensionwidth替换了height,以便可以处理非正方形图像等。
我还建议不要将computed属性用于计算量如此大的任何事情,因此我给了这个方法一个异步方法,您可以使用如下:
let map = Map(with: image)
map.generateTintedImage { image in
    self.tintedImageView.image = image
}

无论如何,上面的结果如下,其中最右边的图像是您的着色图像:
swift - 从灰度矩阵创建CGImage/UIImage-LMLPHP
不用说,要将矩阵转换为pixels数组,只需将数组展平即可:
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/

10-13 04:22