背景

位图文件(Bitmap),扩展名可以是.bmp或者.dib。它将图像定义为由点(像素)组成,每个点可以由多种色彩表示,包括2、4、8、16、24和32位色彩 。
Bitmap 应该是很多应用中最占据内存空间的一类资源了,Bitmap 也是导致应用 OOM 的常见原因之一。例如,如果使用4048 _ 3036 像素(1200 万像素),使用的Bitmap.Config为 ARGB_8888(Android 2.3 及更高版本的默认设置),将单张照片加载到内存大约需要 48MB 内存(4048 _ 3036 * 4 字节),如此庞大的内存需求可能会立即耗尽应用的所有可用内存,所以在android开发中理解好Bitmap也有助于我们开发出更好质量的App

内存大小的计算

图片所占用的内存和图片的宽度、长度、单位像素占的字节数三个因素有关。

基本知识

首先我们得了解 一些基本参数

  • px (pixel) “pixel” 的缩写,像素。是画面显示的基本单位,真实的像素并不是点或者方块(虽然有时这样显示),也没有实际固定长度,只是一个抽象的取样。设计中的像素和实际显示屏上的像素相对应。
  • dpi “dots per inch” 的缩写,“每英寸的像素数”,即像素密度

image.png

安卓手机种类多样,有各种屏幕像素密度。比如120dpi是低密度(ldpi)类型,160dpi 是中密度(mdpi),240dpi是高密度(hdpi),320dpi是超高密度(xhdpi),480dpi是超超高密度(xxhdpi)

  • dp

设备无关像素。关于dp的官方叙述为当屏幕每英寸有160个像素时(也就是160dpi),dp与px等价的,1dp=1px。那么当屏幕为240dpi时,1dp=(240/160)px=1.5px。也就是说dp和px的换算在于dpi这个值,计算的公式为:1dp=(屏幕的dpi/160)px

想要知道在特定一台手机上 1dp 对应多少 px,或者是想要知道屏幕宽高大小,这些信息都可以通过 DisplayMetrics 来获取

1
2
3
4
val displayMetrics = applicationContext.resources.displayMetrics

打印出当前手机信息如下:
DisplayMetrics{density=3.0, width=1080, height=1920, scaledDensity=3.0, xdpi=480.0, ydpi=480.0}

从中就可以提取出几点信息:

  1. density 等于 3,说明在该模拟器上 1dp 等于 3px
  2. 屏幕宽高大小为 1920 x 1080 px,即 640 x 360 dp
  3. 屏幕像素密度为 480dpi

顺带提下 dpi跟drawable关系
如果 drawable 文件夹名不带后缀,那么该文件夹就对应 160dpi,对于 320dpi 的设备来说,应用在选择图片时就会优先从 drawable-xhdpi 文件夹拿,如果该文件夹内没找到图片,就会依照 xxhdpi -> xxxhdpi -> hdpi -> mdpi -> ldpi 的顺序进行查找,优先使用高密度版本,然后从中选择最接近当前屏幕密度的图片资源

drawable dpi
ldpi 120 dpi
mdpi 160 dpi
hdpi 240 dpi
xhdpi 320 dpi
xxhdpi 480 dpi
xxxhdpi 640 dpi

Bitmap.Config

我们都知道颜色经常用ARGB来表示,A表示Alpha,即透明度;R表示red,即红色;G表示green,即绿色;B表示blue,即蓝色。Bitmap的色彩也是用ARGB来表示的,
Bitmap.Config中有Bitmap.Config.ALPHA_8、Bitmap.Config.RGB_565、Bitmap.Config.ARGB_4444、Bitmap.Config.ARGB_8888 四个枚举变量。

  • Bitmap.Config.ALPHA_8表示:每个像素占8位,没有色彩,只有透明度A-8,共8位
  • Bitmap.Config.ARGB_4444表示:每个像素占16位,A-4,R-4,G-4,B-4,共4+4+4+4=16位。
  • Bitmap.Config.RGB_565表示:每个像素占16位,没有透明度,R-5,G-6,B-5,共5+6+5=16位
  • Bitmap.Config.ARGB_8888表示:每个像素占32位,A-8,R-8,G-8,B-8,共8+8+8+8=32位。

位数越高,那么可存储的颜色信息越多,图像也就越逼真,占用空间越大

ALPHA_8只有透明度,基本不用,ARGB_4444画质差被弃用,ARGB_8888为常用数据格式,RGB_565不包含透明度,在没有透明度限制的情况下可以替代ARGB_8888,还可以省却一半内存空间。这些格式配置在压缩图片数据方便有很好的用处,现在一些图片下载框架都是支持设置

