内容简介: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无损缓存方案解析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- SOFAMosn 无损重启/升级
- Zstandard 1.4.4 发布,无损数据压缩算法
- Zstd 1.4.5 发布,Facebook 开源的无损压缩算法
- 伯克利深度无监督学习更新 | 第二讲(上):无损压缩
- zstd v1.4.7 发布,Facebook 开源的无损压缩算法
- 精度无损,体积压缩70%以上,百度PaddleSlim为你的模型瘦身
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。