仿写一个android图片压缩工具

安卓开发图片压缩一直是一个头痛的问题,一不小心就会oom
我对一个github上的库就行了简单的改写,把代码记录下来,自己也梳理了下图片压缩的过程。

本文的代码参考自github项目 Compressor,我只是进行了我认为需要的改动

本文的代码

尺寸压缩

尺寸压缩也就是按比例压缩尺寸,当我们需要显示图片时,控件加载的是bitmap,而bitmap的大小主要和尺寸和图片格式有关,这时候我们不需要进行质量压缩

  • 计算采样比(图片压缩比例)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // 获得原始宽高
    final int height = options.outHeight;
    final int width = options.outWidth;
    //默认为1(不压缩)
    int inSampleSize = 1;
    if (height > reqHeight || width > reqWidth) {
    //压缩到宽高都小于我们要求的大小
    while ((height / inSampleSize) >= reqHeight || (width / inSampleSize) >= reqWidth) {
    //android内部只会取2的倍数,也就是一半一半的压缩
    inSampleSize *= 2;
    }
    }
    return inSampleSize;
    }
  • 压缩尺寸

    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
    static Bitmap decodeSampledBitmapFromFile(File imageFile, int reqWidth, int reqHeight) throws IOException {
    BitmapFactory.Options options = new BitmapFactory.Options();
    //设置inJustDecodeBounds=true,时BitmapFactory加载图片不返回bitmap,只返回信息,减少内存开销
    options.inJustDecodeBounds = true;
    //相当于加载空图片获取尺寸信息
    BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options);
    //用我们上面的方法计算压缩比例
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
    //设置inJustDecodeBounds=false,返回bitmap
    options.inJustDecodeBounds = false;
    Bitmap scaledBitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options);
    //把图片旋转成正确的方向
    ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
    int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0);
    Matrix matrix = new Matrix();
    if (orientation == 6) {
    matrix.postRotate(90);
    } else if (orientation == 3) {
    matrix.postRotate(180);
    } else if (orientation == 8) {
    matrix.postRotate(270);
    }
    //复制一份旋转后的bitmap后返回
    scaledBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0, scaledBitmap.getWidth(),
    scaledBitmap.getHeight(), matrix, true);
    return scaledBitmap;
    }

质量压缩

质量压缩代码比较简单但要注意非常耗时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private static ByteArrayOutputStream compressBitmapSize(Bitmap bitmap, Bitmap.CompressFormat compressFormat,
int defaultQuality, long maxSize) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int quality = defaultQuality;
bitmap.compress(compressFormat, quality, baos);
//当大于我们指定的大小时,我就继续压缩
while (baos.toByteArray().length / 1024 > maxSize) {
if (quality <= 10) { //压缩比例要大于0
break;
} else {
baos.reset();
quality -= 10;
//quality 表示压缩多少 100 表示不压缩
bitmap.compress(compressFormat, quality, baos);
}
}
return baos;
}

