Bitmap无损缓存方案解析

栏目: IOS · Android · 发布时间: 6年前

内容简介:Bitmap是Android图片处理这块绕不过的一个主题,在处理Bitmap缓存这方面,一般会分为两部分:内存缓存和磁盘缓存。磁盘缓存这块呢,常用的就是使用Bitmap的没办法,不管是PNG算法还是Webp算法,毕竟还是对数据有所损耗的。想要毫无损耗的储存Bitmap数据,还需另寻方案。

Bitmap是Android图片处理这块绕不过的一个主题,在处理Bitmap缓存这方面,一般会分为两部分:内存缓存和磁盘缓存。

磁盘缓存这块呢,常用的就是使用Bitmap的 compress 函数,根据实际需求压缩为想要的图片文件。对于常规的带有透明度的图片来说,选择无损压缩成PNG或者Webp文件,二次读取展示的Bitmap一般肉眼看不出差别。而对于一些特殊的Bitmap而言,这种存储方式就不再适用。譬如我最近遇到的一种情况,得到的Bitmap数据中,透明部分占了绝大多数。这种Bitmap可以毫无问题地直接展示出来。但存储为PNG或者Webp文件后,二次读取却缺损严重,根本无法还原。

没办法,不管是PNG算法还是Webp算法,毕竟还是对数据有所损耗的。想要毫无损耗的储存Bitmap数据,还需另寻方案。

磁盘缓存

解决方案一

将Bitmap毫无损耗的做磁盘缓存。我想的第一种方案,就是将Bitmap的所有数据存储为文件,对此可以利用他的 copyPixelsToBuffer 函数,对此扩展函数如下所示:

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 函数,也是无法还原位图的。

此时,还原位图的真正方式如下:

// 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图片要打上十几倍甚至几十倍。所以重点强调!!!如果手机磁盘空间吃紧的话,那么不建议使用这种方式。

解决方案二

既然原始数据存储占用空间大,那么原始数据能不能再压缩呢?针对我遇到的这种情况,Bitmap大部分数据为 0-纯透明 ,利用一些压缩算法来压缩,读取的时候再对数据还原是否可行呢?

对此我进行了尝试,使用的是 GZIP 压缩,代码展示如下:

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:

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


以上所述就是小编给大家介绍的《Bitmap无损缓存方案解析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Math Adventures with Python

Math Adventures with Python

Peter Farrell / No Starch Press / 2018-11-13 / GBP 24.99

Learn math by getting creative with code! Use the Python programming language to transform learning high school-level math topics like algebra, geometry, trigonometry, and calculus! In Math Adventu......一起来看看 《Math Adventures with Python》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具