玩轉Android Bitmap
1. 初識Bitmap
Bitmap是一個final類,因此不能被繼承。Bitmap只有一個構造方法,且該構造方法是沒有任何訪問權限修飾符修飾,也就是說該構造方法是friendly,但是谷歌稱Bitmap的構造方法是private(私有的),感覺有點不嚴謹。不管怎樣,一般情況下,我們不能通過構造方法直接新建一個Bitmap對象。
Bitmap是Android系統中的圖像處理中最重要類之一。Bitmap可以獲取圖像文件信息,對圖像進行剪切、旋轉、縮放,壓縮等操作,并可以以指定格式保存圖像文件。
2. 創建Bitmap對象
既然不能直接通過構造方法創建Bitmap,那怎樣才能創建Bitmap對象。通常我們可以利用Bitmap的靜態方法createBitmap()
和BitmapFactory的decode
系列靜態方法創建Bitmap對象。
-
Bitmap的靜態方法
createBitmap()
-
BitmapFactory的
decode
系列靜態方法
3. Bitmap的顏色配置信息與壓縮方式信息
Bitmap中有兩個內部枚舉類:Config
和CompressFormat
,Config
是用來設置顏色配置信息的,CompressFormat
是用來設置壓縮方式的。
-
Config
解析:-
Bitmap.Config.ALPHA_8
:顏色信息只由透明度組成,占8位。 -
Bitmap.Config.ARGB_4444
:顏色信息由透明度與R(Red),G(Green),B(Blue)四部分組成,每個部分都占4位,總共占16位。 -
Bitmap.Config.ARGB_8888
:顏色信息由透明度與R(Red),G(Green),B(Blue)四部分組成,每個部分都占8位,總共占32位。是Bitmap默認的顏色配置信息,也是最占空間的一種配置。 -
Bitmap.Config.RGB_565
:顏色信息由R(Red),G(Green),B(Blue)三部分組成,R占5位,G占6位,B占5位,總共占16位。
-
通常我們優化Bitmap時,當需要做性能優化或者防止OOM(Out Of Memory),我們通常會使用Bitmap.Config.RGB_565
這個配置,因為Bitmap.Config.ALPHA_8
只有透明度,顯示一般圖片沒有意義,Bitmap.Config.ARGB_4444
顯示圖片不清楚,Bitmap.Config.ARGB_8888
占用內存最多。
-
CompressFormat
解析:-
Bitmap.CompressFormat.JPEG
:表示以JPEG壓縮算法進行圖像壓縮,壓縮后的格式可以是".jpg"或者".jpeg",是一種有損壓縮。 -
Bitmap.CompressFormat.PNG
:表示以PNG壓縮算法進行圖像壓縮,壓縮后的格式可以是".png",是一種無損壓縮。 -
Bitmap.CompressFormat.WEBP
:表示以WebP壓縮算法進行圖像壓縮,壓縮后的格式可以是".webp",是一種有損壓縮,質量相同的情況下,WebP格式圖像的體積要比JPEG格式圖像小40%。美中不足的是,WebP格式圖像的編碼時間“比JPEG格式圖像長8倍”。
-
4. Bitmap對圖像進行操作
1. Bitmap裁剪圖像
Bitmap裁剪圖像有兩種方式:
Bitmap.createBitmap(Bitmap source, int x, int y, int width, int height)
根據源Bitmap對象source,創建出source對象裁剪后的圖像的Bitmap。x,y分別代表裁剪時,x軸和y軸的第一個像素,width,height分別表示裁剪后的圖像的寬度和高度。
注意:x+width
要小于等于source的寬度,y+height
要小于等于source的高度。
Bitmap.createBitmap(Bitmap source, int x, int y, int width, int height,Matrix m, boolean filter)
這個方法只比上面的方法多了m
和filter
這兩個參數,m
是一個Matrix(矩陣)對象,可以進行縮放,旋轉,移動等動作,filter
為true時表示source會被過濾,僅僅當m
操作不僅包含移動操作,還包含別的操作時才適用。其實上面的方法本質上就是調用這個方法而已。
public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height) {
return createBitmap(source, x, y, width, height, null, false);
}
2. Bitmap縮放,旋轉,移動圖像
Bitmap縮放,旋轉,移動,傾斜圖像其實就是通過Bitmap.createBitmap(Bitmap source, int x, int y, int width, int height,Matrix m, boolean filter)
方法實現的,只是在實現這些功能的同時還可以實現圖像的裁剪。
// 定義矩陣對象
Matrix matrix = new Matrix();
// 縮放圖像
matrix.postScale(0.8f, 0.9f);
// 向左旋轉(逆時針旋轉)45度,參數為正則向右旋轉(順時針旋轉)
matrix.postRotate(-45);
//移動圖像
//matrix.postTranslate(100,80);
Bitmap bitmap = Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(),
matrix, true);
Matrix的postScale
和postRotate
方法還有多帶兩個參數的重載方法postScale(float sx, float sy, float px, float py)
和postRotate(float degrees, float px, float py)
,后兩個參數px
和py
都表示以該點為中心進行操作。
注意:雖然Matrix還可以調用postSkew
方法進行傾斜操作,但是卻不可以在此時創建Bitmap時使用。
3. Bitmap保存圖像與釋放資源
bitmap=BitmapFactory.decodeResource(getResources(),R.drawable.feng);
File file=new File(getFilesDir(),"lavor.jpg");
if(file.exists()){
file.delete();
}
try {
FileOutputStream outputStream=new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG,90,outputStream);
outputStream.flush();
outputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
bitmap.recycle();//釋放bitmap的資源,這是一個不可逆轉的操作
5. BitmapFactory通過BitmapFactory.Options對圖像進行操作
BitmapFactory是通過BitmapFactory.Options對圖像進行操作的,然后將操作后的圖像生成Bitmap對象或者將操作后的圖像用已經存在的Bitmap保存,當不能用之保存時會返回null
。
BitmapFactory.Options中常用的字段有:
-
inBitmap
:如果設置將會將生成的圖像內容加載到該Bitmap對象中。 -
inDensity
:給Bitmap對象設置的密度,如果inScaled
為true(這是默認的),而若inDensity
與inTargetDensity
不匹配,那么就會在Bitmap對象返回前將其縮放到匹配inTargetDensity
。 -
inDither
:是否對圖像進行抖動處理,默認值是false。 -
inJustDecodeBounds
:如果設置成true,表示獲取Bitmap對象信息,但是不將其像素加載到內存。 -
inPreferredConfig
:Bitmap對象顏色配置信息,默認是Bitmap.Config.ARGB_8888
。 -
inSampleSize
:對圖像進行壓縮,設置的值為2的整數次冪或者接近2的整數次冪,當次設置為2時,寬和高為都原來的1/2,圖像所占空間為原來的1/4。 -
inScaled
:設置是否縮放。 -
inTargetDensity
:繪制到目標Bitmap上的密度。 -
outHeight
:Bitmap對象的高度。 -
outWidth
:Bitmap對象的寬度。
6. 使用Bitmap時防止OOM的有效方法
1. 高效壓縮圖片
/**
* 谷歌推薦使用方法,從資源中加載圖像,并高效壓縮,有效降低OOM的概率
* @param res 資源
* @param resId 圖像資源的資源id
* @param reqWidth 要求圖像壓縮后的寬度
* @param reqHeight 要求圖像壓縮后的高度
* @return
*/
public Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
// 設置inJustDecodeBounds = true ,表示獲取圖像信息,但是不將圖像的像素加入內存
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// 調用方法計算合適的 inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
// inJustDecodeBounds 置為 false 真正開始加載圖片
options.inJustDecodeBounds = false;
//將options.inPreferredConfig改成Bitmap.Config.RGB_565,
// 是默認情況Bitmap.Config.ARGB_8888占用內存的一般
options.inPreferredConfig= Bitmap.Config.RGB_565;
return BitmapFactory.decodeResource(res, resId, options);
}
// 計算 BitmapFactpry 的 inSimpleSize的值的方法
public int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
if (reqWidth == 0 || reqHeight == 0) {
return 1;
}
// 獲取圖片原生的寬和高
final int height = options.outHeight;
final int width = options.outWidth;
Log.d(TAG, "origin, w= " + width + " h=" + height);
int inSampleSize = 1;
// 如果原生的寬高大于請求的寬高,那么將原生的寬和高都置為原來的一半
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// 主要計算邏輯
// Calculate the largest inSampleSize value that is a power of 2 and
// keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
Log.d(TAG, "sampleSize:" + inSampleSize);
return inSampleSize;
}
2. 使用緩存
常用的緩存有內存緩存LruCache
和磁盤緩存DiskLruCache
。