前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Bitmap 详解

Bitmap 详解

作者头像
Yif
发布2020-04-23 17:42:32
2K0
发布2020-04-23 17:42:32
举报
文章被收录于专栏:Android 进阶Android 进阶

Bitmap在Android中指的是一张图片,可以是png,也可以是jpg等其他图片格式。

Bitmap 与 Drawable 区别

  1. Bitmap 是位图信息的存储器,矩形图形每个颜色的存储器,后缀为bmp,有不同的编码器 比如RGB 565等,作为一种逐像素显示对象执行效率高,缺点是存储效率低。
  2. Drawable 作为Android 平台下图形对象,可以装载常用的格式,比如GIf,PNG,也可以进行渐变,图形等

2.1 Drawable是一种可以在Canvas上进行绘制的抽象的概念。Drawable 是一个可以调用Canvas来进行绘制的上层工具。Drawable.draw(canvas)可以将Drawable设置的绘制内容绘制到Canvas中。

2.2 Drawable的内部存储的是绘制规则,这个规则可以是一个具体的Bitmap,也可以是一个纯粹的颜色,甚至可以是一个抽象。灵活的描述。Drawable可以不含有具体的像素信息,只要它含有的信息足以在draw(canvas)方法中被调用时进行绘制就够了。也就是说,颜色、图片等都可以是一个Drawable

2.3 Drawable 可以通过XML定义,或者通过代码构建

2.4 Android 中 Drawable是一个抽象类,每个具体的Drawable都是其子类。

2.5 由于Drawable存储的只是绘制规则,因此他在draw()方法被调用前,需要先调用Drawable.setBounds()来为它设置绘制边界。

  1. Drawable 优点
  • 使用简单,比自定义View的成本低
  • 非图片类的Drawable所占用空间小,能减小apk大小
  1. Drawable 内部宽高
  • 一般getIntrinsicWidth/Height 能获取内部宽/高
  • 图片Drawable其内部宽高就是图片的宽高
  • 颜色Drawable没有内部宽高的概念
  • 内部宽高不等同于他的大小,一般Drawable没有大小概念(作为View背景时,会被拉伸至View的大小)

计算一张图片的大小

图片占用内存的计算公式:图片高度 * 图片宽度 * 一个像素占用的内存大小。所以,计算图片占用内存大小的时候,要考虑图片所在的目录跟设备密度,这两个因素其实影响的是图片的宽高,android会对图片进行拉升跟压缩

Bitmap的基本加载

BitmapFactory类提供了四类方法用来加载Bitmap

  • decodeFile 从文件系统加载
    • 通过Intent打开本地图片或照片
    • onActivityResult中获取图片uri
    • 根据uri获取图片的路径
    • 根据路径解析bitmap:Bitmap bm = BitmapFactory.decodeFile(sd_path)
  • decodeResourceR.drawable.xxx的形式从本地资源中加载

Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.aaa);

  • decodeStream 从输入流加载
    • 开启异步线程去获取网络图片
    • 网络返回InputStream
    • 解析:Bitmap bm = BitmapFactory.decodeStream(stream),这是一个耗时操作,要在子线程中执行
  • decodeByteArray 从字节数组中加载
    • 开启异步线程去获取网络图片
    • 网络返回InputStream
    • InputStream转换成byte[]
    • 解析:Bitmap bm = BitmapFactory.decodeByteArray(myByte,0,myByte.length);

注意:decodeFiledecodeResource间接调用decodeStream方法。

高效的加载Bitmap

android 色彩模式说明:

  • ALPHA_8:每个像素占用1byte内存。
  • ARGB_4444:每个像素占用2byte内存
  • ARGB_8888:每个像素占用4byte内存
  • RGB_565:每个像素占用2byte内存

Android默认的色彩模式为ARGB_8888,这个色彩模式色彩最细腻,显示质量最高。但同样的,占用的内存也最大。

BitmapFactory.OptionsinPreferredConfig参数可以 指定decode到内存中,手机中所采用的编码,可选值定义在Bitmap.Config中。缺省值是ARGB_8888

假设一张1024*1024,模式为ARGB_8888的图片,那么它占有的内存就是:1024*1024*4 = 4MB

  • 采样率inSampleSize(尺寸压缩)
