一张图片引发的思考

背景:

前段时间做微信小程序分享,用了某家的SDK,然鹅......他们家SDK只能上传pngjpeg格式的图片,微信不是可以上传Data吗????

???.jpeg

我吭哧吭哧半天用UIImageJPEGRepresentation压缩图片,然后在生成图片,也没把图片传上去。我当时想肯定是图片大小有问题,因为微信限制128KB以内。我查看保存在沙盒里的图片才32KB啊??怎么会上传不上去呢?再查看ImageData大小,噗~~~168KB。好吧,我被打败了。最后还是用微信原生SDK才搞定,直接传一个Data过去,多开心,多easy。

正文

好的,扯了这么多,其实就是想说一下为啥会有今天这篇大水文。在解决问题的过程中,我对iOS加载图片的理解稍微深入了那么一丢丢。现在,就水一下我理解的那么一丢丢东西。

图片经过哪些流程加载到屏幕上

  1. 从磁盘拷贝数据到内核缓冲区
  2. 从内核缓冲区复制数据到用户空间(内存级别拷贝)
  3. 生成UIImage,把UIImage赋值给UIImageView
  4. 如果图像数据为未解码的PNG/JPG,解码为位图数据
  5. 隐式CATransaction捕获到UIImageView图层树的变化
  6. 主线程Runloop提交CATransaction,开始进行图像渲染
    6.1 如果数据没有字节对齐,Core Animation会再拷贝一份数据,进行字节对齐
    6.2 GPU处理位图数据,进行渲染

其中第四点就是导致我32KB变168KB的“罪魁祸首”。为啥这么说呢?先了解一些东西。

PNG

PNG只支持无损压缩,所以它的压缩比是有上限的。它有alpha通道,支持图片透明。此外xcode会对png格式进行特殊的优化处理,而对于其他图片不做处理,所以我们一些小图标经常用PNG

JPEG

JPEG支持有损压缩,不含有alpha通道,它可以通过图片质量换取内存空间。网络图片最好选用JPEG,可以节省流量、提高下载速度。

位图

我们是否可以直接使用图片,使其显示在屏幕上呢?答案显然后不可以。图片经过解压后,变成位图数据。那么位图是什么呢?苹果给出的解释是

A bitmap image (or sampled image) is an array of pixels (or samples)

位图是一个像素数组。至于怎么将像素绘制到屏幕上,可以看这篇文章,就不做过多叙述(人家说的很明白)。

解码

解码其实就是将图片的二进制数据转换成像素数据。这个过程是比较耗时的,不能使用 GPU 硬解码,只能通过 CPU 软解码实现(硬解码是通过解码电路实现,软解码是通过解码算法、CPU 的通用计算等方式实现软件层面的解码,效率不如 GPU 硬解码)。解码后的文件大小计算公式

解压缩后的图片大小 = 图片的像素宽 * 图片的像素高 * 每个像素所占的字节数 (4)

每个像素所占的字节数为什么是4呢?因为我们所使用的位图大部分是32位的RGBA模式,这种模式位图的一个像素所占内存为32位,也就是4个字节的长度 。出处在此
所以,本地保存的32KB的图片,解码就是168KB了。(解压缩后的数据)

恍然大悟.jpg

压缩图片

不过分享某一张图片的时候,我用UIImageJPEGRepresentation方法压缩不到128KB一下???什么图片这么大?后来问一下后台才知道,这张图片是相机拍摄的,尺寸非常大,只能重新设置图片尺寸。献上我的代码


