工具類
獲取Bitmap 並進行採樣率壓縮
/**
* 從uri 得到 bitmap
* Decode image from uri using given "inSampleSize", but if failed due to out-of-memory then raise
* the inSampleSize until success.
*
* @param resolver
* @param uri
* @return
*/
public static Bitmap getBitmapFromUri(ContentResolver resolver, Uri uri) {
BitmapFactory.Options bmOpts = null;
try {
//先對圖片的最大寬高進行限定,防止OOM
bmOpts = BitmapUtils.decodeImageForOption(resolver, uri);
} catch (Exception e) {
}
int be = 1;//be=1表示不縮放
if (null != bmOpts) {
int w = bmOpts.outWidth;
int h = bmOpts.outHeight;
float hh = MAX_H_DEFAULT;
float ww = MAX_W_DEFAULT;
if (w >= h && w > ww) {//如果寬度大的話根據寬度固定大小縮放
be = (int) (bmOpts.outWidth / ww) + 1;
} else if (w < h && h > hh) {//如果高度高的話根據寬度固定大小縮放
be = (int) (bmOpts.outHeight / hh) + 1;
}
if (be <= 0) {
be = 1;
}
} else {
bmOpts = new BitmapFactory.Options();
bmOpts.inSampleSize = 2;
}
//旋轉圖片 動作
bmOpts.inSampleSize = be;
//bmOpts.inPreferredConfig = Bitmap.Config.RGB_565;
InputStream stream = null;
try {
stream = resolver.openInputStream(uri);
return BitmapFactory.decodeStream(stream, new Rect(), bmOpts);
} catch (Exception e) {
bmOpts.inSampleSize *= 2;
} finally {
closeSafe(stream);
}
return null;
}
/**
* Close the given closeable object (Stream) in a safe way: check if it is null and catch-log
* exception thrown.
*
* @param closeable the closable object to close
*/
private static void closeSafe(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException ignored) {
}
}
}
/**
* 從路徑中得到bitmap
* @param filePath
* @return
*/
public static Bitmap getBitmapFromFile(String filePath) {
Bitmap bm = null;
try {
int degree = 0;
ExifInterface exif = new ExifInterface(filePath);
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
degree = 90;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
degree = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
degree = 270;
break;
}
//先對圖片的最大寬高進行限定,防止OOM
BitmapFactory.Options bmOpts = new BitmapFactory.Options();
bmOpts.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePath, bmOpts);
bmOpts.inJustDecodeBounds = false;
int w = bmOpts.outWidth;
int h = bmOpts.outHeight;
float hh = MAX_H_DEFAULT;
float ww = MAX_W_DEFAULT;
int be = 1;//be=1表示不縮放
if (w >= h && w > ww) {//如果寬度大的話根據寬度固定大小縮放
be = (int) (bmOpts.outWidth / ww) + 1;
} else if (w < h && h > hh) {//如果高度高的話根據寬度固定大小縮放
be = (int) (bmOpts.outHeight / hh) + 1;
}
if (be <= 0) {
be = 1;
}
//旋轉圖片 動作
Matrix matrix = new Matrix();
matrix.postRotate(degree);
bmOpts.inSampleSize = be;
//bmOpts.inPreferredConfig = Bitmap.Config.RGB_565;
bm = BitmapFactory.decodeFile(filePath, bmOpts);
if (degree == 0) {
return bm;
}
// 創建新的圖片
return Bitmap.createBitmap(bm, 0, 0,
bm.getWidth(), bm.getHeight(), matrix, true);
} catch (Exception e) {
e.printStackTrace();
} finally {
System.gc();
}
return null;
}
同理,根據 BitmapFactory 類中的方法,我們可以知道有哪些創建bitmap 的方法;
大致有四類,從 文件、工程內資源、二進制數組、流 可獲取bitmap 。 上面的代碼給出了 從 文件路徑 、Uri (轉化爲流Stream) 獲取bitmap 。其思路都是類似,
- 獲取bitmap,得到bitmapConfig
- 根據寬高比獲取採樣比,
- 再次獲取bitmap 並帶上options
1 . 獲取bitmap,得到bitmapConfig
BitmapFactory.Options bmOpts = new BitmapFactory.Options();
bmOpts.inJustDecodeBounds = true;
// 獲取到bitmap的過程中來得到config
BitmapFactory.decodeByteArray(bytes, 0, bytes.length, bmOpts);
// 通過file
BitmapFactory.decodeFile(filePath, bmOpts);
....
還可以通過資源、二進制數組
2 根據寬高比獲取採樣比
法 一:一次性確定採樣率
bmOpts.inJustDecodeBounds = false;
int w = bmOpts.outWidth;
int h = bmOpts.outHeight;
float hh = max_h;
float ww = max_w;
int be = 1;//be=1表示不縮放
if (w >= h && w > ww) {//如果寬度大的話根據寬度固定大小縮放
be = (int) (bmOpts.outWidth / ww) + 1;
} else if (w < h && h > hh) {//如果高度高的話根據寬度固定大小縮放
be = (int) (bmOpts.outHeight / hh) + 1;
}
if (be <= 0) {
be = 1;
}
法二:循環減少2倍
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
int height = options.outHeight;//獲取原始圖片的高度
int width = options.outWidth;//獲取原始圖片的寬度
int inSampleSize = 1;//設置採樣率
if (height > reqHeight || width > reqWidth) {
int halfHeight = height / 2;
int halfWidth = width / 2;
while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
3 再次獲取bitmap 並帶上options
bm = BitmapFactory.decodeFile(filePath, bmOpts);
上面的Uri 、 File 都有相應的獲取bitmap 代碼,注意:File 的有些不一樣,最後加了一個 Bitmap.createBitmap
if (degree == 0) {
return bm;
}
// 創建新的圖片
return Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true);
因爲通過這個Bitmap 類createBitmap 方法可以帶一個矩陣Matrix ,matrix可以進行圖片變換,如拉伸、旋轉、縮放操作,在上面的操作中,就有檢測圖片是否有旋轉,有的話就直接恢復成之前的樣子。
質量Quality壓縮
/**
* 只進行質量壓縮
* @param bitmap
* @return
*/
public static byte[] compressMass(Bitmap bitmap) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
int q = 100;
while (stream.toByteArray().length / 1024 > 200) {
stream.reset();
q -= 10;
if (q <= 0) break;
bitmap.compress(Bitmap.CompressFormat.JPEG, q, stream);
}
return stream.toByteArray();
}
一般而言,質量數設置爲80 ,便能夠得到50——60% 的壓縮率。注意,如果使用100,則會使照片大小變大,非常奇怪,由此可見,默認的照片可能不是100。
Matrix 壓縮
一般的,通過採樣率壓縮可以達到縮放的目的,採用Matrix 也可以實現此效果。代碼如下:
/**
* 空間尺寸壓縮 給出最大寬高
* 通過matrix
* @param bitmap
* @param w
* @param h
* @return
*/
public static Bitmap zoomBitmap(Bitmap bitmap, int w, int h) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
if (width <= w && height <= h)
return bitmap;
LogUtil.debugLog("壓縮前尺寸" + width + " x " + height);
float scale ;
Matrix matrix = new Matrix();
if (width > w) {
scale = ((float) w) / width;
matrix.postScale(scale, scale);
} else {
scale = ((float) h) / height;
matrix.postScale(scale, scale);
}
LogUtil.debugLog("壓縮率" + scale * 100 + "%");
return Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
}
有一個疑問,這種方式和採樣率都可以進行尺寸縮放,不知道這二者的區別,這兩個也可以一起用。個人感覺不論是採用率還是Matrix 都是使圖片尺寸減少,從而達到壓縮的目的,二者本質應該一樣的;質量壓縮和上述兩個不一樣,質量壓縮寬高都不會變;
小結論:Bitmap 的寬高就是實際圖片的寬高,這一點可以通過日誌打印來確定。
使用場景
圖片上傳:這種情況只講究圖片大小越小越好,所以無論質量還是尺寸都是會減少圖片磁盤中尺寸,即圖片上傳時,兩種方式都需要進行。
示例代碼:
/**
* 先尺寸壓縮
* 再質量壓縮 (大於200kb 就壓縮) 或者直接設置 80 也可以 (設置爲 80 壓縮率接近50 - 60 % )
* 一般而言,用原生相機拍照 用此方法壓縮後,壓縮率可達 95%
* @param bitmap
* @return
*/
public static byte[] compress(Bitmap bitmap) {
Bitmap zoomBitmap = CompressUtil.zoomBitmap(bitmap, DEFAULT_MAX_W, DEFAULT_MAX_W);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
zoomBitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
int q = 100;
while (stream.toByteArray().length / 1024 > 200) {
stream.reset();
q -= 10;
if (q <= 0) break;
LogUtil.debugLog("當前質量" + q + "%");
zoomBitmap.compress(Bitmap.CompressFormat.JPEG, q, stream);
}
return stream.toByteArray();
}
/**
* 只進行質量壓縮
* @param bitmap
* @return
*/
public static byte[] compressMass(Bitmap bitmap) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
int q = 100;
while (stream.toByteArray().length / 1024 > 200) {
stream.reset();
q -= 10;
if (q <= 0) break;
bitmap.compress(Bitmap.CompressFormat.JPEG, q, stream);
}
return stream.toByteArray();
}
/**
* 空間尺寸壓縮 給出最大寬高
* 通過matrix
* @param bitmap
* @param w
* @param h
* @return
*/
public static Bitmap zoomBitmap(Bitmap bitmap, int w, int h) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
if (width <= w && height <= h)
return bitmap;
LogUtil.debugLog("壓縮前尺寸" + width + " x " + height);
float scale ;
Matrix matrix = new Matrix();
if (width > w) {
scale = ((float) w) / width;
matrix.postScale(scale, scale);
} else {
scale = ((float) h) / height;
matrix.postScale(scale, scale);
}
LogUtil.debugLog("壓縮率" + scale * 100 + "%");
return Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
}
Bitmap uploadBitmap = ImageUtil.getBitmapFromUri(getActivity().getContentResolver(), frontImgUri); byte[] photo = CompressUtil.compress(uploadBitmap);
getBitmapFromUri 這個方法是前面寫過的,這樣在上傳時,就把能夠壓縮的都做了。
圖片展示:Bitmap 展示會消耗內存,而消耗的內存計算方式 寬 * 高 * 一個像素點佔用的字節數, 可以通過
getByteCount() 、 getAllocationByteCount()
獲取內存消耗,一個像素點佔用的字節數和Bitmap的 Config 有關,具體如下
ALPHA_8 表示8位Alpha位圖,即A=8,一個像素點佔用1個字節,它沒有顏色,只有透明度
ARGB_4444 表示16位ARGB位圖,即A=4,R=4,G=4,B=4,一個像素點佔4+4+4+4=16位,2個字節
ARGB_8888 表示32位ARGB位圖,即A=8,R=8,G=8,B=8,一個像素點佔8+8+8+8=32位,4個字節 (系統默認格式)
RGB_565 表示16位RGB位圖,即R=5,G=6,B=5,它沒有透明度,一個像素點佔5+6+5=16位,2個字節
這個模式僅僅是android 內部定義的,它並不是圖片的格式,所以改變這個可以改變內存佔用情況,但不會減少實際尺寸。
寫在最後:實際上通過尺寸和質量壓縮兩種方式,可以使一張圖片大小減少90% 以上,有的達到 95% . 一般的質量壓縮設置爲 80 , 圖片的寬高最大可以設置爲不超過 1200 ,尺寸大小約束爲不超過 200 kb ;
參考資料:
https://www.jianshu.com/p/c77158b6e07e
https://www.jianshu.com/p/4b0ba08bfb58/ (有錯誤,壓縮部分有錯誤)
https://www.jianshu.com/p/e907eca48334
官方資料 (重要)
https://developer.android.com/topic/performance/graphics
https://developer.android.com/topic/performance/graphics/load-bitmap.html 高效加載大圖片
https://developer.android.com/topic/performance/graphics/cache-bitmap.html 緩存圖片
https://developer.android.com/topic/performance/graphics/manage-memory.html#inBitmap 管理圖片