Android:安卓学习笔记之Bitmap的简单理解和使用

713次阅读
没有评论

Android Bitmap的简单理解和使用
Android Bitmap
一.Bitmap的定义
二.Bitmap的格式
2.1 存储格式
2.2 压缩格式
三.Bitmap创建方法
3.1 BitmapFactory
3.1.1、 Bitmap.Options类
3.2 Bitmap静态方法
3.3 创建Bitmap的总结
四.常见函数
4.1 函数及其参数
4.2 常用操作
五.常见问题
5.1 Bitmap与Canvas,View,Drawable的关系
5.2 使用Bitmap如何造成内存溢出的?
5.3怎么解决或者避免Bitmap内存溢出?
5.4 Bitmap与Drawable的转换
5.4.1 Drawable转换成Bitmap
5.4.2 Bitmap转换成Drawable
参考
Android Bitmap
一.Bitmap的定义
Bitmap是Android系统中的图像处理的最重要类之一。用它可以获取图像文件信息,进行图像剪切、旋转、缩放等操作,并可以指定格式保存图像文件。

Bitmap相关的使用主要有两种:

1.给ImageView设置背景
2.当做画布来使用
分别对应下面两个方法

imageView.setImageBitmap(Bitmap bm);
Canvas canvas = new Canvas(Bitmap bm)
1
2
二.Bitmap的格式
我们知道Bitmap是位图,是由像素点组成的,这就涉及到两个问题,

第一:如何存储每个像素点?
第二:怎么压缩像素点?
Bitmap 中有两个内部枚举类:Config 和 CompressFormat,Config 是用来设置颜色配置信息的,CompressFormat 是用来设置压缩方式的

2.1 存储格式

2.2 压缩格式
我们不妨来计算一下,如果一张和手机屏幕大小一样的Bitmap图片,采用ARGB_8888格式存储需要多大的内存!

按照1024*768的分辨率图片来计算,每个像素需要32位也就是4个字节,

result = 102476832B=25165824B=3MB
1
一张手机屏幕大小的Bitmap图片竟然要3M? 那就不奇怪我的app为什么一直闪退了,只不过用for循环创建了几十个用在滑动列表里面。

所以我们必须要对图片进行压缩呀,压缩格式使用枚举类Bitmap.CompressFormat

Bitmap.CompressFormat.JPEG:采用JPEG压缩算法,是一种有损压缩格式,会在压缩过程中改变图像原本质量,画质越差,对原来的图片质量损伤越大,但是得到的文件比较小,而且JPEG不支持透明度,当遇到透明度像素时,会以黑色背景填充。

Bitmap.CompressFormat.PNG:采用PNG算法,是一种支持透明度的无损压缩格式。

Bitmap.CompressFormat.WEBP:WEBP是一种同时提供了有损压缩和无损压缩的图片文件格式,在14<=api<=17时,WEBP是一种有损压缩格式,而且不支持透明度,在api18以后WEBP是一种无损压缩格式,而且支持透明度,有损压缩时,在质量相同的情况下,WEBP格式的图片体积比JPEG小40%,但是编码时间比JPEG长8倍。在无损压缩时,无损的WEBP图片比PNG压缩小26%,但是WEBP的压缩时间是PNG格式压缩时间的5倍。

三.Bitmap创建方法
我们如何创建一个 Bitamap 对象呢?Google 给我们提供了两种方式:

1、Bitmap 的静态方法 createBitmap(XX)

2、BitmapFactory 的 decodeXX 系列静态方法

3.1 BitmapFactory

BitmapFactory提供了多种创建bitmap的静态方法

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

3.1.1、 Bitmap.Options类
如何高效地加载bitmap?

通过BitmapFactory.Options按一定的采样率来加载缩小后的图片,将缩小后的图片在ImageView中显示,这样就会降低内存占用从而在一定程度上避免OOM,提高了Bitmap 加载时的性能。

采样率解释:
BitmapFactory提供的加载图片的四类方法都支持BitmapFactory.Options参数
通过BitmapFactory.Options 来缩放图片,主要用到了inSampleSize参数,即采样率。

