Image downsampling

结论

先说结论,不使用 Downsampling 与使用 Downsampling,图片在内存中的占用大小分别如下图所示
notion image

问题

图片显示有一个涉及内存的问题是:
如上面所显示的图片,在 Finder 中显示磁盘的占用大小为 3.4 MB,而在内存中其占用大小会是 14.1 MB

原因

造成上图的原因在于,图片在磁盘占用大小并不等于内存的占用大小
图片在内存中的占用大小至于图片的大小有关,而与图片文件(在磁盘上)的大小无关
 
在 iOS 上,要显示一张图片,系统首先会解码与解压缩图片数据
通常来说,解码后的图片,1 个像素会占用 4 bytes
💡
1 byte for red, 1 byte for green, 1 byte for blue, and 1 byte for the alpha component
 
以上面显示的图片为例,其尺寸大小为 2560x1440
于是,这张图片在内存的占用大小为
(2560 * 1440) * 4bytes = 14,336,000bytes = 14.0625MB

解决

若想通过缩小和重绘图片来解决这个问题,实际上也不能解决问题
  • 首先,调整一张图片的大小是比较消耗 CPU 的工作
  • 其次,在调整图片大小时,系统依然需要对原始图片进行解码和解压缩,实际上内存占用仍然会有那么几个时刻暴增
 
最后,我们需要使用 ImageIO 实现 Downsampling,具体算法
func downsample( imageAt imageURL: URL, to pointSize: CGSize, scale: CGFloat = UIScreen.main.scale ) -> UIImage? { // Create an CGImageSource that represent an image let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary guard let imageSource = CGImageSourceCreateWithURL(imageURL as CFURL, imageSourceOptions) else { return nil } // Calculate the desired dimension let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale // Perform downsampling let downsampleOptions = [ kCGImageSourceCreateThumbnailFromImageAlways: true, kCGImageSourceShouldCacheImmediately: true, kCGImageSourceCreateThumbnailWithTransform: true, kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels ] as CFDictionary guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else { return nil } // Return the downsampled image as UIImage return UIImage(cgImage: downsampledImage) }

选项解释

kCGImageSourceShouldCache

当这个选项被设置为 false 时, Core Graphic framework 在创建 CGImageSource 对象时,不会同时解码图片数据

kCGImageSourceShouldCacheImmediately

当这个选项被设置为 true 时, Core Graphic 会将 downsampling 与解码图片数据两个操作同时进行
 
💡
结合 kCGImageSourceShouldCachekCGImageSourceShouldCacheImmediately 两个选项,开发者就控制了使用 CPU 进行解码操作的时机

kCGImageSourceCreateThumbnailWithTransform

当这个选项被设置为 true 时, Core Graphic 会保持 downsampled 图片与原始图片的朝向相同

什么时候应该使用 Downsampling

需要注意的是, Downsampling 操作也是一个消耗 CPU 的操作,因此,应该尽可能在「图片源」处就将图片调整到合适大小
使用 Downsampling 的时机,便是图片的大小远大于显示图片的区域(即过于浪费的时候就应该用了)
对于需要经常使用图片,并且它是需要 Downsampling 的话,应该将 downsampled 的图片缓存起来

计算图片占用内存大小的方法

let cgImage = <anImage>.cgImage! let result = cgImage.bytesPerRow * cgImage.height

References