安卓开发图片压缩一直是一个头痛的问题,一不小心就会
oom。 我对一个github上的库就行了简单的改写,把代码记录下来,自己也梳理了下图片压缩的过程。
本文的代码参考自github项目 Compressor,我只是进行了我认为需要的改动
尺寸压缩
尺寸压缩也就是按比例压缩尺寸,当我们需要显示图片时,控件加载的是
bitmap,而bitmap的大小主要和尺寸和图片格式有关,这时候我们不需要进行质量压缩
- 计算采样比(图片压缩比例)
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; } - 压缩尺寸
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; }
质量压缩
质量压缩代码比较简单但要注意非常耗时
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;
}
完整的压缩工具类
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;
}
}
封装成一个工厂模式工具类
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没有透明)
- 简单用法
File file = new Compressor(CompressorActivity.this)
.compressToFile(getExternalCacheDir().getAbsoluteFile(),
UUID.randomUUID().toString() + ".jpg");
- 指定压缩的参数
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");