当inSampleSize为1时,采样后的图片大小为图片的原始大小;
当inSampleSize大于1时,比如为2,那么采样后的图片其宽/高均为原图大小的1/2,而像素数为原图的1/4,其占有的内存大小也为原图的1/4。
拿一张1024×1024像素的图片来说,假定采用ARGB8888格式存储,那么它占有的内存为1024×1024×4即4MB,如果inSampleSize为2,那么采样后的图片其内存占用只有512×512×4,即1MB。

采样率同时作用于宽/高,这将导致缩放后的图片大小以采样率的2次方形式递减,即缩放比例为1/ (inSampleSize的2次方)

比如inSampleSize为4,那么缩放比例就是1/16。
有一种特殊情况,那就是当inSampleSize 小于1时,其作用相当于1,即无缩放效果。
参数说明:

举例说明

try {
FileInputStream fis = new FileInputStream(filePath);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
// 设置inJustDecodeBounds为true后,再使用decodeFile()等方法,并不会真正的分配空间,
//即解码出来的Bitmap为null,但是可计算出原始图片的宽度和高度,即options.outWidth和options.outHeight
BitmapFactory.decodeFileDescriptor(fis.getFD(), null, options);
float srcWidth = options.outWidth;
float srcHeight = options.outHeight;
int inSampleSize = 1;

if (srcHeight > height || srcWidth > width) {
    if (srcWidth > srcHeight) {
        inSampleSize = Math.round(srcHeight / height);
    } else {
        inSampleSize = Math.round(srcWidth / width);
    }
}

options.inJustDecodeBounds = false;
options.inSampleSize = inSampleSize;

return BitmapFactory.decodeFileDescriptor(fis.getFD(), null, options);

} catch (Exception e) {
e.printStackTrace();
}

过程说明:

(1) 将BitmapFactory.Options的inJustDecodeBounds参数设为true并加载图片。该参数为true时,BitmapFactory只会解析图片的原始宽高信息,不会去真正加载图片,同时获取到的信息和图片的位置与程序运行的设备有关
(2) 从BitmapFactory.Options 中取出图片的原始宽高信息,它们对应于outWidth 和outHeight参数。
(3) 根据采样率的规则并结合目标View的所需大小计算出采样率inSampleSize.
(4) 将BitmapFactory.Options 的inJustDecodeBounds 参数设为false, 然后重新加载图片。
3.2 Bitmap静态方法
//width和height是长和宽单位px,config是存储格式
static Bitmap createBitmap(int width , int height Bitmap.Config config)
// 根据一幅图像创建一份一模一样的实例
static Bitmap createBitmap(Bitmap bm)
//截取一幅bitmap,起点是(x,y),width和height分别对应宽高
static Bitmap createBitmap(Bitmap bm,int x,int y,int width,int height)
//比上面的裁剪函数多了两个参数,Matrix:给裁剪后的图像添加矩阵 boolean filter:是否给图像添加滤波效果
static Bitmap createBitmap(Bitmap bm,int x,int y,int width,int height,Matrix m,boolean filter);
//用于缩放bitmap,dstWidth和dstHeight分别是目标宽高
createScaledBitmap(Bitmap bm,int dstWidth,int dstHeight,boolean filter)

这些方法大致可以分为三类:

1、根据已有的Bitmap来创建新Bitmap
/**

  • 通过矩阵的方式,返回原始 Bitmap 中的一个不可变子集。新 Bitmap 可能返回的就是原始的 Bitmap,也可能还是复制出来的。
  • 新 Bitmap 与原始 Bitmap 具有相同的密度(density)和颜色空间;
    *
  • @param source 原始 Bitmap
  • @param x 在原始 Bitmap 中 x方向的其起始坐标(你可能只需要原始 Bitmap x方向上的一部分)
  • @param y 在原始 Bitmap 中 y方向的其起始坐标(你可能只需要原始 Bitmap y方向上的一部分)
  • @param width 需要返回 Bitmap 的宽度(px)(如果超过原始Bitmap宽度会报错)
  • @param height 需要返回 Bitmap 的高度(px)(如果超过原始Bitmap高度会报错)
  • @param m Matrix类型,表示需要做的变换操作
  • @param filter 是否需要过滤,只有 matrix 变换不只有平移操作才有效
    */
    public static Bitmap createBitmap(@NonNull Bitmap source, int x, int y, int width, int height,
    @Nullable Matrix m, boolean filter)