代码语言:javascript
复制
<br />inSampleSize的值必须大于1时才会有效果,且采样率同时作用于宽和高;
 
当inSampleSize=1时,采样后的图片为图片的原始大小
 
当inSampleSize=2时,采样后的图片的宽高均为原始图片宽高的1/2,这时像素为原始图片的1/4,占用内存也为原始图片的1/4;
 
inSampleSize的取值应该总为2的整数倍,否则会向下取整,取一个最接近2的整数倍,比如inSampleSize=3时,系统会取inSampleSize=2
 
假设一张1024*1024,模式为ARGB_8888的图片,inSampleSize=2,原始占用内存大小是4MB,采样后的图片占用内存大小就是(1024/2) * (1024/2 )* 4 = 1MB
 
 
  • 获取采样率遵循以下步骤
代码语言:javascript
复制
<br />将BitmapFacpry.Options的inJustDecodeBounds参数设为true并加载图片当inJustDecodeBounds为true时,执行decodeXXX方法时,BitmapFactory只会解析图片的原始宽高信息,并不会真正的加载图片
 
从BitmapFacpry.Options取出图片的原始宽高(outWidth,outHeight)信息
 
选取合适的采样率
 
将BitmapFacpry.Options的inSampleSize参数设为false并重新加载图片
 
 

使用Bitmap时的一些注意事项

Bitmap recycler 相关

在Android中,Bitmap的存储分为两部分,一部分是Bitmap的数据,一部分是Bitmap的引用。 在Android2.3时代,Bitmap的引用是放在堆中的,而Bitmap的数据部分是放在栈中的,需要用户调用recycle方法手动进行内存回收,而在Android2.3之后,整个Bitmap,包括数据和引用,都放在了堆中,这样,整个Bitmap的回收就全部交给GC了,这个recycle方法就再也不需要使用了。

bitmap recycler引发的问题:当图像的旋转角度小余两个像素点之间的夹角时,图像即使旋转也无法显示,因此,系统完全可以认为图像没有发生变化。这时系统就直接引用同一个对象来进行操作,避免内存浪费。

  • 不用Bitmap即使释放
代码语言:javascript
复制
<br />if (!bmp.isRecycle()) {
 
    bmp.recycle(); //回收图片所占的内存
 
    bitmap = null;
 
    system.gc(); //提醒系统及时回收
 
}
 
 
  • 捕获异常,因为Bitmap耗内存,避免出现OOM被Crash掉

一定要对OutOfMemory异常进行捕获

代码语言:javascript
复制
<br />    Bitmap bitmap = null;
 
    try {
 
        // 实例化Bitmap
 
        bitmap = BitmapFactory.decodeFile(path);
 
    } catch (OutOfMemoryError e) {
 
 
 
    }
 
    if (bitmap == null) {
 
        return defaultBitmapMap; // 如果实例化失败 返回默认的Bitmap对象
 
    }
 
 
  • 缓存通用的Bitmap对象

有时候,可能需要在一个Activity里多次用到同一张图片。比如一个Activity会展示一些用户的头像列表,而如果用户没有设置头像的话,则会显示一个默认头像,而这个头像是位于应用程序本身的资源文件中的。如果有类似上面的场景,就可以对同一Bitmap进行缓存。如果不进行缓存,尽管看到的是同一张图片文件,但是使用BitmapFactory类的方法来实例化出来的Bitmap,是不同的Bitmap对象。缓存可以避免新建多个Bitmap对象,避免内存的浪费。在Android应用开发过程中所说的缓存有两个级别,一个是硬盘缓存,一个是内存缓存

  • 图片的质量压缩

上述用inSampleSize压缩是尺寸压缩,Android中还有一种压缩方式叫质量压缩。质量压缩是在保持像素的前提下改变图片的位深及透明度等,来达到压缩图片的目的,经过它压缩的图片文件大小(kb)会有改变,但是导入成bitmap后占得内存是不变的,宽高也不会改变。因为要保持像素不变,所以它就无法无限压缩,到达一个值之后就不会继续变小了。

  • Android加载大量图片内存溢出解决方案:

尽量不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource来设置一张大图,因为这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存,可以通过BitmapFactory.decodeStream方法,创建出一个bitmap,再将其设为ImageView的 source

使用BitmapFactory.Options对图片进行压缩(上述第二部分)

