Swift 图片渲染优化的几种方式

642 阅读3分钟

参考链接

Simulator Screen Shot - iPhone 8 - 2021-05-20 at 09.22.54.png

如上图二十多MB的图渲染

  • 设置UIImageView
let imageView = UIImageView(frame: CGRect(x: 10.0, y: 74.0, width: (ScreenWith - 20.0), height: (ScreenWith - 20.0)))
imageView.backgroundColor = .orange
self.view.addSubview(imageView)
  • 等比例处理ImageView的size
// size 的计量单位不是用 pixel,而是用 point。想要计算出你调整大小后图像的等效尺寸,用主 UIScreen 的 scale,等比例放大你 UIImageView 的 size 大小
let screenScale = UIScreen.main.scale
let transform = CGAffineTransform(scaleX: screenScale, y: screenScale)
let size = imageView.bounds.size.applying(transform)
  • 渲染展示图片
    DispatchQueue.global(qos: .default).async {
            
            let start = CACurrentMediaTime()
            
            guard let path = Bundle.main.path(forResource: "VIIRS_3Feb2012_lrg.jpg", ofType: nil) else {
                fatalError("没找到资源")
            }
            guard let url = Bundle.main.url(forResource: "VIIRS_3Feb2012_lrg.jpg", withExtension: nil) else {
                fatalError("Bundle.main.url 没找到资源")
            }

            
//            let image = UIImage(contentsOfFile: path)
//            let image = CoreGraphicsManager.imageRenderOfCoreGraphics(url: url, size: size) // CoreGraphics
//            let image = CoreImageManager.renderImageOfCoreImage(url: url, size: size)
//            let image = ImageIO.renderImage(url: url, size: size)
//            let image = ImageIO.renderImageWithHintingAndSubsampling(url: url, size: size)
//            let image = VImage.renderVImage(url: url, size: size)
            let image = self.renderImageWithUIKit(url: url, size: size)
            
            DispatchQueue.main.async {
                let duration = 1.0
                UIView.transition(with: imageView, duration: duration, options: [.curveEaseInOut,.transitionCrossDissolve]) {
                    
                    imageView.image = image
                    
                } completion: { (result) in
                    let end = CACurrentMediaTime()
                    print("耗时: \(end - start - duration)")
                }
            }
        }
  • 根据上面的代码有七种方式,这七种渲染方式效果对比如下
UIImage           - 耗时: 2.272907687998668 内存 561MB
CoreGraphics      - 耗时: 1.3122502480000549 内存 16.2MB
CoreImage         - 耗时: 5.254483673999857 内存 20MB
ImageIO  第一种方式 - 耗时: 1.0823896530000638 内存 15MB
         第二种方式 - 耗时: 1.0853783849997853 内存 15MB
vImage            - 耗时: 2.418650589999743 内存 18MB
UIGraphicsImageRenderer - 耗时: 0.9039393570001266 内存 15MB
  • CoreGraphics
class CoreGraphicsManager: NSObject {
    
    static func imageRenderOfCoreGraphics(url:URL,size:CGSize) -> UIImage? {
        guard size != .zero else {
            debugPrint("图片大小不能为零")
            return nil
        }
        guard let imageSource = CGImageSourceCreateWithURL(url as CFURL, nil),
        let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) else {
            debugPrint("CGImageSourceCreate Error")
            return nil
        }
        let context = CGContext(data: nil,
                                width: Int(size.width),
                                height: Int(size.height),
                                bitsPerComponent: image.bitsPerComponent,
                                bytesPerRow: image.bytesPerRow,
                                space: image.colorSpace ?? CGColorSpace(name: CGColorSpace.sRGB)!,
                                bitmapInfo: image.bitmapInfo.rawValue)
        context?.interpolationQuality = .high
        context?.draw(image, in: CGRect(origin: .zero, size: size))
        guard let cgImage = context?.makeImage() else {
            debugPrint("cgImage nil")
            return nil
        }
        return UIImage(cgImage: cgImage)
    }

}
  • CoreImage
class CoreImageManager: NSObject {
    
    static let sharedContext = CIContext(options: [.useSoftwareRenderer : false])

    static func renderImageOfCoreImage(url:URL,size:CGSize) -> UIImage? {
        precondition(size != .zero)
        
        guard let imageSource = CGImageSourceCreateWithURL(url as NSURL, nil),
            let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [CFString: Any],
            let imageWidth = properties[kCGImagePropertyPixelWidth] as? CGFloat,
            let imageHeight = properties[kCGImagePropertyPixelHeight] as? CGFloat
        else {
            return nil
        }
        
        let scale = max(size.width, size.height) / max(imageWidth, imageHeight)
        guard scale >= 0, !scale.isInfinite, !scale.isNaN else { return nil }

        let aspectRatio = imageWidth / imageHeight
        guard aspectRatio >= 0, !aspectRatio.isInfinite, !aspectRatio.isNaN else { return nil }
        guard let image = CIImage(contentsOf: url) else {
            return nil
        }
        
        let filter = CIFilter(name: "CILanczosScaleTransform")
        filter?.setValue(image, forKey: kCIInputImageKey)
        filter?.setValue(scale, forKey: kCIInputScaleKey)
        filter?.setValue(aspectRatio, forKey: kCIInputAspectRatioKey)
        
        guard let outputCIImage = filter?.outputImage,
            let outputCGImage = sharedContext.createCGImage(outputCIImage, from: outputCIImage.extent)
        else {
            return nil
        }
        
        return UIImage(cgImage: outputCGImage)
    }
    
}
  • ImageIO