2、通过像素点数组创建空的Bitmap
/**
*
* 返回具有指定宽度和高度的不可变位图,每个像素值设置为colors数组中的对应值。
* 其初始密度由给定的确定DisplayMetrics。新创建的位图位于sRGB 颜色空间中。
* @param display 显示将显示此位图的显示的度量标准
* @param colors 用于初始化像素的sRGB数组
* @param offset 颜色数组中第一个颜色之前要跳过的值的数量
* @param stride 行之间数组中的颜色数(必须> = width或<= -width)
* @param width 位图的宽度
* @param height 位图的高度
* @param config 要创建的位图配置。如果配置不支持每像素alpha(例如RGB_565),
* 那么colors []中的alpha字节将被忽略(假设为FF)
*/
public static Bitmap createBitmap(@NonNull DisplayMetrics display,
@NonNull @ColorInt int[] colors, int offset, int stride,
int width, int height, @NonNull Config config)

3、 创建缩放的Bitmap
/**

  • 对Bitmap进行缩放,缩放成宽 dstWidth、高 dstHeight 的新Bitmap
    */
    public static Bitmap createScaledBitmap(@NonNull Bitmap src, int dstWidth, int dstHeight,boolean filter)

3.3 创建Bitmap的总结
1.加载图像可以使用BitmapFactory和Bitmap.create系列方法
2.可以通过Options实现缩放图片,获取图片信息,配置缩放比例等功能
3.如果需要裁剪或者缩放图片,只能使用create系列函数
4.注意加载和创建bitmap事通过try catch捕捉OOM异常
四.常见函数
4.1 函数及其参数
copy(Config config,boolean isMutable)
//根据原图像创建一个副本,但可以指定副本的像素存储格式
//参数含义。
// config:像素在内存中的存储格式,但可以指定副本的像素存储格式
// boolean isMutable:新建的bitmap是否可以修改其中的像素值

extractAlpha()
//主要作用是从bitmap中获取Alpha值,生成一幅只有Alpha值得图像,存储格式是ALPHA_8

getByteCount()//获取bitmap的字节数

recycle()://不用的bitmap必须要及时回收,以免造成oom

isRecycled()//判断bitmap是否被回收,被收回不可使用会造成crash

综合案例演示

    String items[] = {"copy","extractAlpha 1","extractAlpha 2","bitmap大小","recycle","isRecycled()"};
    ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item,items);
    adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    spinner.setAdapter(adapter);
    spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
            switch (position){
                case 0:
                    //copy
                    Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.photo);
                    Bitmap copy = bm.copy(Bitmap.Config.ARGB_8888, true);
                    imageView.setImageBitmap(copy);
                    bm.recycle();
                    break;
                case 1:
                    //extractAlpha 不带参数
                    Bitmap bp = BitmapFactory.decodeResource(getResources(), R.drawable.photo);
                    Bitmap alpha = bp.extractAlpha();
                    imageView.setImageBitmap(alpha);
                    bp.recycle();
                    break;
                case 2:
                    //extractAlpha 带参数
                    Bitmap bp1 = BitmapFactory.decodeResource(getResources(), R.drawable.photo);
                    Paint paint = new Paint();
                    BlurMaskFilter blurMaskFilter = new BlurMaskFilter(6, BlurMaskFilter.Blur.NORMAL);
                    paint.setMaskFilter(blurMaskFilter);
                    int[] offsetXY = new int[2];
                    Bitmap alpha1 = bp1.extractAlpha(paint, offsetXY);
                    imageView.setImageBitmap(alpha1);
                    break;
                case 3:
                    //获取bitmap大小
                    Bitmap b = BitmapFactory.decodeResource(getResources(), R.drawable.photo);
                    Toast.makeText(getApplicationContext(), "图片大小为:"+b.getByteCount()+"字节", Toast.LENGTH_SHORT).show();
                    break;
                case 4:
                    //回收bitmap
                    Bitmap b1 = BitmapFactory.decodeResource(getResources(), R.drawable.photo);
                    b1.recycle();
                    if(b1.isRecycled()){
                        Toast.makeText(getApplicationContext(), "已经被回收", Toast.LENGTH_SHORT).show();
                    }
                    //isRecycled()判断是否被回收
                    break;
            }

        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {

        }
    });

