iOS大图片处理(一) —— 缩略图方案和性能

图像是每个应用程序不可缺少的一部分。尤其是面对高精度大图片的处理时,方式不当可能会出现OOM。

超大图片一般有两种处理方法

1. 缩略图
2. 分片显示

本期先探讨缩略图方案。完整代码在这里DemoSwift。

注:缩略后的图片显示的视觉效果和原图视觉效果一致,要求图片不会模糊


UIScreen.main.scale

谈缩略图之前先了解下scale.官方说明如下:

The natural scale factor associated with the screen.
This value reflects the scale factor needed to convert from the default logical coordinate space into the device coordinate space of this screen. The default logical coordinate space is measured using points. For Retina displays, the scale factor may be 3.0 or 2.0 and one point can represented by nine or four pixels, respectively. For standard-resolution displays, the scale factor is 1.0 and one point equals one pixel.

大概意思就是一个点对应几个像素点,7Plus上UIScreen.main.scale值是3,即一个点对应三个像素点

UIKit

UIGraphicsBeginImageContextWithOptions

在UIKIt框架中可以发现高级的图片调整API。提供一个UIImage对象,一个临时图形上下文,用来渲染一个缩放版本,UIGraphicsBeginImageContextWithOptions() 和 UIGraphicsGetImageFromCurrentImageContext()

    func resizeUI(_ size: CGSize) -> UIImage? {
        UIGraphicsBeginImageContextWithOptions(size, true, 0.0)
        
        draw(in: CGRect(origin: .zero, size: size))
        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        
        UIGraphicsEndImageContext()
        
        return newImage
    }

UIGraphicsBeginImageContextWithOptions有三个参数,第一个是缩略后的尺寸,第二个是否考虑透明度,第三个是scale。第三个参数可以传UIScreen.main.scale,也可以传0.0,会自动转换成UIScreen.main.scale。

scale
The scale factor to apply to the bitmap. If you specify a value of 0.0, the scale factor is set to the scale factor of the device’s main screen.

详细的可以看官方注释说明

CoreGraphics

CGBitmapContextCreate & CGContextDrawImage

CoreGraphics / Quartz 2D提供了一套较低级别的API

func resizeCG(_ size: CGSize) -> UIImage? {
    guard let cgImage = self.cgImage else { return nil }
    let scale: CGFloat = UIScreen.main.scale
    let bitsPerComponent = cgImage.bitsPerComponent
    let bytesPerRow = cgImage.bytesPerRow
    let colorSpace = cgImage.colorSpace
    let bitmapInfo = cgImage.bitmapInfo
    
    guard let context = CGContext(data: nil,
                                  width: Int(size.width * scale),
                                  height: Int(size.height * scale),
                                  bitsPerComponent: bitsPerComponent,
                                  bytesPerRow: bytesPerRow,
                                  space: colorSpace!,
                                  bitmapInfo: bitmapInfo.rawValue) else { return nil }
    
    context.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.width * scale, height: size.height * scale))
    
    let resizedImage = context.makeImage().flatMap {
        UIImage(cgImage: $0)
    }
    
    return resizedImage
}

CGContext(data: UnsafeMutableRawPointer?, width: Int, height: Int, bitsPerComponent: Int, bytesPerRow: Int, space: CGColorSpace, bitmapInfo: UInt32)

参数:
data:指向要渲染的绘制内存的地址。这个内存块的大小至少是(bytesPerRow*height)个字节。使用时可填NULL或unsigned char类型的指针。

width:bitmap的宽度,单位为像素。

height:bitmap的高度,单位为像素。

bitsPerComponent:内存中像素的每个组件的位数.例如,对于32位像素格式和RGB 颜色空间,你应该将这个值设为8。

bytesPerRow:bitmap的每一行在内存所占的比特数,一个像素一个byte。

colorspace:bitmap上下文使用的颜色空间。

注:CoreGraphics中坐标原点在左下角,和UIKit中坐标原点在左上角不同

疑问:func draw(_ image: CGImage, in rect: CGRect, byTiling: Bool = default)中,rect的宽高单位是 点 还是像素?

测试结果推测单位是像素

不乘以scale时,缩略图的结果截图如下:
IMG_0157.PNG

CoreImage

CoreImage是IOS5中新加入的框架,里面提供了强大高效的图像处理功能,用来对基于像素的图像进行操作与分析。IOS提供了很多强大的滤镜(Filter),这些Filter提供了各种各样的效果,并且还可以通过滤镜链将各种效果的Filter叠加起来,形成强大的自定义效果,如果你对该效果不满意,还可以子类化滤镜。

func resizeCI(_ size: CGSize) -> UIImage? {
    
    guard  let cgImage = self.cgImage else { return nil }
    
    let widthScale = (Double)(size.width * UIScreen.main.scale) / (Double)(self.size.width)
    let heightScale = (Double)(size.height * UIScreen.main.scale) / (Double)(self.size.height)
    let scale = max(widthScale, heightScale)
    
    let image = CIImage(cgImage: cgImage)
    
    let filter = CIFilter(name: "CILanczosScaleTransform")!
    filter.setValue(image, forKey: kCIInputImageKey)
    filter.setValue(NSNumber(value: Float(scale)), forKey: kCIInputScaleKey)
    filter.setValue(1.0, forKey:kCIInputAspectRatioKey)
    
    guard let outputImage = filter.value(forKey: kCIOutputImageKey) as? CIImage else { return nil}
    
    let context = CIContext(options: [kCIContextUseSoftwareRenderer: false])
    
    let resizedImage = context.createCGImage(outputImage, from: outputImage.extent).flatMap {
        UIImage(cgImage: $0)
    }
    return resizedImage
}

Image I/O

func resizeIO(_ size: CGSize) -> UIImage? {
    guard let data = UIImagePNGRepresentation(self) else { return nil }
    
    let scale = UIScreen.main.scale
    let maxPixelSize = max(size.width, size.height) * scale
    
    guard let imageSource = CGImageSourceCreateWithData(data as CFData, nil) else { return nil }
    
    let options: [CFString: Any] = [kCGImageSourceThumbnailMaxPixelSize: maxPixelSize,
                                  kCGImageSourceCreateThumbnailFromImageAlways: true]
    let newImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary).flatMap {
        UIImage(cgImage: $0)
    }
    
    return newImage
}

性能

性能从时间和空间两个维度分析
以下数据是在iPhone7Plus上测试的。图片大小是3024.0 * 4032.0, demo中有图片


iOS大图片处理(一) —— 缩略图方案和性能_第1张图片
内存.png

iOS大图片处理(一) —— 缩略图方案和性能_第2张图片
image.png

不处理时图片解码时间没想到用什么方法测试,有知道的感谢告知

首先排除CoreGraphics和Image I/O,UIKit解压后占内存比CoreImage小,但是解压过程中比CoreImage占内存大

以上内容有错误欢迎指摘,共同进步

完整demo

你可能感兴趣的:(iOS大图片处理(一) —— 缩略图方案和性能)