iOS开发 App图像使用及内存优化(wwdc2018)
一、图像如何显示
内存使用与图像的尺寸有关,而不是与文件大小有关。
举个例子,我有一张非常漂亮的图片,我想用它作为iPad应用程序的墙纸。
它的大小是2048✖️1536,磁盘上的文件是590KB。
但是它到底用了多少内存呢?10MB。
10MB,太大了!其原因是,将宽像素数*高像素数:2048✖️1536,再✖️每像素4个字节,得到大约10MB。
那它为什么这么大呢?好吧,我们得谈谈图像在iOS上是如何工作的:
1. 有加载-解码-渲染阶段:
加载阶段将这个590KB的JPEG文件压缩后加载到内存中。
解码器将该JPEG文件转换为GPU可以读取的格式。
现在,这需要解压缩,这使得它有10MB。
一旦被解码,就可以随意渲染。
2. 图片格式:
2.1 SRGB格式
我们用SRGB格式得到了每像素4个字节。
这通常是图形中最常见的图像格式。
它是每像素8位,所以你有1个字节代表红色,1个字节代表绿色,1个字节代表蓝色,还有一个alpha分量。
不过,我们可以做得更大一些。
2.2 宽格式(wide format)
iOS硬件可以呈现宽格式(wide format)。
现在,宽格式为了得到有表现力的颜色,每像素需要2个字节,所以我们把图像的大小增加了一倍。
iPhone7、8、X和iPhone上的摄像头,一些iPad专业人士非常适合捕捉这种高保真的内容。
你也可以使用它的超级准确的颜色,如体育标志等。
但是这些只有在宽格式显示上才真正有用,所以我们不想在不需要的时候使用它。
2.3 luminance and alpha 8(AL8)
另一方面,我们实际上可以缩小规模。
现在,有一个亮度和alpha 8格式。
此格式仅存储灰度和alpha值。
这通常用于着色器,就像金属应用程序之类。
在我们的使用中不太常见。
2.4 alpha 8(AL8)
实际上我们可以变得更小。
我们可以继续使用alpha8格式。
现在,alpha8只有一个通道,每像素一个字节。
非常小。
它比SRGB小75%。
现在,这很适合蒙版或单色的文本,因为我们使用75%的内存。
3. 选择正确格式
如果我们看一下细分,我们可以从alpha8的每像素1字节一直到宽格式的每像素8字节。
范围很大。
所以我们真正需要做的是知道如何选择正确的格式。
那么我们如何选择正确的格式呢?简单的回答是
不要选择格式。
让格式选择你。
如果您从使用UIGraphics BeginImageContext with options API迁移到iOS,而改为使用UIGraphics ImageRenderer格式,则可以节省大量内存,因为UIGraphics BeginImageContext with options始终是每像素4字节的格式。
它总是SRGB。
所以如果你想要的话,你不能得到宽格式,如果你需要的话,你也不能得到每像素1字节的A8格式。
相反,如果您使用iOS 10中的UIGraphics ImageRenderer API(从iOS 12开始),它将自动为您选择最佳的图形格式。
假设我画了一个圆。
现在,使用旧的API和突出显示的代码是我的绘图代码,我得到的是每像素4字节的格式,只是为了画一个黑色的圆。
/**
使用旧的API
*/
let bounds = CGRect(x: 0, y: 0, width:300, height: 100)
UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0)
// Drawing Code
UIColor.black.setFill()
let path = UIBezierPath(roundedRect: bounds,
path.addClip()
UIRectFill(bounds)
byRoundingCorners: UIRectCorner.allCorners,
cornerRadii: CGSize(width: 20, height: 20))
path.addClip()
UIRectFill(bounds)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
/**
使用新的API
*/
// Circle via UIGraphicsImageRenderer
let bounds = CGRect(x: 0, y: 0, width:300, height: 100)
let renderer = UIGraphicsImageRenderer(size: bounds.size)
let image = renderer.image { context in
// Drawing Code
UIColor.black.setFill()
let path = UIBezierPath(roundedRect: bounds,
path.addClip()
UIRectFill(bounds)
byRoundingCorners: UIRectCorner.allCorners,
cornerRadii: CGSize(width: 20, height: 20))
}
如果我改为使用新的API,我使用的是完全相同的绘图代码。
仅仅使用新的API,我现在就得到了每像素1字节的图像。
这意味着它的内存使用减少了75%。
这是一个巨大的节约和同样的保真度。
另外一个好处,如果我想再次使用这个蒙版,我可以改变UIimageView的tintColor,这用一个·就可以做到,这意味着我不必分配更多的内存。
// Make circle render blue, but stay at 1 byte-per-pixel image
let imageView = UIImageView(image: image)
imageView.tintColor = .blue
因此,我可以不仅把它作为一个黑色的圆圈,还可以作为一个蓝色的圆圈,红色的圆圈,绿色的圆圈,而没有额外的内存成本。
参考资料:
1.iOS图片内存优化
2.WWDC2018 图像最佳实践
3.iOS核心动画高级技术