Bitmap存儲原始數據方案

原文地址
git

原創文章,未經做者容許不得轉載github

眉共春山爭秀
可憐長皺
莫將清淚溼花枝,恐花也、如人瘦算法

概述

Bitmap是Android圖片處理這塊繞不過的一個主題,在處理Bitmap緩存這方面,通常會分爲兩部分:內存緩存和磁盤緩存。
磁盤緩存這塊呢,經常使用的就是使用Bitmap的compress函數,根據實際需求壓縮爲想要的圖片文件。對於常規的帶有透明度的圖片來講,選擇無損壓縮成PNG或者Webp文件,二次讀取展現的Bitmap通常肉眼看不出差異。而對於一些特殊的Bitmap而言,這種存儲方式就再也不適用。譬如我最近遇到的一種狀況,獲得的Bitmap數據中,透明部分佔了絕大多數。這種Bitmap能夠毫無問題地直接展現出來。但存儲爲PNG或者Webp文件後,二次讀取卻缺損嚴重,根本沒法還原。緩存

磁盤緩存

解決方案一

將Bitmap毫無損耗的作磁盤緩存。我想的第一種方案,就是將Bitmap的全部數據存儲爲文件,對此能夠利用他的copyPixelsToBuffer函數,對此擴展函數以下所示:bash

fun Bitmap.saveUndamaged(dir:String){
	// 文件後綴能夠是任意格式,只要是文件便可
	val f = File(dir)
	val byteBuffer = ByteBuffer.allocate(byteCount)
   copyPixelsToBuffer(byteBuffer)
   val byteArray = byteBuffer.array()
   file.writeBytes(byteArray)
}

複製代碼

那麼讀取的時候,這個時候咱們有了文件的ByteArray流,是否是隻須要利用BitmapFactory.decodeByteArray這個函數就能夠拿到原始位圖了呢?
答案是否認,存儲的ByteArray磁盤文件只有原始位圖的RGBA信息,缺失了位圖的寬高,因此即便使用了BitmapFactory.decodeByteArray函數,也是沒法還原位圖的。
此時,還原位圖的真正方式以下:app

// bitmap的寬高信息必須從外界傳入
fun getUndamagedBitmap(dir:String, size:Size):Bitmap{
	val b = Bitmap.createBitmap(size.width, size.height, Bitmap.Config.ARGB_8888)
b.copyPixelsFromBuffer(ByteBuffer.wrap(File(dir).readBytes()))
	return b
}
複製代碼

獲得了位圖的RGBA數據後,須要使用如上方式才能還原位圖。首先咱們建立一個等寬高的空白位圖,而後將RGBA數據填充進去。這樣就能遠遠本版的還原位圖。可是務必注意這裏的寬高必定要和原始位圖相同,不然展現的位圖將是錯亂不堪函數

目前爲止,這種方案實施起來還算可行,文件的讀取速度還算能夠接受。好一點的手機上,基本都在20ms左右徘徊*【這個時間視位圖數據量而定】*。但這種方式一樣有一個缺點,就是文件存儲空間過大。相同的Bitmap,儲存原始數據的文件比PNG圖片要打上十幾倍甚至幾十倍。因此重點強調!!!若是手機磁盤空間吃緊的話,那麼不建議使用這種方式。ui

解決方案二

既然原始數據存儲佔用空間大,那麼原始數據能不能再壓縮呢?針對我遇到的這種狀況,Bitmap大部分數據爲0-純透明,利用一些壓縮算法來壓縮,讀取的時候再對數據還原是否可行呢?
對此我進行了嘗試,使用的是GZIP壓縮,代碼展現以下:this

fun Bitmap.saveUndamaged(dir: String) {
    val byteBuffer = ByteBuffer.allocate(byteCount)
    copyPixelsToBuffer(byteBuffer)
    val byteArray = byteBuffer.array()
    // 這裏的後綴一樣能夠是任意格式,存儲不針對文件格式,只須要Byte數據
    val fileOut = FileOutputStream(File(dir))
    val zipOutputStream = GZIPOutputStream(fileOut)
    zipOutputStream.write(byteArray)

    zipOutputStream.close()
    fileOut.close()
}
複製代碼

那麼,同理再讀取時,須要對文件作解壓縮處理,而後生成Bitmap:spa

val file = File(dir)
val zip = GZIPInputStream(file)
val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(zip.readBytes()))        
zip.close()
file.close()
複製代碼

對數據作壓縮處理後,空間佔用會小不少。但相對圖片文件來講,依然仍是比較大的。一樣的,用空間換時間,空間佔用小了。讀取時間必然就是長了,這種解壓縮讀取的話,時間相比上要比讀取原始數據文件多了一兩倍。孰輕孰重,還需根據需求自行定奪。

內存緩存

同理。既然能夠存儲爲文件,那麼必然能夠做內存緩存。只要稍微將上述方法,更換部分代碼便可。
緩存到內存中:

class BitmapLru(val size: Size, val data: ByteArray)

fun Bitmap.lruCache(): BitmapLru {
    val array = byteArray()

    val out = ByteArrayOutputStream()
    val zip = GZIPOutputStream(out)
    zip.write(array)
    zip.close()
    // 在這裏zip要及時關閉,不然讀取壓縮數據時會出現異常
    val data = out.toByteArray()
    out.close()
    return BitmapLru(Size(width, height), data)
}
複製代碼

從內存中解壓生成原始位圖:

fun BitmapLru?.lruToBitmap(): Bitmap? {
    this?.apply {
        val s = System.currentTimeMillis()
        val inb = ByteArrayInputStream(data)
        val zip = GZIPInputStream(inb)

        val bitmap = Bitmap.createBitmap(size.width, size.height, Bitmap.Config.ARGB_8888)
        bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(zip.readBytes()))

        zip.close()
        inb.close()
        Log.d("lruToBitmap", "lru to bitmap cost :${System.currentTimeMillis() - s} ")
        return bitmap
    }
    return null
}
複製代碼

在這裏須要注意的是,在壓縮數據時,必定要及時關閉GZIPOutputStream。不然在解壓縮時,會拋出EOFException: Unexpected end of ZLIB input stream異常。

結語

兩種無損存儲方案,就是空間和時間的選擇問題。手機空間支持,就存儲原始文件;空間吃緊,可是時間又容許,就選擇壓縮原始數據方案。
好了~~~以上就是此次的分享,若是你們對音視頻感興趣的話,歡迎關注個人Github項目MediaLearn

相關文章
相關標籤/搜索