Swift:使用 CIColorCube 将 LUT查找表应用于图像缓慢性能

问题描述

我正在使用 CIColorCube 将 LUT(来自 .png - Example LUT Image)应用于图像。它运作良好。我面临的唯一问题是当我创建按钮缩略图时,应用会停止几秒钟。

按钮看起来像这样 -> Buttons Example Image

这是我的代码

    @IBOutlet weak var filteRSScrollView: UIScrollView!
    var filters = ["-","Filter1","Filter2","Filter3","Filter4"]
    
    override func viewDidLoad() {
        createFilters()
    }
    
    func createFilters() {
        var x: CGFloat = 10
        let y: CGFloat = 0
        let width: CGFloat = 60
        let height: CGFloat = 83
        let gap: CGFloat = 2
    
        for i in 0..<filters.count {
            let filterButton = UIButton(type: .custom)
            filterButton.frame = CGRect(x: x,y: y,width: width,height: height)
            filterButton.imageView?.contentMode = .scaleAspectFill
            filterButton.setTitleColor(#colorLiteral(red: 1,green: 1,blue: 1,alpha: 0),for: .normal)
            
            let text = UILabel()
            text.frame = CGRect(x: 0,y: height - 21,width: filterButton.frame.width,height: 21)
            text.textAlignment = .center
            text.backgroundColor = #colorLiteral(red: 0.9372549057,green: 0.3490196168,blue: 0.1921568662,alpha: 1)
            text.textColor = .white
            text.font = .systemFont(ofSize: 8.5,weight: .medium)
            
            filterButton.addSubview(text)
            filteRSScrollView.insertSubview(filterButton,at: 1)
            
            x += width + gap
    
            if i == 0 {
                filterButton.setimage(originalImage,for: .normal)
                text.text = "-"
                text.backgroundColor = #colorLiteral(red: 0.1215686275,green: 0.1215686275,blue: 0.1215686275,alpha: 1)
            }
            else {
                // THIS LINE MAKES THE APP STOP FOR A FEW SECONDS
                let filteredImage = filterFromLUT(inputimage: originalCIImage,lut: "\(filters[i])")?.outputimage
                filterButton.setimage(UIImage(ciImage: filteredImage!),for: .normal)
                text.text = "\(filters[i])"
            }
        }
        
        filteRSScrollView.contentSize = CGSize(width: x,height: height)
    }
    
    func filterFromLUT(inputimage: CIImage,lut: String) -> CIFilter? {
        let dimension = 64
        
        let lutimage = UIImage(named: lut)!.cgImage
        let width = lutimage!.width
        let height = lutimage!.height
        let rowNum = width / dimension
        let columnNum = height / dimension
        
        let bitmap = createBitmap(image: lutimage!)
    
        let dataSize = dimension * dimension * dimension * MemoryLayout<Float>.size * 4
        var array = Array<Float>(repeating: 0,count: dataSize)
    
        var bitmapOffest: Int = 0
        var z: Int = 0
    
        for _ in stride(from: 0,to: rowNum,by: 1) {
          for y in stride(from: 0,to: dimension,by: 1) {
            let tmp = z
            for _ in stride(from: 0,to: columnNum,by: 1) {
              for x in stride(from: 0,by: 1) {
    
                let dataOffset = (z * dimension * dimension + y * dimension + x) * 4
    
                let position = bitmap!
                  .advanced(by: bitmapOffest)
    
                array[dataOffset + 0] = Float(position
                  .advanced(by: 0)
                  .pointee) / 255
    
                array[dataOffset + 1] = Float(position
                  .advanced(by: 1)
                  .pointee) / 255
    
                array[dataOffset + 2] = Float(position
                  .advanced(by: 2)
                  .pointee) / 255
    
                array[dataOffset + 3] = Float(position
                  .advanced(by: 3)
                  .pointee) / 255
    
                bitmapOffest += 4
              }
              z += 1
            }
            z = tmp
          }
          z += columnNum
        }
    
        free(bitmap)
    
        let data = Data.init(bytes: array,count: dataSize)
        
        // Create CIColorCube filter
        let filter = CIFilter.colorCube()
        filter.inputimage = inputimage
        filter.cubedata = data
        filter.cubedimension = Float(dimension)
    
        return filter
    }
    
    func createBitmap(image: CGImage) -> UnsafeMutablePointer<UInt8>? {
        let width = image.width
        let height = image.height
    
        let bitsPerComponent = 8
        let bytesPerRow = width * 4
        
        let bitmapSize = bytesPerRow * height
    
        guard let data = malloc(bitmapSize) else {
            return nil
        }
        
        let context = CGContext(
            data: data,height: height,bitsPerComponent: bitsPerComponent,bytesPerRow: bytesPerRow,space: CGColorSpaceCreateDeviceRGB(),bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue,releaseCallback: nil,releaseInfo: nil)
    
        context!.draw(image,in: CGRect(x: 0,y: 0,height: height))
    
        return data.bindMemory(to: UInt8.self,capacity: bitmapSize)
    }

我认为可能是 createBitmap() 函数中的 CGContext 导致了这种情况。有大佬知道怎么解决吗?

解决方法

您可以采取一些措施来提高性能:

  • 目前,您正在处理原始输入图像(我认为它非常大)只是为了在 60 x 83 按钮中显示结果。在将图像放入过滤器之前,请考虑先缩小图像
  • 您可以通过使图像处理代码异步来避免阻塞 UI。只需创建适当大小的按钮并DispatchQueue.global().async { ... }进行图像处理。
  • 不要使用 .setImage(UIImage(ciImage: filteredImage)。根据我的经验,以这种方式从 UIImage 创建 CIImage 非常不可预测。而是使用 CIContext 将过滤后的图像呈现为 CGImage,然后将其转换为 UIImage。还要尝试重复使用单个 CIContext,而不是为每个图像重新创建它。
  • 可以使用 vDSP 加速将 LUT 图像转换为浮点数据数组的代码(见下文)。

使用 vDSP 创建 LUT 数据:

let lutImage = UIImage(named: lut)!.cgImage
let dimension = lutImage.height

// get data from image
let lutImageData = lutImage.dataProvider?.data
let lutImageDataPtr = CFDataGetBytePtr(lutImageData)!

// convert to float and divide by 255
let numElements = dimension * dimension * dimension * 4
let inDataFloat = UnsafeMutablePointer<Float>.allocate(capacity: numElements)
vDSP_vfltu8(lutImageDataPtr,1,inDataFloat,vDSP_Length(numElements))
var div: Float = 255.0
vDSP_vsdiv(inDataFloat,&div,vDSP_Length(numElements))

// convert buffer pointer to data
let lutData = NSData(bytesNoCopy: inDataFloat,length: numElements * MemoryLayout<Float>.size,freeWhenDone: true)