Bitmap

作者: QiYiFridge | 来源:发表于2017-09-07 10:23 被阅读376次
    1. 基本概念(是什么,应用场景)以及BitMap的编码原理(做引导)

    2. BitMap类在Android类中的基本实现(基本结构)

    3. recycle

    Bitmap

    位图的像素都分配有特定的位置和颜色值。每个像素的颜色信息由RGB组合或者灰度值表示。
    根据位深度,可将位图分为1、4、8、16、24及32位图像等。每个像素使用的信息位数越多,可用的颜色就越多,颜色表现就越逼真,相应的数据量越大。例如,位深度为1的像素位图只有两个可能的值(黑色和白色),所以又称为二值位图。位深度为8的图像有28(即256)个可能的值。位深度为8的灰度模式图像有256个可能的灰色值。
    Config解析:

    Bitmap.Config.ALPHA_8:颜色信息只由透明度组成,占8位。
    Bitmap.Config.ARGB_4444:颜色信息由透明度与R(Red),G(Green),B(Blue)四部分组成,每个部分都占4位,总共占16位。已经被废弃,因为显示质量不好。
    Bitmap.Config.ARGB_8888:颜色信息由透明度与R(Red),G(Green),B(Blue)四部分组成,每个部分都占8位,总共占32位。是Bitmap默认的颜色配置信息,也是最占空间的一种配置。
    Bitmap.Config.RGB_565:颜色信息由R(Red),G(Green),B(Blue)三部分组成,R占5位,G占6位,B占5位,总共占16位。如果不需要 alpha 通道,特别是资源本身为 jpg 格式的情况下,用这个格式比较理想

    这个在skia库中可以看到


    image.png

    当需要做性能优化或者防止OOM(Out Of Memory),我们通常会使用Bitmap.Config.RGB_565这个配置。

    大多数情况下其实我们并不需要argb中的alpha通道,在背景已知的情况下,rgb和argb是可以互相转换的。而多数情况下我们都是白色背景。(直接使用target的 rgb就可以了)
    Source => Target = (BGColor + Source) =
    Target.R = ((1 - Source.A) * BGColor.R) + (Source.A * Source.R)
    Target.G = ((1 - Source.A) * BGColor.G) + (Source.A * Source.G)
    Target.B = ((1 - Source.A) * BGColor.B) + (Source.A * Source.B)

    小问题:RGB_565这个数字是怎么定的?为什么不取555?
    文件读取是按byte来的。

    和矢量图的比较
    1.文件小,图像中保存的是线条和图块的信息,所以矢量图形文件与分辨率和图像大小无关,只与图像的复杂程度有关,图像文件所占的存储空间较小。
    2矢量图无限放大不模糊,大部分位图都是由矢量导出来的
    3.矢量图最大的缺点是难以表现色彩层次丰富的逼真图像效果。

    移动端开发中位图的应用很少,因为很少遇到这种需要无限缩放的场景。对于少数有缩放需要的场景,Bitmap类提供了一种特殊而且有趣的方式。这就是(九点图)

    Bitmap的相关类很多,但是只要按照一个基本思路梳理 ,就会很清晰

    1. 文件和Bitmap的相互转换
      1.1 文件转换为Bitmap

    Bitmap是一个final类,因此不能被继承。Bitmap只有一个构造方法,且该构造方法是没有任何访问权限修饰符修饰,也就是说该构造方法是friendly,但是谷歌称Bitmap的构造方法是private(私有的),感觉有点不严谨。不管怎样,一般情况下,我们不能通过构造方法直接新建一个Bitmap对象。
    从文件创建Bitmap类就离不开BitmapFactory

    BitmapFactory类提供了四类方法:decodeFile、decodeRe-source、decodeStream和decodeByteArray,分别用于支持从文件系统、资源、输入流以及字节数组中加载出一个Bitmap对象,其中decodeFile和decodeResource又间接调用了decode-Stream方法,这四类方法最终是在Android的底层实现的,对应着BitmapFactory类的几个native方法。

    其实核心思想也很简单,那就是采用BitmapFactory.Options来加载所需尺寸的图片。
    通过BitmapFactory.Options来缩放图片,主要是用到了它的inSampleSize参数,即采样率。当inSampleSize为1时,采样后的图片大小为图片的原始大小;当inSampleSize大于1时,比如为2,那么采样后的图片其宽/高均为原图大小的1/2,而像素数为原图的1/4,其占有的内存大小也为原图的1/4。

    (1)将BitmapFactory.Options的inJustDecodeBounds参数设为true并加载图片。
    这一步并不会读取文件的像素区块。只会去从
    (2)从BitmapFactory.Options中取出图片的原始宽高信息,它们对应于outWidth和outHeight参数。
    (3)根据采样率的规则并结合目标View的所需大小计算出采样率inSampleSize。
    (4)将BitmapFactory.Options的inJustDecodeBounds参数设为false,然后重新加载图片。

    我们现在有个需求,要求将一张图片进行模糊,然后作为 ImageView 的 src 呈现给用户,而我们的原始图片大小为 1080*1920,如果我们直接拿来模糊的话,一方面模糊的过程费时费力,另一方面生成的图片又占用内存,实际上在模糊运算过程中可能会存在输入和输出并存的情况,此时内存将会有一个短暂的峰值。

    1.2 从Bitmap转换为文件
    Bitmap支持的文件格式

    Bitmap在内存中的大小是可以简单计算出来的了。但是文件不同,文件可以进行压缩

    一种比较典型的压缩方式比如

    image.png image.png

    位图的格式有很多种, 每种的压缩算法都不同。


    image.png

    但是目前Android中的Bitmap只支持三种

    image.png image.png

    CompressFormat解析:
    和刚才的Bitmap.Config相比,这个内部类只会在压缩文件等时被用到

    Bitmap.CompressFormat.JPEG:表示以JPEG压缩算法进行图像压缩,压缩后的格式可以是".jpg"或者".jpeg",是一种有损压缩。

    Bitmap.CompressFormat.PNG:表示以PNG压缩算法进行图像压缩,压缩后的格式可以是".png",是一种无损压缩。这意味着在解析时,可能会忽略掉 质量。

    Bitmap.CompressFormat.WEBP:是一种同时提供了有损压缩无损压缩(可逆压缩)的图片文件格式
    而在日常应用中发现,同样的图片质量(70%)下,这三种格式的大小是有差异的:

    泡泡图文发布不同格式对比

    如果对存储性能要求更严格的话(存储器空间不足)或者有大量图片存储,可以考虑使用webp格式。

    题外话,如果本地有大量的图片资源文件,可以考虑批量将png图转换成webp格式。

    2.Bitmap的调整
    Bitmap自身的调整也是一件非常有意思的事情。 说道bitmap就不得不提Matrix。
    可以说,这俩如影相随。
    ???

    这里写什么# 矩阵变换?

    3. 手动recycler()是否有必要?

    Google对这个问题其实已经解释,但是比较含混:
    首先我们看下这个方法到底做了些什么:
    我们刚才已经看过Bitmap的java类中所包含的只是一些方便我们取用的信息。

    主要方法包括recycler都在能去JNI层查看。

    Paste_Image.png Paste_Image.png

    这段话其实看得人也比较迷糊。前一句还比较简单。大致是说其主要数据都是存在native的内存中,无轮是malloc了native memory哪些,轮不到dalvik来管。所以在“交互接口”上得自己管理好资源的分配和释放。 如果处理得不好,有可能java虚拟机自己跑得还挺欢,进程首先内存就不够用了。
    怎么办 ,就需要我们显式去调用recycler()

    所以2.3.3 之前的代码应该怎么写呢,得靠你自己来实现一个引用计数器。

    Paste_Image.png

    对于我们java程序员来说,这个真的有点难。我就想做个图片,你还得让我实现一个引用计数器?
    即便是对于今天的C++ 程序员来说,也已经有智能指针来帮助他做这些事情了。

    那么 现在的版本里,recycler是否有效果?我们写一个小demo先试试吧:
    我选用的targetApi为25 ,源码非常简单

    Paste_Image.png

    结果如图:


    Paste_Image.png

    我们发现,其实手动调用recycler并没有将内存释放掉。

    那是以前,现在又提到到了3.0之后 Dalvik 又把这些东西都收到自己的堆里, 并且和Bitmap联系起来。
    怎么联系起来的?
    我们先看看Bitmap的构造方法,这是个私有的构造方法。是在native层构建了之后,再回调过来的。

    Paste_Image.png

    在这个方法中 ,有这样一句话(API25):

    Paste_Image.png

    厉害了,native层分配的内存大小居然是业务中自己计算出来的,连同析构函数一同给了这个注册器。

    这个注册器最终会调用到VMRunTime的registerNativeAllocation
    会将native对象的大小通知给dalvik,如果当前的native内存分配过大,可能会引发一次GC,这也是为什么我们看到了上面的效果、

    Paste_Image.png

    (https://android.googlesource.com/platform/libcore/+/master/libart/src/main/java/dalvik/system/VMRuntime.java)

    最终实际进行gc的地方:

    Paste_Image.png

    (https://android.googlesource.com/platform/dalvik/+/kitkat-release/vm/alloc/HeapSource.cpp)

    当然这只是其中一种的gc触发路径。在别的很多情况下都有可能,但是recycler并不会触发gc,或者说recycler 方法并不能在性能上带来提升。gc的事情还是去交给gc去做吧。

    相关文章

      网友评论

          本文标题:Bitmap

          本文链接:https://www.haomeiwen.com/subject/qocdjxtx.html