import MobileCoreServices

class ImageIO: NSObject {
    
    static func renderImage(url:URL,size:CGSize) -> UIImage? {
        precondition(size != .zero)
        let options: [CFString: Any] = [
            kCGImageSourceThumbnailMaxPixelSize: max(size.width, size.height),
            kCGImageSourceCreateThumbnailFromImageAlways: true,
            kCGImageSourceCreateThumbnailWithTransform: true
        ]
        
        guard let imageSource = CGImageSourceCreateWithURL(url as NSURL, nil),
            let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary)
        else {
            return nil
        }
        return UIImage(cgImage: image)
    }
    
    static func renderImageWithHintingAndSubsampling(url:URL,size:CGSize) -> UIImage? {
        precondition(size != .zero)
        
        guard let imageSource = CGImageSourceCreateWithURL(url as NSURL, nil),
            let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [CFString: Any],
            let imageWidth = properties[kCGImagePropertyPixelWidth] as? CGFloat,
            let imageHeight = properties[kCGImagePropertyPixelHeight] as? CGFloat
        else {
            return nil
        }
        
        var options: [CFString: Any] = [
            kCGImageSourceThumbnailMaxPixelSize: max(size.width, size.height),
            kCGImageSourceCreateThumbnailFromImageAlways: true,
            kCGImageSourceCreateThumbnailWithTransform: true
        ]
        
        if let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, url.pathExtension as CFString, kUTTypeImage)?.takeRetainedValue() {
            options[kCGImageSourceTypeIdentifierHint] = uti
            
            if uti == kUTTypeJPEG || uti == kUTTypeTIFF || uti == kUTTypePNG ||
                String(uti).hasPrefix("public.heif")
            {
                switch min(imageWidth / size.width, imageHeight / size.height) {
                case ...2.0:
                    options[kCGImageSourceSubsampleFactor] = 2.0
                case 2.0...4.0:
                    options[kCGImageSourceSubsampleFactor] = 4.0
                case 4.0...:
                    options[kCGImageSourceSubsampleFactor] = 8.0
                default:
                    break
                }
            }
           
        }
        
        guard let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) else {
            return nil
        }
        
        return UIImage(cgImage: image)
    }

}
  • VImage
import Accelerate.vImage

class VImage: NSObject {
    
    static func renderVImage(url:URL,size:CGSize) -> UIImage? {
        precondition(size != .zero)
        // Decode the source image
        guard let imageSource = CGImageSourceCreateWithURL(url as NSURL, nil),
            let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil),
            let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [CFString: Any],
            let imageWidth = properties[kCGImagePropertyPixelWidth] as? vImagePixelCount,
            let imageHeight = properties[kCGImagePropertyPixelHeight] as? vImagePixelCount
        else {
           return nil
        }
        // Define the image format
        var format = vImage_CGImageFormat(bitsPerComponent: 8,
                                          bitsPerPixel: 32,
                                          colorSpace: nil,
                                          bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.first.rawValue),
                                          version: 0,
                                          decode: nil,
                                          renderingIntent: .defaultIntent)
        
        var error: vImage_Error
        
        // Create and initialize the source buffer
        var sourceBuffer = vImage_Buffer()
        defer { sourceBuffer.data.deallocate() }
        error = vImageBuffer_InitWithCGImage(&sourceBuffer,
                                             &format,
                                             nil,
                                             image,
                                             vImage_Flags(kvImageNoFlags))
        guard error == kvImageNoError else { return nil }
        
        // Create and initialize the destination buffer
        var destinationBuffer = vImage_Buffer()
        error = vImageBuffer_Init(&destinationBuffer,
                                  vImagePixelCount(size.height),
                                  vImagePixelCount(size.width),
                                  format.bitsPerPixel,
                                  vImage_Flags(kvImageNoFlags))
        guard error == kvImageNoError else { return nil }

        // Scale the image
        error = vImageScale_ARGB8888(&sourceBuffer,
                                     &destinationBuffer,
                                     nil,
                                     vImage_Flags(kvImageNoFlags))
        guard error == kvImageNoError else { return nil }

        // Create a CGImage from the destination buffer
        guard let resizedImage =
            vImageCreateCGImageFromBuffer(&destinationBuffer,
                                          &format,
                                          nil,
                                          nil,
                                          vImage_Flags(kvImageNoAllocate),
                                          &error)?.takeRetainedValue(),
            error == kvImageNoError
        else {
            return nil
        }
        
        return UIImage(cgImage: resizedImage)
    }
    
}
  • UIGraphicsImageRenderer
func renderImageWithUIKit(url:URL,size:CGSize) -> UIImage? {
        guard let image = UIImage(contentsOfFile: url.path) else {
            return nil
        }
        
        let renderer = UIGraphicsImageRenderer(size: size)
        return renderer.image { (context) in
            image.draw(in: CGRect(origin: .zero, size: size))
        }
}