内存计算

ARGB_8888是最占内存的,因为一个像素占32位,8位=1字节,所以一个像素占4字节的内存。ARGB_4444的一个像素占2个字节。RGB_565的一个像素也是占两个字节。ALPHA_8的一个像素只占一个字节。
一个图片的像素=图片宽度 × 图片长度。
一张图片(BitMap)占用的内存=图片宽度×图片长度×单位像素占用的字节数(图片的像素×单位像素占用的字节数)
假设有一张480x800的图片,四个格式下所占的内存如下

类型 内存计算 占内存大小(B 占内存大小(KB)
ARGB_8888 480×800×4 1536000 1536000÷1024=1500
ARGB_4444 480×800×2 768000 768000÷1024=750
ARGB_565 480×800×2 768000 768000÷1024=750
ARGB_8 480×800×1 384000 384000÷1024=375

一些常用计算

Bitmap使用主要有以下2种

  • 给ImageView设置背景

  • 当做画布来使用

    1
    2
    imageView.setImageBitmap(Bitmap bm);
    Canvas canvas = new Canvas(Bitmap bm)

    BItmap创建方法

    Bitmap.Options

    这个参数的作用非常大,他可以设置Bitmap的采样率,通过改变图片的宽度高度和缩放比例等,以达到减少图片像素数的目的,一言以蔽之,通过设置这个参数我们可以很好的控制显示和使用Bitmap。实际开发过程中,可以灵活设置该值,以降低OOM发生的概率。

  • inJustDecodeBounds:boolean类型,设为true时,无需要把图片加载入内存就可以获取图片的高度,宽度和图片的MIME类型。

高度通过options.outWidth获取 宽度通过options.outHeight获取,MIME通过options.outMineType获取

  • inSampleSize:这个字段表示采样率,打个比方说,设置为4,则是从原本图片的四个像素中取一个像素作为结果返回。其余的都被丢弃。可见,采样率越大,图片越小,失真越严重。 如何计算采样率呢?看一下这段代码你就会明白

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public int getSampleSize(BitmapFactory.Options options , int dstWidth,int dstHeight){
    //dstWidth:表示目前ImageView的宽度
    //dstHeight:表示目标ImageView的高度
    //option中获取bitmap图片的信息
    int rawWidth = options.outWidth;
    int rawHeight = options.outHeight;
    int sampleSize=1;
    if(rawWidth>dstWidth||rawHeight>dstHeight){
    float ratioHeight = (float) (rawHeight/dstHeight);
    float ratioWidth = (float) (rawWidth/dstWidth);
    sampleSize = (int) Math.min(rawHeight, ratioWidth);
    }
    return sampleSize;
    }
  • **inScald:**这个参数表示,在可以缩放时,是否对当前文件进行放缩,如果设置为false就不放缩。设置为true,则会根据文件夹分辨率和屏幕分辨率进行动态缩放。

  • **inPreferredConfig:**这个参数是用来设置像素的存储格式的

    BitmapFactory

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//从资源文件中通过id加载bitmap
//Resources res:资源文件,可以context.getResources()获得
//id:资源文件的id,如R.drawable.xxx
public static Bitmap decodeResources(Resources res,int id)
//第二种只是第一种的重载方法,多了个Options参数
public static Bitmap decodeResources(Resources res,int id,Options opt)


//传入文件路径加载,比如加载sd卡中的文件
//pathName:文件的全路径名
public static Bitmap decodeFile(String pathName);
public static Bitmap decodeFile(String pathName,Options opt);


//从byte数组中加载
//offset:对应data数组的起始下标
//length:截取的data数组的长度
public static Bitmap decodeByteArray(byte[] data,int offset , int length);
public static Bitmap decodeByteArray(byte[] data,int offset , int length,Options opt);


//从输入流中加载图片
//InputStream is:输入流
//Rect outPadding:用于返回矩形的内边距
public static Bitmap decodeStream(InputStream is);
public static Bitmap decodeStream(InputStream is,Rect outPadding,Options opt);


//FileDescriptor :包含解码位图的数据文件的路径
//通过该方式从路径加载bitmap比decodeFile更节省内存,原因不解释了。
public static Bitmap decodeFileDescriptor(FileDescriptor fd);
public static Bitmap decodeFileDescriptor(FileDescriptor fd,Rect outPadding,Options opt);

BitmapFactory.decodeResource 加载的图片可能会经过缩放,该缩放目前是放在 java 层做的,效率比较低,而且需要消耗 java 层的内存。因此,如果大量使用该接口加载图片,容易导致OOM错误
BitmapFactory.decodeStream 不会对所加载的图片进行缩放,相比之下占用内存少,效率更高。
这两个接口各有用处,如果对性能要求较高,则应该使用 decodeStream;如果对性能要求不高,且需要 Android 自带的图片自适应缩放功能,则可以使用 decodeResource。
常用操作

保存本地图片

1
2
3
4
5
6
7
8
9
10
11
12
public static void writeBitmapToFile(String filePath, Bitmap b, int quality) {
try {
File desFile = new File(filePath);
FileOutputStream fos = new FileOutputStream(desFile);
BufferedOutputStream bos = new BufferedOutputStream(fos);
b.compress(Bitmap.CompressFormat.JPEG, quality, bos);
bos.flush();
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}

图片缩放

1
2
3
4
5
6
public static Bitmap bitmapScale(Bitmap bitmap, float scale) {
Matrix matrix = new Matrix();
matrix.postScale(scale, scale); // 长和宽放大缩小的比例
Bitmap resizeBmp = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
return resizeBmp;
}

对bitmap裁剪

1
2
3
4
5
public Bitmap  bitmapClip(Context context , int id , int x , int y){
Bitmap map = BitmapFactory.decodeResource(context.getResources(), id);
map = Bitmap.createBitmap(map, x, y, 120, 120);
return map;
}

Bitmap静态方法

1
2
3
4
5
6
7
8
9
10
//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.加载图像可以使用BitmapFactory和Bitmap.create系列方法
2.可以通过Options实现缩放图片,获取图片信息,配置缩放比例等功能
3.如果需要裁剪或者缩放图片,只能使用create系列函数
4.注意加载和创建bitmap事通过try catch捕捉OOM异常

Bitmap对图像操作

Bitmap裁剪图像

Bitmap.createBitmap(Bitmap source, int x, int y, int width, int height)

根据源Bitmap对象source,创建出source对象裁剪后的图像的Bitmap。x,y分别代表裁剪时,x轴和y轴的第一个像素,width,height分别表示裁剪后的图像的宽度和高度。 注意:x+width要小于等于source的宽度,y+height要小于等于source的高度。

Bitmap.createBitmap(Bitmap source, int x, int y, int width, int height,Matrix m, boolean filter)

这个方法只比上面的方法多了m和filter这两个参数,m是一个Matrix(矩阵)对象,可以进行缩放,旋转,移动等动作,filter为true时表示source会被过滤,仅仅当m操作不仅包含移动操作,还包含别的操作时才适用。其实上面的方法本质上就是调用这个方法而已。

1
2
3
public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height) {
return createBitmap(source, x, y, width, height, null, false);
}

Bitmap缩放,旋转,移动图像
Bitmap缩放,旋转,移动,倾斜图像其实就是通过

Bitmap.createBitmap(Bitmap source, int x, int y, int width, int height,Matrix m, boolean filter)

方法实现的,只是在实现这些功能的同时还可以实现图像的裁剪

1
2
3
4
5
6
7
8
9
10
// 定义矩阵对象  
Matrix matrix = new Matrix();
// 缩放图像
matrix.postScale(0.8f, 0.9f);
// 向左旋转(逆时针旋转)45度,参数为正则向右旋转(顺时针旋转)
matrix.postRotate(-45);
//移动图像
//matrix.postTranslate(100,80);
Bitmap bitmap = Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(),
matrix, true);

