Bitmap

作者: 30cf443c3643 | 来源:发表于2018-10-29 16:51 被阅读8次

    位图

    最常见的图片格式有两种,png和jpg。两者的区别:

    1. png背景可以为透明,jpg背景不可以。类似于iconfont上任意素材下载下来都是png格式,它是没有背景的


      2018-10-22_164454.png
    2. png是无损压缩,jpg是有损压缩,会牺牲图片质量

    一张位图的所占内存 = 图片的长度px * 宽度px * 像素点所占的内存
    而像素点所占内存的大小跟色彩模式有关

    Bitmap.Config 字节
    Bitmap.Config ARGB_4444 即A=4,R=4,G=4,B=4,那么一个像素点占4+4+4+4=16位 ,占2字节
    Bitmap.Config ARGB_8888 即A=8,R=8,G=8,B=8,那么一个像素点占8+8+8+8=32位,占4字节
    Bitmap.Config RGB_565 即R=5,G=6,B=5,没有透明度,那么一个像素点占5+6+5=16位,占2字节
    Bitmap.Config ALPHA_8 每个像素占四位,只有透明度,没有颜色 A = 8。占1字节

    A:透明度 R:红色 G:绿 B:蓝
    android中创建的bitmap一般会选用ARGB8888模式。当图片的像素配置和手机配置不一样时,开启抖动可以避免图片显示过于失真

    BitmapDrawable

    并不是所有的drawable都有宽高,但是位图是有内部宽高的,可以通过getIntrinsicWidth和getIntrinsicHeight来获得。

    1. bitmap转drawable
    Drawable drawable = new BitmapDrawable(getResources(),bitmap);
    
    1. drawable转bitmap
    private void drawableToBitamp(Drawable drawable)   {
            BitmapDrawable bd = (BitmapDrawable) drawable;
            bitmap = bd.getBitmap();
     }
    

    Bitmap的加载和压缩

    BitmapFactory类提供了四个方法

    1. decodeFile(String pathName, Options opts)
    2. decodeResource(Resources res, int id, Options opts)
    3. decodeSteam(@Nullable InputStream is)
    4. decodeByteArray(byte[] data, int offset, int length, Options opts)

    分别用于支持从文件系统,资源,输入流和字节数组中加载返回一个bitmap。但是加载图片在一定程度上容易引起OOM,需要提高bitmap加载时的性能。首先可以对图像压缩。

    质量压缩

    质量压缩主要借助Bitmap中的compress方法实现

    public boolean compress (Bitmap.CompressFormat format, int quality, OutputStream stream)

    format压缩后的格式,取值为Bitmap.CompressFormat .JPEG,Bitmap.CompressFormat.PNG,Bitmap.CompressFormat.WEBP;quality质量,0-100之间。值越小,经过压缩后图片失真越严重,当然图片文件也会越小。但是,质量压缩只是改变磁盘中的文件大小,并不能改变加载时内存中的图片大小

    public static void compressQuality(Bitmap bitmap, int quality, File file) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos);
        try {
            FileOutputStream fos = new FileOutputStream(file);
            fos.write(baos.toByteArray());
            fos.flush();
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static Bitmap compressImage(Bitmap image) {  
                ByteArrayOutputStream baos = new ByteArrayOutputStream();  
                image.compress(Bitmap.CompressFormat.JPEG, 90, baos);// 质量压缩方法
                ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());// 把压缩后的数据baos存放到ByteArrayInputStream中  
                Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);// 把ByteArrayInputStream数据生成图片  
                return bitmap;  
            }
    

    大小压缩

    等比例缩放图片大小。磁盘中图片的大小并没有改变,只是改变了加载时内存中的图片大小

    /**
     * 尺寸压缩
     *
     * @param bitmap
     * @param file
     */
    public static void compressSize(Bitmap bitmap, File file) {
        int ratio = 8;//尺寸压缩比例
        Bitmap result = Bitmap.createBitmap(bitmap.getWidth() / ratio, bitmap.getHeight() / ratio, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(result);
        Rect rect = new Rect(0, 0, bitmap.getWidth() / ratio, bitmap.getHeight() / ratio);
        canvas.drawBitmap(bitmap, null, rect, null);
    
        compressQuality(result, 100, file);
    }
    

    采样率压缩

    通过BitmapFactory.Options来缩放图片,主要是inSampleSize参数,取值应该为2的指数,1,2,4,8,16。如果不是2的指数,则向下去整选择一个接近2的指数来代替
    流程:

    1. inJustDecodeBounds 设置为true 并加载图片;为true时,并不会真正的加载图片,轻量级的操作
    2. 取出原图片的宽高 对应options的outHeight,outWidth
    3. 根据需求计算采样率
    4. inJustDecodeBounds 设置为false
    
    public static void compressSample(String filePath, File file) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(filePath,options);
        options.inSampleSize = calcuateInSampleSize(options,reqwidth,reqheight);
        options.inJustDecodeBounds = false;
        Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);
    }
    

    其他

    Android图片压缩的几种方案,里面有写ndk的压缩方案。
    鲁班压缩 开源库,接近微信朋友圈压缩后的效果

    缓存策略

    一般是这样的"内存-本地-网络"三级缓存策略。常用的缓存算法是LRU,核心思想是当缓存满时,会优先淘汰近期最少使用的缓存对象。采用LRU算法的缓存有两种,LruCache用于实现内存缓存,DiskLruCache充当设备缓存。LruCache内部采用一个LinkedHashMap以强引用的形式来存储缓存,它是线程安全的,直接new对象,同时重写sizeof方法。而DiskLruCache提供open来创建自身

    mDiskLruCache = DiskLruCache.open(file_File,version_int,1,cachesize_long)
    

    缓存添加通过editor完成,获取图片url对应的key,根据key就可以获得editor对象

    String key = hashKeyFromUrl(url);//可能有特殊字符,一般用md5值作为key
    DiskLruCache.Editor editor = mDiskLruCache.edit(key);
    

    然后就可以得到一个文件输出流

    OutputSteam steam = editor.newOutputSteam(DISK_CACHE_INDEX);
    

    然后写入文件系统,提交。如果异常可以通过editor的abort回退操作

    if(downloadUrlToSteam(url,steam)){
          editor.commit();
    }else{
          editor.abort();
    }
    mDiskLruCache.flush();
    

    模糊

    常用的图片高斯模糊技术:RenderScript是在Android3.0(API 11)引入的。而Android图片高斯模糊处理,通常也是用这个库来完成。它提供了我们Java层调用的API,实际上是在c/c++ 层来处理的,所以它的效率和性能通常是最高的。具体实现代码

    private static Bitmap rsBlur(Context context,Bitmap source,int radius){
    
            Bitmap inputBmp = source;
            //(1)创建RenderScript内核对象
            RenderScript renderScript =  RenderScript.create(context);
    
            // Allocate memory for Renderscript to work with
            //(2)创建一张渲染后的输入图片
            /**
             * 由于RenderScript并没有使用VM来分配内存,所以需要使用Allocation类来创建和分配内存空间。
             * 创建Allocation对象的时候其实内存是空的,需要使用copyTo()将数据填充进去。
             */
            final Allocation input = Allocation.createFromBitmap(renderScript,inputBmp);
            final Allocation output = Allocation.createTyped(renderScript,input.getType());
            //(3)  创建一个模糊效果的RenderScript的工具对象
            // Load up an instance of the specific script that we want to use.
            ScriptIntrinsicBlur scriptIntrinsicBlur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript));
            //(4)设置ScriptIntrinsicBlur对象的输入内存
            scriptIntrinsicBlur.setInput(input);
            //(5) 输入模糊半径0-25
            scriptIntrinsicBlur.setRadius(radius);
            //(6) 启动内核,调用方法处理:调用forEach 方法模糊处理
            scriptIntrinsicBlur.forEach(output);
            //(7) 从Allocation 中拷贝数据
            output.copyTo(inputBmp);
            //(8) 销毁RenderScript对象
            renderScript.destroy();
    
            return inputBmp;
        }
    
    

    原图与模糊后效果


    2018-10-29_145920.png

    其余还有NativeBlur,还有JavaBlur直接在Java层做图片的模糊处理。对每个像素点应用高斯模糊计算、最后在合成Bitmap的方法,但是相对于NativeBlur以及RsBlur更耗时,这种方式是把图片全部加载到内存,如果图片较大,容易导致OOM。
    可参考如何封装个好用的高斯模糊组件

    内存大小

    看了这篇文章后Android中一张图片占据的内存大小是如何计算,总结了一些知识点

    1. pic.png占用空间大小 并不等于加载后的内存。bitmap.getByteCount得到的值等于长4B(系统默认是以 ARGB_8888)
    2. 位于 res 内的不同资源目录中的图片,当加载进内存时,会先经过一次分辨率的转换,然后再计算大小,宽高会改变,转换的影响因素是设备的 dpi 和不同的资源目录。所以图片需要正确的加载对应目录下的图片,否则非常影响内存的大小
    3. 同一图片,放在 res 内相同的资源目录下,但在不同 dpi 的设备中,图片占用的内存空间也是会不一样的

    加载大图

    对于图片加载还有种情况,就是单个图片非常巨大,并且还不允许压缩。比如显示:世界地图、清明上河图、微博长图等。通过局部加载,然后手势拖动查看其余部分。BitmapRegionDecoder可以用来显示图片的某一块矩形区域

     BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
    bitmapRegionDecoder.decodeRegion(rect, options);  //rect 为区域
    

    然后自定义显示大图控件

    1. 提供一个设置图片的入口
    2. 重写onTouchEvent,在里面根据用户移动的手势,去更新显示区域的参数
    3. 每次更新区域参数后,调用invalidate,onDraw里面去regionDecoder.decodeRegion拿到bitmap,去draw

    Android 高清加载巨图方案 拒绝压缩图片
    其余https://mp.weixin.qq.com/s/4Ol-_PdO_yJwc4bIHqgzgg

    相关文章

      网友评论

          本文标题:Bitmap

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