一張圖片引發(fā)的思考

背景:

前段時間做微信小程序分享,用了某家的SDK,然鵝......他們家SDK只能上傳pngjpeg格式的圖片,微信不是可以上傳Data嗎????

???.jpeg

我吭哧吭哧半天用UIImageJPEGRepresentation壓縮圖片,然后在生成圖片,也沒把圖片傳上去。我當(dāng)時想肯定是圖片大小有問題,因?yàn)槲⑿畔拗?28KB以內(nèi)。我查看保存在沙盒里的圖片才32KB啊??怎么會上傳不上去呢?再查看ImageData大小,噗~~~168KB。好吧,我被打敗了。最后還是用微信原生SDK才搞定,直接傳一個Data過去,多開心,多easy。

正文

好的,扯了這么多,其實(shí)就是想說一下為啥會有今天這篇大水文。在解決問題的過程中,我對iOS加載圖片的理解稍微深入了那么一丟丟。現(xiàn)在,就水一下我理解的那么一丟丟東西。

圖片經(jīng)過哪些流程加載到屏幕上

  1. 從磁盤拷貝數(shù)據(jù)到內(nèi)核緩沖區(qū)
  2. 從內(nèi)核緩沖區(qū)復(fù)制數(shù)據(jù)到用戶空間(內(nèi)存級別拷貝)
  3. 生成UIImage,把UIImage賦值給UIImageView
  4. 如果圖像數(shù)據(jù)為未解碼的PNG/JPG,解碼為位圖數(shù)據(jù)
  5. 隱式CATransaction捕獲到UIImageView圖層樹的變化
  6. 主線程Runloop提交CATransaction,開始進(jìn)行圖像渲染
    6.1 如果數(shù)據(jù)沒有字節(jié)對齊,Core Animation會再拷貝一份數(shù)據(jù),進(jìn)行字節(jié)對齊
    6.2 GPU處理位圖數(shù)據(jù),進(jìn)行渲染

其中第四點(diǎn)就是導(dǎo)致我32KB變168KB的“罪魁禍?zhǔn)住?。為啥這么說呢?先了解一些東西。

PNG

PNG只支持無損壓縮,所以它的壓縮比是有上限的。它有alpha通道,支持圖片透明。此外xcode會對png格式進(jìn)行特殊的優(yōu)化處理,而對于其他圖片不做處理,所以我們一些小圖標(biāo)經(jīng)常用PNG

JPEG

JPEG支持有損壓縮,不含有alpha通道,它可以通過圖片質(zhì)量換取內(nèi)存空間。網(wǎng)絡(luò)圖片最好選用JPEG,可以節(jié)省流量、提高下載速度。

位圖

我們是否可以直接使用圖片,使其顯示在屏幕上呢?答案顯然后不可以。圖片經(jīng)過解壓后,變成位圖數(shù)據(jù)。那么位圖是什么呢?蘋果給出的解釋是

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

位圖是一個像素?cái)?shù)組。至于怎么將像素繪制到屏幕上,可以看這篇文章,就不做過多敘述(人家說的很明白)。

解碼

解碼其實(shí)就是將圖片的二進(jìn)制數(shù)據(jù)轉(zhuǎn)換成像素?cái)?shù)據(jù)。這個過程是比較耗時的,不能使用 GPU 硬解碼,只能通過 CPU 軟解碼實(shí)現(xiàn)(硬解碼是通過解碼電路實(shí)現(xiàn),軟解碼是通過解碼算法、CPU 的通用計(jì)算等方式實(shí)現(xiàn)軟件層面的解碼,效率不如 GPU 硬解碼)。解碼后的文件大小計(jì)算公式

解壓縮后的圖片大小 = 圖片的像素寬 * 圖片的像素高 * 每個像素所占的字節(jié)數(shù) (4)

每個像素所占的字節(jié)數(shù)為什么是4呢?因?yàn)槲覀兯褂玫奈粓D大部分是32位的RGBA模式,這種模式位圖的一個像素所占內(nèi)存為32位,也就是4個字節(jié)的長度 。出處在此
所以,本地保存的32KB的圖片,解碼就是168KB了。(解壓縮后的數(shù)據(jù))

恍然大悟.jpg

壓縮圖片

不過分享某一張圖片的時候,我用UIImageJPEGRepresentation方法壓縮不到128KB一下???什么圖片這么大?后來問一下后臺才知道,這張圖片是相機(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
}

圖片加載

通常我們說圖片加載會用到兩種方法:imageNamed、imageWithContentsOfFile,我們簡單介紹這兩種方法

imageNamed

該方法的特點(diǎn)在于可以緩存已經(jīng)加載的圖片;使用時,先根據(jù)文件名在系統(tǒng)緩存中尋找圖片,如果找到了就返回;如果沒有,就在Bundle內(nèi)查找到文件名,找到后把這個文件名放到UIImage里返回,并沒有進(jìn)行實(shí)際的文件讀取和解碼。當(dāng)UIImage第一次顯示到屏幕上時,其內(nèi)部的解碼方法才會被調(diào)用,同時解碼結(jié)果會保存到一個全局緩存去。在圖片解碼后,App 第一次退到后臺和收到內(nèi)存警告時,該圖片的緩存才會被清空,其他情況下緩存會一直存在。

imageWithContentsOfFile

該方法僅加載圖片,不緩存圖像數(shù)據(jù),其解碼依然要等到第一次顯示該圖片的時候。

對于這兩種方法,我們可以做出如下比較:

  • 本地(Assets)保存的圖標(biāo)加載使用imageNamed
  • 經(jīng)常使用且文件不大的圖片使用imageNamed
  • 對于一些文件較大的圖片使用imageWithContentsOfFile,當(dāng)然最好的辦法是用UIGraphicsBeginImageContext方法重新繪制圖片

此外,在 WWDC 2018上,蘋果為我們建議了一種大家平時使用較少的大圖加載方式,它的實(shí)際占用內(nèi)存與理論值最為接近。

func downsample(imageAt imageURL: URL, to pointSize: CGSize, scale: CGFloat) -> UIImage
{
    let sourceOpt = [kCGImageSourceShouldCache : false] as  CFDictionary
    // 其他場景可以用createwithdata (data并未decode,所占內(nè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)存對比和最佳實(shí)踐

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 繪制像素到屏幕上 answer-huang22 Mar 2014 分享文章 一個像素是如何繪制到屏幕上去的?有很多...
    阿貍旅途T恤閱讀 1,779評論 0 7
  • 卷首語 歡迎來到 objc.io 的第三期! 這一期都是關(guān)于視圖層的。當(dāng)然視圖層有很多方面,我們需要把它們縮小到幾...
    評評分分閱讀 1,944評論 0 18
  • 秋冬之際,北方的人們最經(jīng)常談?wù)摰脑掝}就是穿,因?yàn)樘鞖庾兓媚獪y冷暖不定,也許今天你還穿一件夾克,明天就得披上厚厚的大...
    祁小祺閱讀 1,372評論 1 3
  • ——————————————————轉(zhuǎn)載自雷純鋒的技術(shù)博客 對于大多數(shù) iOS 應(yīng)用來說,圖片往往是最占用手機(jī)內(nèi)存...
    woshishui1243閱讀 588評論 0 2
  • 為什么圖像在顯示到屏幕上之前要進(jìn)行解碼 一般我們使用的圖像是JPEG/PNG,這些圖像數(shù)據(jù)不是位圖,而是是經(jīng)過編碼...
    zziazm閱讀 8,227評論 1 30

友情鏈接更多精彩內(nèi)容