背景:
前段時(shí)間做微信小程序分享肤粱,用了某家的SDK厨相,然鵝......他們家SDK只能上傳png
领铐、jpeg
格式的圖片,微信不是可以上傳Data
嗎绪撵?音诈??褥傍?
我吭哧吭哧半天用
UIImageJPEGRepresentation
壓縮圖片喇聊,然后在生成圖片誓篱,也沒(méi)把圖片傳上去。我當(dāng)時(shí)想肯定是圖片大小有問(wèn)題锦募,因?yàn)槲⑿畔拗?28KB以內(nèi)邻遏。我查看保存在沙盒里的圖片才32KB啊赎线?糊饱?怎么會(huì)上傳不上去呢?再查看Image
的Data
大小矫废,噗~~~168KB砰蠢。好吧台舱,我被打敗了。最后還是用微信原生SDK才搞定竞惋,直接傳一個(gè)Data
過(guò)去拆宛,多開(kāi)心,多easy股耽。
正文
好的钳幅,扯了這么多,其實(shí)就是想說(shuō)一下為啥會(huì)有今天這篇大水文诬乞。在解決問(wèn)題的過(guò)程中钠导,我對(duì)iOS加載圖片的理解稍微深入了那么一丟丟”菜現(xiàn)在,就水一下我理解的那么一丟丟東西换衬。
圖片經(jīng)過(guò)哪些流程加載到屏幕上
- 從磁盤拷貝數(shù)據(jù)到內(nèi)核緩沖區(qū)
- 從內(nèi)核緩沖區(qū)復(fù)制數(shù)據(jù)到用戶空間(內(nèi)存級(jí)別拷貝)
- 生成
UIImage
证芭,把UIImage
賦值給UIImageView
- 如果圖像數(shù)據(jù)為未解碼的PNG/JPG,解碼為位圖數(shù)據(jù)
- 隱式
CATransaction
捕獲到UIImageView
圖層樹(shù)的變化 - 主線程
Runloop
提交CATransaction
叫潦,開(kāi)始進(jìn)行圖像渲染
6.1 如果數(shù)據(jù)沒(méi)有字節(jié)對(duì)齊矗蕊,Core Animation會(huì)再拷貝一份數(shù)據(jù),進(jìn)行字節(jié)對(duì)齊
6.2 GPU處理位圖數(shù)據(jù)傻咖,進(jìn)行渲染
其中第四點(diǎn)就是導(dǎo)致我32KB變168KB的“罪魁禍?zhǔn)住鼻洳佟樯哆@么說(shuō)呢?先了解一些東西扇雕。
PNG
PNG
只支持無(wú)損壓縮窥摄,所以它的壓縮比是有上限的崭放。它有alpha
通道慷荔,支持圖片透明。此外xcode會(huì)對(duì)png格式進(jìn)行特殊的優(yōu)化處理荷逞,而對(duì)于其他圖片不做處理迹缀,所以我們一些小圖標(biāo)經(jīng)常用PNG
祝懂。
JPEG
JPEG
支持有損壓縮拘鞋,不含有alpha
通道,它可以通過(guò)圖片質(zhì)量換取內(nèi)存空間盆色。網(wǎng)絡(luò)圖片最好選用JPEG
隔躲,可以節(jié)省流量、提高下載速度仅父。
位圖
我們是否可以直接使用圖片,使其顯示在屏幕上呢耗溜?答案顯然后不可以省容。圖片經(jīng)過(guò)解壓后,變成位圖數(shù)據(jù)。那么位圖是什么呢?蘋果給出的解釋是
A bitmap image (or sampled image) is an array of pixels (or samples)
位圖是一個(gè)像素?cái)?shù)組寞酿。至于怎么將像素繪制到屏幕上脱柱,可以看這篇文章榨为,就不做過(guò)多敘述(人家說(shuō)的很明白)。
解碼
解碼其實(shí)就是將圖片的二進(jìn)制數(shù)據(jù)轉(zhuǎn)換成像素?cái)?shù)據(jù)日川。這個(gè)過(guò)程是比較耗時(shí)的矩乐,不能使用 GPU 硬解碼,只能通過(guò) CPU 軟解碼實(shí)現(xiàn)(硬解碼是通過(guò)解碼電路實(shí)現(xiàn)分歇,軟解碼是通過(guò)解碼算法职抡、CPU 的通用計(jì)算等方式實(shí)現(xiàn)軟件層面的解碼误甚,效率不如 GPU 硬解碼)。解碼后的文件大小計(jì)算公式
解壓縮后的圖片大小 = 圖片的像素寬 * 圖片的像素高 * 每個(gè)像素所占的字節(jié)數(shù) (4)
每個(gè)像素所占的字節(jié)數(shù)為什么是4呢蹄胰?因?yàn)槲覀兯褂玫奈粓D大部分是32位的RGBA模式裕寨,這種模式位圖的一個(gè)像素所占內(nèi)存為32位,也就是4個(gè)字節(jié)的長(zhǎng)度 捻艳。出處在此
所以庆猫,本地保存的32KB的圖片月培,解碼就是168KB了。(解壓縮后的數(shù)據(jù))
壓縮圖片
不過(guò)分享某一張圖片的時(shí)候纪蜒,我用UIImageJPEGRepresentation
方法壓縮不到128KB一下此叠??猬错?什么圖片這么大茸歧?后來(lái)問(wèn)一下后臺(tái)才知道,這張圖片是相機(jī)拍攝的析校,尺寸非常大铜涉,只能重新設(shè)置圖片尺寸芙代。獻(xiàn)上我的代碼
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
}
圖片加載
通常我們說(shuō)圖片加載會(huì)用到兩種方法:imageNamed
纹烹、imageWithContentsOfFile
,我們簡(jiǎn)單介紹這兩種方法
imageNamed
該方法的特點(diǎn)在于可以緩存已經(jīng)加載的圖片裹驰;使用時(shí)幻林,先根據(jù)文件名在系統(tǒng)緩存中尋找圖片,如果找到了就返回躏敢;如果沒(méi)有整葡,就在Bundle
內(nèi)查找到文件名,找到后把這個(gè)文件名放到UIImage
里返回啼器,并沒(méi)有進(jìn)行實(shí)際的文件讀取和解碼端壳。當(dāng)UIImage
第一次顯示到屏幕上時(shí)鼠次,其內(nèi)部的解碼方法才會(huì)被調(diào)用腥寇,同時(shí)解碼結(jié)果會(huì)保存到一個(gè)全局緩存去觅捆。在圖片解碼后,App 第一次退到后臺(tái)和收到內(nèi)存警告時(shí)掂摔,該圖片的緩存才會(huì)被清空赢赊,其他情況下緩存會(huì)一直存在释移。
imageWithContentsOfFile
該方法僅加載圖片,不緩存圖像數(shù)據(jù)涩蜘,其解碼依然要等到第一次顯示該圖片的時(shí)候同诫。
對(duì)于這兩種方法樟澜,我們可以做出如下比較:
- 本地(Assets)保存的圖標(biāo)加載使用
imageNamed
- 經(jīng)常使用且文件不大的圖片使用
imageNamed
- 對(duì)于一些文件較大的圖片使用
imageWithContentsOfFile
,當(dāng)然最好的辦法是用UIGraphicsBeginImageContext
方法重新繪制圖片
此外贩猎,在 WWDC 2018上吭服,蘋果為我們建議了一種大家平時(shí)使用較少的大圖加載方式,它的實(shí)際占用內(nèi)存與理論值最為接近蝌戒。
func downsample(imageAt imageURL: URL, to pointSize: CGSize, scale: CGFloat) -> UIImage
{
let sourceOpt = [kCGImageSourceShouldCache : false] as CFDictionary
// 其他場(chǎng)景可以用createwithdata (data并未decode,所占內(nèi)存沒(méi)那么大),
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圖片加載速度極限優(yōu)化—FastImageCache解析
談?wù)?iOS 中圖片的解壓縮
iOS中的圖片使用方式北苟、內(nèi)存對(duì)比和最佳實(shí)踐