运用Java软引用,进行图片缓存,将需要经常加载的图片放进缓存里,避免反复加载

Bitmap一些其他用法

  • 图片旋转指定角度
  • 图片合成
  • 图片圆角
  • 将Bitmap转换成drawable

Drawable newBitmapDrawable = new BitmapDrawable(bitmap);

还可以从BitmapDrawable中获取Bitmap对象

Bitmap bitmap = new BitmapDrawable.getBitmap();

  • drawable转换成Bitmap
  • 图片的放大和缩小
代码语言:javascript
复制
<br />public Bitmap scaleMatrixImage(Bitmap oldbitmap, float scaleWidth, float scaleHeight) {
 
    Matrix matrix = new Matrix();
 
    matrix.postScale(scaleWidth,scaleHeight);// 放大缩小比例
 
    Bitmap ScaleBitmap = Bitmap.createBitmap(oldbitmap, 0, 0, oldbitmap.getWidth(), oldbitmap.getHeight(), matrix, true);
 
    return ScaleBitmap;
 
}
 
 
  • 图片裁剪
代码语言:javascript
复制
<br />public Bitmap cutImage(Bitmap bitmap, int reqWidth, int reqHeight) {
 
    Bitmap newBitmap = null;
 
    if (bitmap.getWidth() &gt; reqWidth &amp;&amp; bitmap.getHeight() &gt; reqHeight) {
 
        bitmap = Bitmap.createBitmap(bitmap, 0, 0, reqWidth, reqHeight);
 
    } else {
 
        bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight());
 
    }
 
    return bitmap;
 
}
 
 
  • 图片保存到sd
代码语言:javascript
复制
<br />    public void savePic(Bitmap bitmap,String path) {
 
        File file = new File(path);
 
        FileOutputStream fileOutputStream = null;
 
        try {
 
            file.createNewFile();
 
            fileOutputStream = new FileOutputStream(file);
 
//以质量为100%的方式压缩图像,但是图片大小不变
 
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);
 
            fileOutputStream.flush();
 
        } catch (IOException e) {
 
            e.printStackTrace();
 
        } finally {
 
            try {
 
                if (fileOutputStream != null) {
 
                    fileOutputStream.close();
 
                }
 
            } catch (IOException e) {
 
                e.printStackTrace();
 
            }
 
        }
 
    }
 
 

Bitmap与Drawable相互转换

Drawable 转换成 Bitmap

  • 方法一

通过 BitmapFactory 中的 decodeResource 方法,将资源文件中的 R.drawable.ic_drawable 转化成Bitmap

代码语言:javascript
复制
<br />Resources res = getResources();
 
Bitmap bmp = BitmapFactory.decodeResource(res, R.drawable.ic_drawable);
 
 
  • 方法二

将 Drable 对象先转化成 BitmapDrawable ,然后调用 getBitmap 方法 获取

代码语言:javascript
复制
<br />Resource res = gerResource();
 
Drawable drawable = res.getDrawable(R.drawable.ic_drawable);//获取drawable
 
BitmapDrawable bd = (BitmapDrawable) drawable;
 
Bitmap bm = bd.getBitmap();
 
 
  • 方法三

根据已有的Drawable创建一个新的Bitmap

代码语言:javascript
复制
<br />    public static Bitmap drawableToBitmap(Drawable drawable) {
 
 
 
        int w = drawable.getIntrinsicWidth();
 
        int h = drawable.getIntrinsicHeight();
 
        System.out.println("Drawable转Bitmap");
 
        Bitmap.Config config =
 
                drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888
 
                        : Bitmap.Config.RGB_565;
 
        Bitmap bitmap = Bitmap.createBitmap(w, h, config);
 
        //注意,下面三行代码要用到,否则在View或者SurfaceView里的canvas.drawBitmap会看不到图
 
        Canvas canvas = new Canvas(bitmap);
 
        drawable.setBounds(0, 0, w, h);
 
        drawable.draw(canvas);
 
 
 
        return bitmap;
 
    }
 
 

Bitmap 转换成 Drawable

使用 BitmapDrawable 对 Bitmap 进行强制转换

Drawable drawable = new BitmapDrawable(bmp);

Bitmap 转换成 byte[]