完整的压缩工具类

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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
public class ImageUtil {
//compress main way
static File compressImage(File imageFile, long size, int reqWith, int reqHeight, Bitmap.CompressFormat compressFormat,
int quantity, String destinationPath) throws IOException {
FileOutputStream fileOutputStream = null;
File file = new File(destinationPath).getParentFile();
if (!file.exists()) {
file.mkdirs();
}
try {
fileOutputStream = new FileOutputStream(destinationPath);
//先压缩尺寸,防止内存溢出
Bitmap bitmap = decodeSampledBitmapFromFile(imageFile, reqWith, reqHeight);
//ByteArrayOutputStream 不需要关闭
//压缩尺寸
ByteArrayOutputStream baos = compressBitmapSize(bitmap, compressFormat, quantity, size);
fileOutputStream.write(baos.toByteArray());
} finally {
if (fileOutputStream != null) {
fileOutputStream.flush();
fileOutputStream.close();
}
}
return new File(destinationPath);
}
//按照比例压缩图片
static Bitmap decodeSampledBitmapFromFile(File imageFile, int reqWidth, int reqHeight) throws IOException {
BitmapFactory.Options options = new BitmapFactory.Options();
//just need size
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options);
//calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
//real decode bitmap
options.inJustDecodeBounds = false;
Bitmap scaledBitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options);
//rotation of the image
ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0);
Matrix matrix = new Matrix();
if (orientation == 6) {
matrix.postRotate(90);
} else if (orientation == 3) {
matrix.postRotate(180);
} else if (orientation == 8) {
matrix.postRotate(270);
}
scaledBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0, scaledBitmap.getWidth(),
scaledBitmap.getHeight(), matrix, true);
return scaledBitmap;
}
//获取采样(压缩比)
private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
while ((height / inSampleSize) >= reqHeight || (width / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
//压缩尺寸
private static ByteArrayOutputStream compressBitmapSize(Bitmap bitmap, Bitmap.CompressFormat compressFormat,
int defaultQuality, long maxSize) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int quality = defaultQuality;
bitmap.compress(compressFormat, quality, baos);
while (baos.toByteArray().length / 1024 > maxSize) {
if (quality <= 10) { // quality must >0
break;
} else {
baos.reset();
quality -= 10;
bitmap.compress(compressFormat, quality, baos);
}
}
return baos;
}
}

封装成一个工厂模式工具类

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
public class Compressor {
private long maxSize = 1024; //1024kb
private int maxWidth = 800;
private int maxHeight = 800;
private Bitmap.CompressFormat compressFormat = Bitmap.CompressFormat.JPEG;
private int quality = 80;
private String destinationDirectoryPath;
public Compressor(Context context) {
destinationDirectoryPath = context.getCacheDir().getPath() + File.separator + "images";
}
public Compressor setMaxSize(long size) {
this.maxSize = size;
return this;
}
public Compressor setMaxWidth(int maxWidth) {
this.maxWidth = maxWidth;
return this;
}
public Compressor setMaxHeight(int maxHeight) {
this.maxHeight = maxHeight;
return this;
}
public Compressor setCompressFormat(Bitmap.CompressFormat compressFormat) {
this.compressFormat = compressFormat;
return this;
}
public Compressor setQuality(int quality) {
this.quality = quality;
return this;
}
public Compressor setDestinationDirectoryPath(String destinationDirectoryPath) {
this.destinationDirectoryPath = destinationDirectoryPath;
return this;
}
//压缩到文件
public File compressToFile(File imageFile, String compressedFileName) throws IOException {
return ImageUtil.compressImage(imageFile, maxSize, maxWidth, maxHeight, compressFormat, quality,
destinationDirectoryPath + File.separator + compressedFileName);
}
//只压缩尺寸
public Bitmap compressToBitmap(File imageFile) throws IOException {
return ImageUtil.decodeSampledBitmapFromFile(imageFile, maxWidth, maxHeight);
}
}

使用

推荐大家使用WEBP格式的图片,内存更小(png无损格式很大)而且不会损失透明度(jpg没有透明)

  • 简单用法

    1
    2
    3
    File file = new Compressor(CompressorActivity.this)
    .compressToFile(getExternalCacheDir().getAbsoluteFile(),
    UUID.randomUUID().toString() + ".jpg");
  • 指定压缩的参数

    1
    2
    3
    4
    5
    6
    7
    8
    File file = new Compressor(CompressorActivity.this)
    .setMaxWidth(1080)
    .setMaxHeight(1080)
    .setQuality(75)
    .setMaxSize(100)
    .setCompressFormat(Bitmap.CompressFormat.WEBP)
    .setDestinationDirectoryPath(getExternalCacheDir().getAbsolutePath())
    .compressToFile(new File(getPath(CompressorActivity.this, uri)), UUID.randomUUID().toString() + ".webp");