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

相关文章
相关标签/搜索