代码语言:javascript
复制
<br />public static byte[] getBytes(Bitmap bitmap){
 
 
 
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
 
 
 
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
 
 
 
        return baos.toByteArray();   
 
 
 
    }
 
 

byte[] 转化成 Bitmap

代码语言:javascript
复制
<br />    public static Bitmap Bytes2Bimap(byte[] b) {
 
 
 
        if (b.length != 0) {
 
            return BitmapFactory.decodeByteArray(b, 0, b.length);
 
        } else {
 
            return null;
 
        }
 
 
 
    }
 
 

Android 改变bitmap内部颜色

代码语言:javascript
复制
<br />public static Bitmap tintBitmap(Bitmap inBitmap , int tintColor) {
 
    if (inBitmap == null) {
 
        return null;
 
    }
 
    Bitmap outBitmap = Bitmap.createBitmap (inBitmap.getWidth(), inBitmap.getHeight() , inBitmap.getConfig());
 
    Canvas canvas = new Canvas(outBitmap);
 
    Paint paint = new Paint();
 
    paint.setColorFilter( new PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_IN)) ;
 
    canvas.drawBitmap(inBitmap , 0, 0, paint) ;
 
    return outBitmap ;
 
}
 
 

高斯模糊

高斯模糊实现原理

在Android平台上进行模糊渲染是一个相当耗CPU也相当耗时的操作,一旦处理不好,卡顿是在所难免的。考虑到效率,渲染一张图片最好的方法是使用OpenGL,其次是使用C++/C,使用Java代码是最慢的。但是Android推出RenderScript之后,我们就有了新的选择,测试表明,使用RenderScript的渲染效率和使用C/C++不相上下,但是使用RenderScript却比使用JNI简单地多!

原理步骤如下所示:

  • 压缩图片,可以质量压缩,也可以宽高压缩
  • 创建RenderScript内核对象
  • 创建一个模糊效果的RenderScript的工具对象
  • 设置相关参数,具体看代码……

实现思路:先将图片进行最大程度的模糊处理,再将原图放置在模糊后的图片上面,通过不断改变原图的透明度(Alpha值)来实现动态模糊效果。

2 高斯模糊实现的代码

2.1 设置高斯模糊代码

代码语言:javascript
复制
<br />/**
 
 * 设置模糊背景
 
 */
 
private void setBlurBackground(int pos) {
 
    //获取轮播图索引pos处的图片
 
    Integer integer = pagerAdapter.getBitmapHashMap().get(pos);
 
    Resources res = this.getResources();
 
    Bitmap bitmap= BitmapFactory.decodeResource(res, integer);
 
    //压缩图片
 
    final Bitmap image = BitmapUtils.compressImage(bitmap);
 
 
 
    if (bitmap != null) {
 
        if (mBlurRunnable != null) {
 
            mIvBlurBackground.removeCallbacks(mBlurRunnable);
 
        }
 
        mBlurRunnable = new Runnable() {
 
            @Override
 
            public void run() {
 
                //压缩图片,宽高缩放
 
                Bitmap blurBitmap = BlurBitmapUtils.getBlurBitmap(
 
                        mIvBlurBackground.getContext(), image, 15);
 
                ViewSwitchUtils.startSwitchBackgroundAnim(mIvBlurBackground, blurBitmap);
 
            }
 
        };
 
        mIvBlurBackground.postDelayed(mBlurRunnable, 100);
 
    }
 
}
 
 

2.2 RenderScript图片高斯模糊

代码语言:javascript
复制
<br />/**
 
 * RenderScript图片高斯模糊
 
 */
 
public class BlurBitmapUtils {
 
 
 
    /**
 
     * 建议模糊度(在0.0到25.0之间)
 
     */
 
    private static final int SCALED_WIDTH = 100;
 
    private static final int SCALED_HEIGHT = 100;
 
 
 
    /**
 
     * 得到模糊后的bitmap
 
     * @param context 上下文
 
     * @param bitmap bitmap
 
     * @param radius 半径
 
     * @return
 
     */
 