Matrix的postScale和postRotate方法还有多带两个参数的重载方法postScale(float sx, float sy, float px, float py)和postRotate(float degrees, float px, float py),后两个参数px和py都表示以该点为中心进行操作。
注意:虽然Matrix还可以调用postSkew方法进行倾斜操作,但是却不可以在此时创建Bitmap时使用

Bitmap 保存图像与资源释放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bitmap=BitmapFactory.decodeResource(getResources(),R.drawable.feng);
File file=new File(getFilesDir(),"lavor.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.recycle();//释放bitmap的资源,这是一个不可逆转的操作

Matrix拓展类

  • setRotate(float degrees, float px, float py) 对图片进行旋转
  • setScale(float sx, float sy) 对图片进行缩放
  • setTranslate(float dx, float dy) 对图片进行平移
  • postTranslate(centerX, centerY) 在上一次修改的基础上进行再次修改set每次操作都是全新的会覆盖上次操作
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
        //显示原图 
    ImageView iv_src = (ImageView) findViewById(R.id.iv_src);

    //显示副本
    ImageView iv_copy = (ImageView) findViewById(R.id.iv_copy);

    //[1]先把tomcat.png 图片转换成bitmap 显示到iv_src
    Bitmap srcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.tomcat);

    //[1.1]操作图片
    // srcBitmap.setPixel(20, 30, Color.RED);
    iv_src.setImageBitmap(srcBitmap);

    //[2]创建原图的副本

    //[2.1]创建一个模板 相当于 创建了一个大小和原图一样的 空白的白纸
    Bitmap copybiBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), srcBitmap.getConfig());
    //[2.2]想作画需要一个画笔
    Paint paint = new Paint();
    //[2.3]创建一个画布 把白纸铺到画布上
    Canvas canvas = new Canvas(copybiBitmap);
    //[2.4]开始作画
    Matrix matrix = new Matrix();

    //[2.5]对图片进行旋转
    //matrix.setRotate(20, srcBitmap.getWidth()/2, srcBitmap.getHeight()/2);

    //[2.5]对图片进行
    // matrix.setScale(0.5f, 0.5f);

    //[2.6]对图片进行平移
    // matrix.setTranslate(30, 0);


    //[2.7]镜面效果 如果2个方法一起用
    // matrix.setScale(-1.0f, 1);
    //post是在上一次修改的基础上进行再次修改 set 每次操作都是最新的 会覆盖上次的操作
    // matrix.postTranslate(srcBitmap.getWidth(), 0);



    //[2,7]倒影效果
    matrix.setScale(1.0f, -1);
    //post是在上一次修改的基础上进行再次修改 set 每次操作都是最新的 会覆盖上次的操作
    matrix.postTranslate(0, srcBitmap.getHeight());
    canvas.drawBitmap(srcBitmap,matrix , paint);



    //[3]把copybimap显示到iv_copy上
    iv_copy.setImageBitmap(copybiBitmap);

    常见问题

    Bitmap与Canvas,View,Drawable的关系

    我们在创建一个Canvas时,可以传入一个Bitmap,Paint在Canvas上的绘制实际上就是绘制在Bitmap对象上的。我们自定义空间所显示的View也是通过Canvas中的Bitmap来显示的。Drawable在内存占用和绘制速度这两个非常关键的点上胜过Bitmap.

    Bitmap是如何造成内存溢出以及如何避免

    Bitmap容易造成内存溢出是由于位图较大,特别是ARGB_8888存储格式的图片如果有几个这种量级的图片在内存中,并且没有及时回收,那会非常容易造成OOM,
    如何避免呢?

