相关文章推荐
欢快的消炎药  ·  如何配置Android客户端的相关依赖_对象 ...·  1 月前    · 
阳刚的红茶  ·  android 图片加载库 Glide ...·  1 月前    · 
迷茫的领结  ·  总结java创建文件夹的4种方法及其优缺点- ...·  1 年前    · 
温柔的滑板  ·  Java中restTemplate的使用-阿 ...·  1 年前    · 
憨厚的单杠  ·  鼠标移入暂停animation动画与清除动画 ...·  1 年前    · 
成熟的饭盒  ·  dataGrid 删除行-CSDN博客·  1 年前    · 
Code  ›  Bitmap 详解开发者社区
canvas 图像像素 图片压缩 bitmap
https://cloud.tencent.com/developer/article/1619719
活泼的瀑布
1 年前
Yif

Bitmap 详解

腾讯云
开发者社区
文档 建议反馈 控制台
首页
学习
活动
专区
工具
TVP
最新优惠活动
文章/答案/技术大牛
发布
首页
学习
活动
专区
工具
TVP 最新优惠活动
返回腾讯云官网
Yif
首页
学习
活动
专区
工具
TVP 最新优惠活动
返回腾讯云官网
社区首页 > 专栏 > Bitmap 详解

Bitmap 详解

作者头像
Yif
发布 于 2020-04-23 17:42:32
2K 0
发布 于 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)
  • decodeResource 以 R.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);

注意: decodeFile 和 decodeResource 间接调用 decodeStream 方法。

高效的加载Bitmap

android 色彩模式说明:

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

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

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

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

  • 采样率inSampleSize(尺寸压缩)
<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
 
  • 获取采样率遵循以下步骤
<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即使释放
<br />if (!bmp.isRecycle()) {
    bmp.recycle(); //回收图片所占的内存
    bitmap = null;
    system.gc(); //提醒系统及时回收
 
  • 捕获异常,因为Bitmap耗内存,避免出现OOM被Crash掉

一定要对 OutOfMemory 异常进行捕获

<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
  • 图片的放大和缩小
<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;
 
  • 图片裁剪
<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
<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

<br />Resources res = getResources();
Bitmap bmp = BitmapFactory.decodeResource(res, R.drawable.ic_drawable);
 
  • 方法二

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

<br />Resource res = gerResource();
Drawable drawable = res.getDrawable(R.drawable.ic_drawable);//获取drawable
BitmapDrawable bd = (BitmapDrawable) drawable;
Bitmap bm = bd.getBitmap();
 
  • 方法三

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

<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[]

<br />public static byte[] getBytes(Bitmap bitmap){
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
        return baos.toByteArray();   
 

byte[] 转化成 Bitmap

<br />    public static Bitmap Bytes2Bimap(byte[] b) {
        if (b.length != 0) {
            return BitmapFactory.decodeByteArray(b, 0, b.length);
        } else {
            return null;
 

Android 改变bitmap内部颜色

<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 设置高斯模糊代码

<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 图片高斯模糊

<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动画过渡效果

<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);
 
推荐文章
欢快的消炎药  ·  如何配置Android客户端的相关依赖_对象存储(OSS)-阿里云帮助中心
1 月前
阳刚的红茶  ·  android 图片加载库 Glide 的使用介绍-阿里云开发者社区
1 月前
迷茫的领结  ·  总结java创建文件夹的4种方法及其优缺点-JAVA IO基础总结第三篇 - 字母哥博客 - 博客园
1 年前
温柔的滑板  ·  Java中restTemplate的使用-阿里云开发者社区
1 年前
憨厚的单杠  ·  鼠标移入暂停animation动画与清除动画_css3 鼠标移入取消某个伪元素的animation动画-CSDN博客
1 年前
成熟的饭盒  ·  dataGrid 删除行-CSDN博客
1 年前
今天看啥   ·   Py中国   ·   codingpro   ·   小百科   ·   link之家   ·   卧龙AI搜索
删除内容请联系邮箱 2879853325@qq.com
Code - 代码工具平台
© 2024 ~ 沪ICP备11025650号