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