图片的数据:资源数据(地址)、原始数据(Data)、显示数据(解码后的数据)
- 解压图片 - PNG或者JPEG压缩之后的图片文件会比同质量的位图小得多。但是在图片绘制到屏幕上之前,必须把它扩展成完整的未解压的尺寸(通常等同于图片宽 x 长 x 4个字节)。为了节省内存,iOS通常直到真正绘制的时候才去解码图片(14章“图片IO”会更详细讨论)。根据你加载图片的方式,第一次对图层内容赋值的时候(直接或者间接使用UIImageView)或者把它绘制到Core Graphics中,都需要对它解压,这样的话,对于一个较大的图片,都会占用一定的时间。
https://blog.csdn.net/jasonjwl/article/details/52337274
图片的解码
当你用 UIImage 或 CGImageSource 的那几个方法创建图片时,图片数据并不会立刻解码。图片设置到 UIImageView 或者 CALayer.contents 中去,并且 CALayer 被提交到 GPU 前,CGImage 中的数据才会得到解码。这一步是发生在主线程的,并且不可避免。如果想要绕开这个机制,常见的做法是在后台线程先把图片绘制到 CGBitmapContext 中,然后从 Bitmap 直接创建图片。目前常见的网络图片库都自带这个功能。
https://www.cnblogs.com/feng9exe/p/8856857.html
图片的解码
Once an image file has been loaded, it must then be decompressed. This decompression can be a computationally complex task and take considerable time. The decompressed image will also use substantially more memory than the original.
图片被加载后需要解码,图片的解码是一个复杂耗时的过程,并且需要占用比原始图片还多的内存资源。
为了节省内存,iOS 系统会延迟解码过程, 在图片被设置到 layer 的 contents 属性或者设置成 UIImageView 的 image 属性后才会执行解码过程,但是这两个操作都是在主线程进行,还是会带来性能问题。
如果想要提前解码,可以使用 ImageIO 或者提前将图片绘制到 CGContext 中,这部分实践可以参考 iOS Core Animation: Advanced Techniques
这里多提一点,常用的 UIImage 加载方法有 imageNamed和 imageWithContentsOfFile。其中 imageNamed加载图片后会马上解码,并且系统会将解码后的图片缓存起来,但是这个缓存策略是不公开的,我们无法知道图片什么时候会被释放。因此在一些性能敏感的页面,我们还可以用 static 变量 hold 住 imageNamed加载到的图片避免被释放掉,以空间换时间的方式来提高性能。
https://www.cnblogs.com/feng9exe/p/10321303.html
UIImage 缓存是怎么回事?
通过 imageNamed 创建 UIImage 时,系统实际上只是在 Bundle 内查找到文件名,然后把这个文件名放到 UIImage 里返回,并没有进行实际的文件读取和解码。当 UIImage 第一次显示到屏幕上时,其内部的解码方法才会被调用,同时解码结果会保存到一个全局缓存去。据我观察,在图片解码后,App 第一次退到后台和收到内存警告时,该图片的缓存才会被清空,其他情况下缓存会一直存在。
我要是用 imageWithData 能不能避免缓存呢?
不能。通过数据创建 UIImage 时,UIImage 底层是调用 ImageIO 的 CGImageSourceCreateWithData() 方法。该方法有个参数叫 ShouldCache,在 64 位的设备上,这个参数是默认开启的。这个图片也是同样在第一次显示到屏幕时才会被解码,随后解码数据被缓存到 CGImage 内部。与 imageNamed 创建的图片不同,如果这个图片被释放掉,其内部的解码数据也会被立刻释放。
怎么能避免缓存呢?
1. 手动调用 CGImageSourceCreateWithData() 来创建图片,并把 ShouldCache 和 ShouldCacheImmediately 关掉。这么做会导致每次图片显示到屏幕时,解码方法都会被调用,造成很大的 CPU 占用。
2. 把图片用 CGContextDrawImage() 绘制到画布上,然后把画布的数据取出来当作图片。这也是常见的网络图片库的做法。
我能直接取到图片解码后的数据,而不是通过画布取到吗?
1.CGImageSourceCreateWithData(data) 创建 ImageSource。
2.CGImageSourceCreateImageAtIndex(source) 创建一个未解码的 CGImage。
3.CGImageGetDataProvider(image) 获取这个图片的数据源。
4.CGDataProviderCopyData(provider) 从数据源获取直接解码的数据。
ImageIO 解码发生在最后一步,这样获得的数据是没有经过颜色类型转换的原生数据(比如灰度图像)。
https://www.cnblogs.com/feng9exe/p/8857506.html
UIImage 缓存是怎么回事?
通过 imageNamed 创建 UIImage 时,系统实际上只是在 Bundle 内查找到文件名,然后把这个文件名放到 UIImage 里返回,并没有进行实际的文件读取和解码。当 UIImage 第一次显示到屏幕上时,其内部的解码方法才会被调用,同时解码结果会保存到一个全局缓存去。据我观察,在图片解码后,App 第一次退到后台和收到内存警告时,该图片的缓存才会被清空,其他情况下缓存会一直存在。
我要是用 imageWithData 能不能避免缓存呢?
不能。通过数据创建 UIImage 时,UIImage 底层是调用 ImageIO 的 CGImageSourceCreateWithData() 方法。该方法有个参数叫 ShouldCache,在 64 位的设备上,这个参数是默认开启的。这个图片也是同样在第一次显示到屏幕时才会被解码,随后解码数据被缓存到 CGImage 内部。与 imageNamed 创建的图片不同,如果这个图片被释放掉,其内部的解码数据也会被立刻释放。
怎么能避免缓存呢?
1. 手动调用 CGImageSourceCreateWithData() 来创建图片,并把 ShouldCache 和 ShouldCacheImmediately 关掉。这么做会导致每次图片显示到屏幕时,解码方法都会被调用,造成很大的 CPU 占用。
2. 把图片用 CGContextDrawImage() 绘制到画布上,然后把画布的数据取出来当作图片。这也是常见的网络图片库的做法。
https://blog.ibireme.com/2015/11/02/ios_image_tips/
Image的初始化方法
imageWithData从内部函数的调用来看,通过CGImageSourceRef访问图像数据,创建CGImageRef。
imageWithContentsOfFile内部调用如下
文件通过mmap到内存然后通过CGImageSourceRef访问图像数据,创建CGImageRef。
imageNamed先从Bundle里找到资源路径,然后同样也是将文件mmap到内存,再通过CGImageSourceRef访问图像数据,创建CGImageRef。
Image的缓存
通过调用不同的UIImage初始化方法然后创建UIImageVIew展示到屏幕上,来看看不同方法是否有缓存的行为。
imageNamed在第二次展示相同image的时候没有调用imageIO的任何方法。
imageWithData和imageWithContentsOfFile在第二次在展示相同image的时候均调用了imageIO的解码方法。
而imageWithData和imageWithContentsOfFile初始化方法创建的UIImage只要不被释放,再次渲染不会调用imageIO解码方法。
结论为UIImage有两种缓存,一种是UIImage类的缓存,这种缓存保证imageNamed初始化的UIImage只会被解码一次。另一种是UIImage对象的缓存,这种缓存保证只要UIImage没有被释放,就不会再解码。
CGImageSourceCreateImageAtIndex方法的kCGImageSourceShouldCache选项指的是第二种缓存,而如果设置为false,我测试出来image再次渲染的时候仍没有进行解码,这有些奇怪。如果有同学详细知道怎么回事,还请赐教。
http://www.cnblogs.com/fengmin/p/5702240.html
Discussion This method looks in the system caches for an image object with the specified name and returns the variant of that image that is best suited for the main screen. If a matching image object is not already in the cache, this method locates and loads the image data from disk or from an available asset catalog, and then returns the resulting object. The system may purge cached image data at any time to free up memory. Purging occurs only for images that are in the cache but are not currently being used. In iOS 9 and later, this method is thread safe. |
iOS 图片解码(decode)笔记
为什么图像在显示到屏幕上之前要进行解码
一般我们使用的图像是JPEG/PNG,这些图像数据不是位图,而是是经过编码压缩后的数据,需要线将它解码转成位图数据,然后才能把位图渲染到屏幕上。
当你用 UIImage 或 CGImageSource 的那几个方法创建图片时,图片数据并不会立刻解码。图片设置到 UIImageView 或者 CALayer.contents 中去,并且 CALayer 被提交到 GPU 前,CGImage 中的数据才会得到解码。这一步是发生在主线程的,并且不可避免。
图片加载的工作流
概括来说,从磁盘中加载一张图片,并将它显示到屏幕上,中间的主要工作流如下:
- 假设我们使用 +imageWithContentsOfFile: 方法从磁盘中加载一张图片,这个时候的图片并没有解压缩;
- 然后将生成的 UIImage 赋值给 UIImageView ;
- 接着一个隐式的 CATransaction 捕获到了 UIImageView 图层树的变化;
- 在主线程的下一个 run loop 到来时,Core Animation 提交了这个隐式的 transaction ,这个过程可能会对图片进行 copy 操作,而受图片是否字节对齐等因素的影响,这个 copy 操作可能会涉及以下部分或全部步骤:
- 分配内存缓冲区用于管理文件 IO 和解压缩操作;
- 将文件数据从磁盘读到内存中;
- 将压缩的图片数据解码成未压缩的位图形式,这是一个非常耗时的 CPU 操作;
- 最后 Core Animation 使用未压缩的位图数据渲染 UIImageView 的图层。
在上面的步骤中,我们提到了图片的解压缩是一个非常耗时的 CPU 操作,并且它默认是在主线程中执行的。那么当需要加载的图片比较多时,就会对我们应用的响应性造成严重的影响,尤其是在快速滑动的列表上,这个问题会表现得更加突出。
图像的解码
解码操作是比较耗时的,并且没有GPU硬解码,只能通过CPU,iOS默认会在主线程对图像进行解码。解码过程是一个相当复杂的任务,需要消耗非常长的时间。60FPS ≈ 0.01666s per frame = 16.7ms per frame,这意味着在主线程超过16.7ms的任务都会引起掉帧。很多库都解决了图像解码的问题,不过由于解码后的图像太大,一般不会缓存到磁盘,SDWebImage的做法是把解码操作从主线程移到子线程,让耗时的解码操作不占用主线程的时间。
对于PNG图片来说,因为文件可能更大,所以加载会比JPEG更长,但是解码会相对较快,而且Xcode会把PNG图片进行解码优化之后引入工程。JPEG图片更小,加载更快,但是解压的步骤要消耗更长的时间,因为JPEG解压算法比基于zip的PNG算法更加复杂。
当加载图片的时候,iOS通常会延迟解压图片的时间,直到加载到内存之后。因为需要在绘制之前进行解压,这就会在准备绘制图片的时候影响性能。
iOS通常会延时解压图片,等到图片在屏幕上显示的时候解压图片。解压图片是非常耗时的操作。
https://www.jianshu.com/p/4da6981a746c
19. 图片格式
当你在 iOS 或者 OS X 上处理图片时,他们大多数为 JPEG 和 PNG。让我们更进一步观察。
1) JPEG
每个人都知道 JPEG。它是相机的产物。它代表着照片如何存储在电脑上。甚至你妈妈都听说过 JPEG。
一个很好的理由,很多人都认为 JPEG 文件仅是另一种像素数据的格式,就像我们刚刚谈到的 RGB 像素布局那样。这样理解离真相真是差十万八千里了。
将 JPEG 数据转换成像素数据是一个非常复杂的过程,你通过一个周末的计划都不能完成,甚至是一个非常漫长的周末(原文的意思好像就是为了表达这个过程非常复杂,不过老外的比喻总让人拎不清)。对于每一个二维颜色,JPEG 使用一种基于离散余弦变换(简称 DCT 变换)的算法,将空间信息转变到频域.这个信息然后被量子化,排好序,并且用一种哈夫曼编码的变种来压缩。很多时候,首先数据会被从 RGB 转换到二维 YCbCr,当解码 JPEG 的时候,这一切都将变得可逆。
这也是为什么当你通过 JPEG 文件创建一个 UIImage 并且绘制到屏幕上时,将会有一个延时,因为 CPU 这时候忙于解压这个 JPEG。如果你需要为每一个 tableviewcell 解压 JPEG,那么你的滚动当然不会平滑(原来 tableviewcell 里面最要不要用 JPEG 的图片)。
那究竟为什么我们还要用 JPEG 呢?答案就是 JPEG 可以非常非常好的压缩图片。
2) PNG
PNG读作”ping”。和 JPEG 相反,它的压缩对格式是无损的。当你将一张图片保存为 PNG,并且打开它(或解压),所有的像素数据会和最初一模一样,因为这个限制,PNG 不能像 JPEG 一样压缩图片,但是对于像程序中的原图(如buttons,icons),它工作的非常好。更重要的是,解码 PNG 数据比解码 JPEG 简单的多。
在现实世界中,事情从来没有那么简单,目前存在了大量不同的 PNG 格式。可以通过*查看详情。但是简言之,PNG 支持压缩带或不带 alpha 通道的颜色像素(RGB),这也是为什么它在程序原图中表现良好的另一个原因。
https://www.cnblogs.com/feng9exe/p/9436833.html