    public static Bitmap getBlurBitmap(Context context, Bitmap bitmap, int radius) {
 
        // 将缩小后的图片做为预渲染的图片。
 
        Bitmap inputBitmap = Bitmap.createScaledBitmap(bitmap, SCALED_WIDTH, SCALED_HEIGHT, false);
 
        // 创建一张渲染后的输出图片。
 
        Bitmap outputBitmap = Bitmap.createBitmap(inputBitmap);
 
        // 创建RenderScript内核对象
 
        RenderScript rs = RenderScript.create(context);
 
        // 创建一个模糊效果的RenderScript的工具对象
 
        ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
 
        // 由于RenderScript并没有使用VM来分配内存,所以需要使用Allocation类来创建和分配内存空间。
 
        // 创建Allocation对象的时候其实内存是空的,需要使用copyTo()将数据填充进去。
 
        Allocation tmpIn = Allocation.createFromBitmap(rs, inputBitmap);
 
        Allocation tmpOut = Allocation.createFromBitmap(rs, outputBitmap);
 
        // 设置渲染的模糊程度, 25f是最大模糊度
 
        blurScript.setRadius(radius);
 
        // 设置blurScript对象的输入内存
 
        blurScript.setInput(tmpIn);
 
        // 将输出数据保存到输出内存中
 
        blurScript.forEach(tmpOut);
 
        // 将数据填充到Allocation中
 
        tmpOut.copyTo(outputBitmap);
 
        return outputBitmap;
 
    }
 
 
 
}
 
 

2.3 设置高斯模糊背景View动画过渡效果

代码语言:javascript
复制
<br />/**
 
 * 图片背景切换动画帮助类,设置View动画
 
 */
 
public class ViewSwitchUtils {
 
 
 
    static void startSwitchBackgroundAnim(ImageView view, Bitmap bitmap) {
 
        Drawable oldDrawable = view.getDrawable();
 
        Drawable oldBitmapDrawable ;
 
        TransitionDrawable oldTransitionDrawable = null;
 
        if (oldDrawable instanceof TransitionDrawable) {
 
            oldTransitionDrawable = (TransitionDrawable) oldDrawable;
 
            oldBitmapDrawable = oldTransitionDrawable.findDrawableByLayerId(oldTransitionDrawable.getId(1));
 
        } else if (oldDrawable instanceof BitmapDrawable) {
 
            oldBitmapDrawable = oldDrawable;
 
        } else {
 
            oldBitmapDrawable = new ColorDrawable(0xffc2c2c2);
 
        }
 
 
 
        if (oldTransitionDrawable == null) {
 
            oldTransitionDrawable = new TransitionDrawable(new Drawable[]{oldBitmapDrawable, new BitmapDrawable(bitmap)});
 
            oldTransitionDrawable.setId(0, 0);
 
            oldTransitionDrawable.setId(1, 1);
 
            oldTransitionDrawable.setCrossFadeEnabled(true);
 
            view.setImageDrawable(oldTransitionDrawable);
 
        } else {
 
            oldTransitionDrawable.setDrawableByLayerId(oldTransitionDrawable.getId(0), oldBitmapDrawable);
 
            oldTransitionDrawable.setDrawableByLayerId(oldTransitionDrawable.getId(1), new BitmapDrawable(bitmap));
 
        }
 
        oldTransitionDrawable.startTransition(1000);
 
    }
 
}
 
 

3 高斯模糊可能会造成的崩溃

3.1 崩溃日志

开发回收bitmap引发Canvas: trying to use a recycled bitmap错误处理

3.2 抛该异常的原因分析

如果代码已经不再需要使用Bitmap对象了,就可以释放了。释放内存以后,就不能再使用该Bitmap对象了,如果再次使用,就会抛出异常。所以一定要保证不再使用的时候释放。

3.3 解决该问题的办法

使用缓存

4 高斯模糊参考案例

Android 图片高斯模糊解决方案:https://www.jianshu.com/p/02da487a2f43

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020年4月22日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Bitmap 与 Drawable 区别
  • 计算一张图片的大小
  • Bitmap的基本加载
  • 高效的加载Bitmap
  • 使用Bitmap时的一些注意事项
    • Bitmap recycler 相关
    • Bitmap一些其他用法
    • Bitmap与Drawable相互转换
      • Drawable 转换成 Bitmap
        • Bitmap 转换成 Drawable
          • Bitmap 转换成 byte[]
            • byte[] 转化成 Bitmap
              • Android 改变bitmap内部颜色
              • 高斯模糊
              相关产品与服务
              文件存储
              文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档