4.2 常用操作
1、裁剪、缩放、旋转、移动

Matrix matrix = new Matrix();
// 缩放
matrix.postScale(0.8f, 0.9f);
// 左旋,参数为正则向右旋
matrix.postRotate(-45);
// 平移, 在上一次修改的基础上进行再次修改 set 每次操作都是最新的 会覆盖上次的操作
matrix.postTranslate(100, 80);
// 裁剪并执行以上操作
Bitmap bitmap = Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, true);

2、保存与释放

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
File file = new File(getFilesDir(),”test.jpg”);
if(file.exists()){
file.delete();
}
try {
FileOutputStream outputStream=new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG,90,outputStream);
outputStream.flush();
outputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//释放bitmap的资源,这是一个不可逆转的操作
bitmap.recycle();

3、图片压缩

public static Bitmap compressImage(Bitmap image) {
if (image == null) {
return null;
}
ByteArrayOutputStream baos = null;
try {
baos = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.JPEG, 100, baos);
byte[] bytes = baos.toByteArray();
ByteArrayInputStream isBm = new ByteArrayInputStream(bytes);
Bitmap bitmap = BitmapFactory.decodeStream(isBm);
return bitmap;
} catch (OutOfMemoryError e) {
e.printStackTrace();
} finally {
try {
if (baos != null) {
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}

五.常见问题
5.1 Bitmap与Canvas,View,Drawable的关系
5.2 使用Bitmap如何造成内存溢出的?
个人认为,Bitmap容易造成内存溢出是由于位图较大,一张屏幕大小的ARGB_8888存储格式的图片竟然有24M,如果有几个这种量级的图片在内存中,并且没有及时回收,那会非常容易造成OOM

5.3怎么解决或者避免Bitmap内存溢出?
1.我们可以对位图进行压缩,压缩手段有PNG,JPEG,WEBP
2.对不使用的Bitmap一定要及时回收。
3.在创建Bitmap时使用try catch步骤OOM异常,使程序更健壮,即使发生了OOM也不会闪退,造成不好的使用体验.
5.4 Bitmap与Drawable的转换
5.4.1 Drawable转换成Bitmap
public static Bitmap drawableToBitmap(Drawable drawable) {
// 取 drawable 的长宽
int w = drawable.getIntrinsicWidth();
int h = drawable.getIntrinsicHeight();

    // 取 drawable 的颜色格式  
    Bitmap.Config config = drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888  
            : Bitmap.Config.RGB_565;  
    // 建立对应 bitmap  
    Bitmap bitmap = Bitmap.createBitmap(w, h, config);  
    // 建立对应 bitmap 的画布  
    Canvas canvas = new Canvas(bitmap);  
    drawable.setBounds(0, 0, w, h);  
    // 把 drawable 内容画到画布中  
    drawable.draw(canvas);  
    return bitmap;  
}  

5.4.2 Bitmap转换成Drawable
Bitmap bm=Bitmap.createBitmap(xxx);
BitmapDrawable bd= new BitmapDrawable(getResource(), bm);

参考
1、那些关于Bitmap图片资源优化的小事
2、Bitmap史上最详细全解
3、Android Bitmap详解
4、Android Bitmap的内存大小是如何计算的?
5、Android Bitmap 详解:关于 Bitamp 你所要知道的一切

正文完
可以使用微信扫码关注公众号(ID:xzluomor)
post-qrcode
 
评论(没有评论)