安卓開發圖片壓縮一直是一個頭痛的問題,一不小心就會
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");
複製程式碼