如果给定一个CIImage,最快的将图像数据写入磁盘的方法是什么?

7
我正在使用PhotoKit,并实现了用户可以在其照片库中应用的过滤器。我目前正在获取图像,应用过滤器,将编辑后的版本返回为CIImage,然后我使用UIImageJPEGRepresentationCIImage转换为NSData,以便将其写入磁盘。虽然这很好用,但当用户尝试编辑非常大(例如30 MB)的照片时,这可能需要超过30秒才能完成,其中98%的时间花费在UIImageJPEGRepresentation上(从Instruments获取统计数据)。
如果可能的话,我正在寻找一种更有效的方法来保存此编辑后的照片到磁盘而不影响质量。
我了解UIImagePNGRepresentation可能会导致更好的质量,但这甚至比JPEG表示更慢。
以下是我目前正在使用的默认优先级线程(qos_class_utility):
func jpegRepresentationOfImage(image: CIImage) -> NSData {
    let eaglContext = EAGLContext(API: .OpenGLES2)
    let ciContext = CIContext(EAGLContext: eaglContext)

    let outputImageRef = ciContext.createCGImage(image, fromRect: image.extent())
    let uiImage = UIImage(CGImage: outputImageRef, scale: 1.0, orientation: UIImageOrientation.Up)

    return UIImageJPEGRepresentation(uiImage, 0.9) //this takes upwards of 20-30 seconds with large photos!
}

//writing out to disk:
var error: NSError?
let success = jpegData.writeToURL(contentEditingOutput.renderedContentURL, options: NSDataWritingOptions.AtomicWrite, error: &error)

Brad 发现了似乎出现了减速问题 https://github.com/BradLarson/GPUImage - Stephen J
2个回答

9

尝试使用CIContext方法writeJPEGRepresentation(可用于iOS 10),以避免使用UIImage并使写入更快。

extension CIImage {

    @objc func saveJPEG(_ name:String, inDirectoryURL:URL? = nil, quality:CGFloat = 1.0) -> String? {
        
        var destinationURL = inDirectoryURL
        
        if destinationURL == nil {
            destinationURL = try? FileManager.default.url(for:.documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
        }
        
        if var destinationURL = destinationURL {
            
            destinationURL = destinationURL.appendingPathComponent(name)
            
            if let colorSpace = CGColorSpace(name: CGColorSpace.sRGB) {
                
                do {

                    let context = CIContext()

                    try context.writeJPEGRepresentation(of: self, to: destinationURL, colorSpace: colorSpace, options: [kCGImageDestinationLossyCompressionQuality as CIImageRepresentationOption : quality])
                    
                    return destinationURL.path
                    
                } catch {
                    return nil
                }
            }
        }
        
        return nil
    }
}

@objc关键字使您能够在Objective-C中调用方法,如下所示:

NSString* path = [image saveJPEG:@"image.jpg" inDirectoryURL:url quality:1.0];

对于PNG格式,在iOS 11中有一个类似的方法writePNGRepresentation:

   if #available(iOS 11.0, *) {

        if let colorSpace = CGColorSpace(name: CGColorSpace.sRGB) {
                
            do {
                let format = CIFormat.RGBA8
                    
                try context.writePNGRepresentation(of: self, to: destinationURL, format: format, colorSpace: colorSpace)
                    
                return destinationURL.path
                    
            } catch {
                return nil
            }   
        }
    }

顺便说一句,如果您只需要Data作为输出,那么您无需先将其保存到磁盘中。只需调用context.jpegRepresentation(of:colorSpace:options:)即可。 - Maschina

8
我建议直接使用CGImageDestination将CGImage转换为ImageIO格式。您可以传递一个字典到CGImageDestinationAddImage中,以指示压缩质量、图像方向等。
CFDataRef save_cgimage_to_jpeg (CGImageRef image)
{
    CFMutableDataRef cfdata = CFDataCreateMutable(nil,0);
    CGImageDestinationRef dest = CGImageDestinationCreateWithData(data, CFSTR("public.jpeg"), 1, NULL);
    CGImageDestinationAddImage(dest, image, NULL);
    if(!CGImageDestinationFinalize(dest))
        ; // error
    CFRelease(dest);
    return cfdata
}

这确实快了不少,至少快了8秒。22秒仍然是很长的时间,但我想当图像很大时,这是可以预料的。 - Jordan H
1
你尝试过改变线程优先级吗?实用程序是最低的。如果你的目标是及时执行,可以尝试在NSOperationQueue中使用.UserInitiated或.UserInteractive QoS模式。 - johnnyclem

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接