func compressImage(_ image: UIImage, toByte maxLength: Int) -> Data?{
    var compression: CGFloat = 1

    var data = UIImageJPEGRepresentation(image, compression)!
    if data.count <= maxLength {
        return data
    }

    var max: CGFloat = 1
    var min: CGFloat = 0
    
    let newSize = CGSize.init(width: 200, height: 160)
    UIGraphicsBeginImageContext(newSize)
    image.draw(in: CGRect.init(x: 0, y: 0, width: newSize.width, height: newSize.height))
    let newImage = UIGraphicsGetImageFromCurrentImageContext()!
    UIGraphicsEndImageContext()
    data = UIImageJPEGRepresentation(newImage, 1.0)!
    if data.count <= maxLength {
        return data
    }

    for _ in 0..<10 {
        compression = (max + min) / 2
        data = UIImageJPEGRepresentation(newImage, compression)!
        
        if CGFloat(data.count) < CGFloat(maxLength) * 0.9 {
            min = compression
        } else if data.count > maxLength {
            max = compression
        } else {
            break
        }
    }
 
    return data
}

图片加载

通常我们说图片加载会用到两种方法:imageNamedimageWithContentsOfFile,我们简单介绍这两种方法

imageNamed

该方法的特点在于可以缓存已经加载的图片;使用时,先根据文件名在系统缓存中寻找图片,如果找到了就返回;如果没有,就在Bundle内查找到文件名,找到后把这个文件名放到UIImage里返回,并没有进行实际的文件读取和解码。当UIImage第一次显示到屏幕上时,其内部的解码方法才会被调用,同时解码结果会保存到一个全局缓存去。在图片解码后,App 第一次退到后台和收到内存警告时,该图片的缓存才会被清空,其他情况下缓存会一直存在。

imageWithContentsOfFile

该方法仅加载图片,不缓存图像数据,其解码依然要等到第一次显示该图片的时候。

对于这两种方法,我们可以做出如下比较:

  • 本地(Assets)保存的图标加载使用imageNamed
  • 经常使用且文件不大的图片使用imageNamed
  • 对于一些文件较大的图片使用imageWithContentsOfFile,当然最好的办法是用UIGraphicsBeginImageContext方法重新绘制图片

此外,在 WWDC 2018上,苹果为我们建议了一种大家平时使用较少的大图加载方式,它的实际占用内存与理论值最为接近。

func downsample(imageAt imageURL: URL, to pointSize: CGSize, scale: CGFloat) -> UIImage
{
    let sourceOpt = [kCGImageSourceShouldCache : false] as  CFDictionary
    // 其他场景可以用createwithdata (data并未decode,所占内存没那么大),
    let source = CGImageSourceCreateWithURL(imageURL as CFURL, sourceOpt)!
    
    let maxDimension = max(pointSize.width, pointSize.height) * scale
    let downsampleOpt = [kCGImageSourceCreateThumbnailFromImageAlways : true,
                         kCGImageSourceShouldCacheImmediately : true ,
                         kCGImageSourceCreateThumbnailWithTransform : true,
                         kCGImageSourceThumbnailMaxPixelSize : maxDimension] as CFDictionary
    let downsampleImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOpt)!
    return UIImage(cgImage: downsampleImage)
}

参考

iOS图片加载速度极限优化—FastImageCache解析
谈谈 iOS 中图片的解压缩
iOS中的图片使用方式、内存对比和最佳实践

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容

  • 绘制像素到屏幕上 answer-huang22 Mar 2014 分享文章 一个像素是如何绘制到屏幕上去的?有很多...
    阿狸旅途T恤阅读 1,619评论 0 7
  • 卷首语 欢迎来到 objc.io 的第三期! 这一期都是关于视图层的。当然视图层有很多方面,我们需要把它们缩小到几...
    评评分分阅读 1,755评论 0 18
  • 秋冬之际,北方的人们最经常谈论的话题就是穿,因为天气变幻莫测冷暖不定,也许今天你还穿一件夹克,明天就得披上厚厚的大...
    祁小祺阅读 1,253评论 1 3
  • ——————————————————转载自雷纯锋的技术博客 对于大多数 iOS 应用来说,图片往往是最占用手机内存...
    woshishui1243阅读 532评论 0 2
  • 为什么图像在显示到屏幕上之前要进行解码 一般我们使用的图像是JPEG/PNG,这些图像数据不是位图,而是是经过编码...
    zziazm阅读 7,548评论 1 30