1.我们可以对位图进行压缩,压缩手段有PNG,JPEG,WEBP
2.对不使用的Bitmap一定要及时回收。
3.在创建Bitmap时使用try catch步骤OOM异常,使程序更健壮,即使发生了OOM也不会闪退,造成不好的使用体验.

Bitmap内存在版本中的变化

  • Android1.0~Android2.3 Bitmap的像素数据是分配在Native内存中的,

Bitmap对象在Dalvik堆中占用的数据是很小的,只有width、height、config和指向堆的引用,这样的结果是Bitmaps don’t get monitored well by the GC (it thinks they are the size of a reference),所以GC无法知道当前的内存情况是否乐观,大量创建bitmap可能不会触发到GC,而Native中bitmap的像素数据可能已经占用了过多内存,这时候就会OOM,所以推荐在bitmap使用完之后,调用recycle释放掉Native的内存。

  • Android3.0~Android7.1 分配在堆里

Bitmap的数据结构发生了改变,其中多了如下属性,用来存储像素数据 private byte[] mBuffer;
至此像素数据就和bitmap对象一起都分配在堆中了,一起接受GC管理,只要bitmap置为null没有被强引用持有,GC就会把它回收掉,和普通对象一样。

  • Android8.0+

这里开始,Bitmap的像素数据又重新回到native分配了,Bitmap数据结构中
private byte[] mBuffer;这个属性不见了,取而代之的是private final long mNativePtr;,其指向的是native的内存地址。为什么呢?
安卓的每个APP都是运行在单独的虚拟机中的,系统同时会有多个APP同时运行,所以分给每个虚拟机内存上限不会太高,一般也就几百M,虚拟机启动时内存上限就是定值,一旦达到内存上限,就会OOM。
但是安卓手机的可用内存普遍已经4、6、8个G,大多数情况下系统还是有剩余内存可用的(其他APP远没有达到自己虚拟机内存上限的情况下),而一个APP中占用内存最多的一般都是Bitmap,所以如果能把系统空余内存空间利用起来,就能大大增加当前APP的可用内存,而把bitmap的像素数据放到native就能解决这个问题,native可以直接使用整个linux系统的内存,不受当前APP所在虚拟机的内存上限控制,这样就可以持续使用内存,直到用完系统的空余内存。这样的坏处是一旦发生OOM,不会被系统的UncaughtExceptionHandler捕获,会直接crash。

