图片压缩

图片压缩是开发绕不过去的一道坎,曾经的我也是拿来就用

为了更好的开发,有必要了解些图片压缩的基本知识了

前言

当我们在本地显示图片时,我们会把图片加载到内存.内存大大小是图片的宽高和图片的质量决定的,所以我们需要限制图片的尺寸大小 (尺寸压缩).

当向服务器上传图片时我们要限制图片的文件大小(质量压缩).

结合实际情况两者配合使用.

  • 尺寸压缩:计算好压缩比例,加载指定宽高的图片到内存
  • 质量压缩:把数据读取到内存压缩图片的质量

尺寸压缩

BitmapFactory.Options:控制图片加载的策略

1
2
3
4
5
6
7
8
9
10
11
12
//压缩图片到指定宽高
public static Bitmap getSmallBitmap(String filePath, int reqWidth, int reqHeight) {
BitmapFactory.Options options = new BitmapFactory.Options();
//是否把图片解码到内存,true不解码到内存
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePath, options);
//计算压缩比例
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
//算好比例后加载压缩到指定尺寸的图片
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(filePath, options);
}

options.inSampleSize压缩图片的程度,压缩后的图片大小=原大小/inSampleSize

options.inSampleSize会默认取离2的倍数的最近的数值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//计算压缩到指定宽高的压缩比例
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
//获得bitmap的宽高
int h = options.outHeight;
int w = options.outWidth;
int inSampleSize = 0;
//如果大于指定宽高,压缩
if (h > reqHeight || w > reqWidth) {
float ratioW = (float) w / reqWidth;
float ratioH = (float) h / reqHeight;
//取小的压缩比例,防止图片压缩的太厉害
inSampleSize = (int) Math.min(ratioH, ratioW);
}
inSampleSize = Math.max(1, inSampleSize);
return inSampleSize;
}

质量压缩

1
2
3
4
5
6
7
8
9
10
11
12
13
public byte[] compressBitmapToBytes(String filePath, int reqWidth, int reqHeight, int quality) {
//先尺寸压缩
Bitmap bitmap = getSmallBitmap(filePath, reqWidth, reqHeight);
//存放的直接输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//压缩(图片格式,质量百分比(100最大),输出流)
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos);
byte[] bytes = baos.toByteArray();
//回收数据
bitmap.recycle();
Log.i(TAG, "Bitmap compressed success, size: " + bytes.length);
return bytes;
}

需要注意的地方:

1
2
3
4
5
6
7
8
9
//得到压缩后的字节数组
byte[] bytes1 = compressBitmapToBytes();
//当你用获得字节数组去加载bitmap时候
Bitmap newBItmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
byte[] bytes2 = baos.toByteArray();
//what?怎么加载一次就变大了?
//newBItmap和没有压缩过的图片占用的内存是一样大的
bytes2.length > bytes1.length;

android加载图片的时候是按照照片的大小和配置来分配内存的,虽然我们的存储大小改变了,但是宽高没有变化.

所以,当我们显示图片的时候压缩宽高就够了.

质量压缩过的图片不应该读取成bitmap,应该转换成byte数组或是保存成文件.

保存图片

  • 保存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
public static boolean saveBitmap(Bitmap bitmap, File file) {
if(bitmap == null) {
return false;
} else {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
bitmap.compress(CompressFormat.PNG, 100, fos);
fos.flush();
boolean e = true;
return e;
} catch (Exception var13) {
var13.printStackTrace();
} finally {
if(fos != null) {
try {
fos.close();
} catch (IOException var12) {
var12.printStackTrace();
}
}
}
return false;
}
}
  • 保存btye[]
1
2
3
FileOutputStream outputStream = new FileOutputStream(new File("path/xx.jpg"));
outputStream.write(bytes);
outputStream.close();

异步处理

图片上传是耗时操作,如果图片过大,压缩操作会阻塞线程很久,造成不好的体验

可以利用AsyncTask把操作放到线程中去

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
public class CompressBitmapTask extends AsyncTask<Void, Integer, Void> {
ProgressDialog dialog;//进度条
private List<String> sourcePaths;//图片地址集合
private String saveRoot;//存放目录
private Context context;
private CallBack callBack;
//还可以添加压缩的尺寸大小,文件大小等参数....
public CompressBitmapTask(String saveRoot, Context context, List<String> sourcePaths, CallBack callBack) {
this.saveRoot = saveRoot;
this.context = context;
this.sourcePaths = sourcePaths;
this.callBack = callBack;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
dialog = ProgressDialog.show(context, null, "压缩中...");
}
@Override
protected Void doInBackground(Void... voids) {
for (String path : sourcePaths) {
//1.图片压缩
//2.质量压缩
//3.存放
}
//4.关闭数据流
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
dialog.dismiss();
if (callBack != null) {
callBack.success();
}
}
}
//回调
public interface CallBack {
void success();
}

封装好的代码:

https://github.com/songsongtao/study_code