常用函数

Drawble和Bitmap互相转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//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;
}

//bitmap转drawable
Bitmap bm=Bitmap.createBitmap(xxx);
BitmapDrawable bd= new BitmapDrawable(getResource(), bm);

常见函数使用例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

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) {

}
});

采样率压缩

下面的例子主要用到了 Bitmap 的采样压缩(这个采样率是根据需求来进行生成的),使用到了inBitmap内存复用和 inJustDecodeBounds (这两个字段上面都有介绍)
下面介绍获取采样的流程:
将 BitmapFactory.Options 的 inJustDecodeBounds 参数设置为 true 并加装图片。
从 BitmapFactory.Options 中取出图片的原始宽和高,它们对应于 outWidth 和 outHeight 参数。
根据采样率的规则并结合目标 View 的所需要大小计算出采样率 inSampleSize 。
将 BitmapFactory.Options 的 inJustDecodeBounds 参数设为 false ,然后重新加装图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/**
* 采样率压缩,这个和矩阵来实现缩放有点类似,但是有一个原则是“大图小用用采样,小图大用用矩阵”。
* 也可以先用采样来压缩图片,这样内存小了,可是图的尺寸也小。如果要是用 Canvas 来绘制这张图时,再用矩阵放大
* @param image
* @param compressFormat
* @param requestWidth 要求的宽度
* @param requestHeight 要求的长度
* @return
*/
public static Bitmap compressbySample(Bitmap image, Bitmap.CompressFormat compressFormat, int requestWidth, int requestHeight){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
image.compress(compressFormat, 100, baos);//质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());//把压缩后的数据baos存放到ByteArrayInputStream中

BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inPurgeable = true;
options.inJustDecodeBounds = true;//只读取图片的头信息,不去解析真是的位图
BitmapFactory.decodeStream(isBm,null,options);
options.inSampleSize = calculateInSampleSize(options,requestWidth,requestHeight);
//-------------inBitmap------------------
options.inMutable = true;
try{
Bitmap inBitmap = Bitmap.createBitmap(options.outWidth, options.outHeight, Bitmap.Config.RGB_565);
if (inBitmap != null && canUseForInBitmap(inBitmap, options)) {
options.inBitmap = inBitmap;
}
}catch (OutOfMemoryError e){
options.inBitmap = null;
System.gc();
}

//---------------------------------------

options.inJustDecodeBounds = false;//真正的解析位图
isBm.reset();
Bitmap compressBitmap;
try{
compressBitmap = BitmapFactory.decodeStream(isBm, null, options);//把ByteArrayInputStream数据生成图片
}catch (OutOfMemoryError e){
compressBitmap = null;
System.gc();
}

return compressBitmap;
}

/**
* 采样压缩比例
* @param options
* @param reqWidth 要求的宽度
* @param reqHeight 要求的长度
* @return
*/
private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {

int originalWidth = options.outWidth;
int originalHeight = options.outHeight;

int inSampleSize = 1;

if (originalHeight > reqHeight || originalWidth > reqHeight){
// 计算出实际宽高和目标宽高的比率
final int heightRatio = Math.round((float) originalHeight / (float) reqHeight);
final int widthRatio = Math.round((float) originalWidth / (float) reqWidth);
// 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高
// 一定都会大于等于目标的宽和高。
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;

}
return inSampleSize;
}

Bitmap 模糊

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
fun blurBitmap(bitmap: Bitmap, blurRadius: Float) {
// 创建RenderScript内核对象
val rs = RenderScript.create(application)
// 创建一个模糊效果的RenderScript的工具对象,第二个参数Element相当于一种像素处理的算法,高斯模糊的话用这个就好
val blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs))

// 由于RenderScript并没有使用VM来分配内存,所以需要使用Allocation类来创建和分配内存空间
// 创建Allocation对象的时候其实内存是空的,需要使用copyTo()将数据填充进去
val input = Allocation.createFromBitmap(rs, bitmap)
// 创建相同类型的Allocation对象用来输出
val type: Type = input.type
val output = Allocation.createTyped(rs, type)

// 设置渲染的模糊程度, 25f是最大模糊度
blurScript.setRadius(blurRadius)
// 设置blurScript对象的输入内存
blurScript.setInput(input)
// 将输出数据保存到输出内存中
blurScript.forEach(output)
// 将数据填充到bitmap中
output.copyTo(bitmap)

// 销毁它们释放内存
input.destroy()
output.destroy()
blurScript.destroy()
rs.destroy()
}

参考