文章目錄
一、解碼圖片參數 inJustDecodeBounds
1 . 解碼圖片參數 :
① 設置獲取參數解碼選項 : 設置解碼時的 BitmapFactory.Options 對象的 inJustDecodeBounds 爲 true ,
② 解碼圖像 : 解析器返回的 Bitmap 對象爲 null ;
③ 解碼選項 : BitmapFactory.Options 中的 outXxx 字段會被設置對應的圖片屬性值 ;
④ 解碼選項參數示例 : 如 : outWidth 輸出圖像的 寬度 , outHeight 輸出高度 , outMimeType 輸出類型 , outConfig 像素格式 , outColorSpace 輸出顏色空間 ;
2 . 代碼示例 :
// Bitmap 圖片加載選項
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(resources, iamgeResId, options);
二、計算圖片的縮小比例
計算圖片的縮小比例 :
① 目標圖片寬高要求 : 寬度和高度只要存在一個大於限定的最大值時 , 就進行縮小操作 ; 要求指定的圖片必須能放到 maxBitmapWidth 寬度 , maxBitmapHeight 高度的矩形框中 ; 最終要求就是 寬度必須小於 maxBitmapWidth, 同時高度也要小於 maxBitmapHeight ;
② 縮小倍數要求 : 縮小倍數只能是 2 的冪次方值 , 1 , 2 , 4 , 8 , 16 , 32 , 64 ;
/*
計算縮小的比例
寬度和高度只要存在一個大於限定的最大值時 , 就進行縮小操作
要求指定的圖片必須能放到 maxBitmapWidth 寬度 , maxBitmapHeight 高度的矩形框中
最終要求就是 寬度必須小於 maxBitmapWidth, 同時高度也要小於 maxBitmapHeight
*/
if(imageWidth > maxBitmapWidth || imageHeight > maxBitmapHeight){
// 如果需要啓動縮小功能 , 那麼進入如下循環 , 試探最小的縮放比例是多少
while ( imageWidth / inSampleSize > maxBitmapWidth ||
imageHeight / inSampleSize > maxBitmapHeight ){
// 注意該值必須是 2 的冪次方值 , 1 , 2 , 4 , 8 , 16 , 32 , 64
inSampleSize = inSampleSize * 2;
}
// 執行到此處 , 說明已經找到了最小的縮放比例 , 打印下最小比例
Log.w(TAG, "getResizedBitmap inSampleSize=" + inSampleSize);
}
三、設置圖片縮小配置 inSampleSize
1 . 圖片縮小配置 inSampleSize :
① inSampleSize 設置大於 1 : 如果值大於 1 , 那麼就會縮小圖片 ;
② 解碼器操作 : 此時解碼器對原始的圖片數據進行子採樣 , 返回較小的 Bitmap 對象 ;
③ 樣本個數 : 樣本的大小是在兩個維度計算的像素個數 , 每個像素對應一個解碼後的圖片中的單獨的像素點 ;
④ 樣本個數計算示例 : 如果 inSampleSize 值爲 2 , 那麼寬度的像素個數會縮小 2 倍 , 高度也會縮小兩倍 ; 整體像素個數縮小 4 倍 , 內存也縮小了 4 倍 ;
2 . inSampleSize 取值要求 :
① 小於 1 取值 : 如果取值小於 1 , 那麼就會被當做 1 , 1 相當於 2 的 0 次方 ;
② 取值要求 : 該值必須是 2 的冪次方值 , 2 的次方值 , 如 1 , 2 , 4 , 8 , 16 , 32
③ 不合法值 : 如果出現了不合法的值 , 就會就近四捨五入到最近的 2 的冪次方值
3 . 代碼示例 :
BitmapFactory.Options options = new BitmapFactory.Options();
// ...
options.inSampleSize = inSampleSize;
四、設置圖片像素格式 inPreferredConfig
1 . 解碼像素格式 :
① 指定配置解碼 : 如果配置爲非空 , 解碼器會將 Bitmap 的像素解碼成該指定的非空像素格式 ;
② 自動匹配配置解碼 : 如果該配置爲空 , 或者像素配置無法滿足 , 解碼器會嘗試根據系統的屏幕深度 , 源圖像的特點 , 選擇合適的像素格式 ; 如果源圖像有透明度通道 , 那麼自動匹配的默認配置也有對應通道 ;
③ 默認配置 : 默認使用 ARGB_8888 進行解碼
2 . 代碼示例 :
options.inPreferredConfig = Bitmap.Config.RGB_565;
五、設置圖片複用機制
1 . 圖片複用機制 :
① 圖片複用 : 如果設置了一個 Bitmap 對象給 inBitmap 參數 , 解碼方法會獲取該 Bitmap 對象 , 當加載圖片內容時 , 會嘗試複用該 Bitmap 對象的內存
② 無法複用拋出異常 : 如果解碼方法無法複用該 Bitmap 對象 , 解碼方法可能會拋出 IllegalArgumentException 異常 ;
③ 圖片可變性 : 當前的實現是很有必要的 , 被複用的圖片必須是可變的 , 解碼後的 Bitmap 對象也是可變的 , 即使當解碼一個資源圖片時 , 經常會得到一個不可變的 Bitmap 對象 ;
2 . 解碼結果判定 :
① 解碼可能失敗 : 該解碼方法返回的 Bitmap 對象是可以使用的 , 鑑於上述約束情況 和 可能發生的失敗故障 , 不能假定該圖片解碼操作是成功的 ;
② 檢查複用是否成功 : 解碼檢查解碼返回的 Bitmap 對象是否與設置給 Options 對象的 inBitmap 相匹配 , 來判斷該 inBitmap 是否被複用 ;
③ 後續操作 : 不管有沒有複用成功 , 你應該使用解碼函數返回的 Bitmap 對象 , 保證程序的正常運行 ;
3 . 與 BitmapFactory 配合使用 :
① Android 4.4 以後的複用機制 : 在 KITKAT 以後的代碼中 , 只要被解碼生成的 Bitmap 對象的字節大小 ( 縮放後的 ) , 小於等於 inBitmap 的字節大小 , 就可以複用成功 ;
② Android 4.4 之前的複用機制 : 在 KITKAT ( Android 4.4 系統 , android-19 平臺 ) 之前的代碼中 , 被解碼的圖像必須是
- JPEG 或 PNG 格式 ,
- 並且 圖像大小必須是相等的 ,
- inssampleSize 設置爲 1 ,
才能複用成功 , 另外被複用的圖像的 像素格式 Config ( 如 RGB_565 ) 會覆蓋設置的 inPreferredConfig 參數
4 . 代碼示例 :
options.inBitmap = inBitmap;
六、Bitmap 圖像尺寸縮小代碼示例
1、圖片縮小工具類
圖片縮小工具類 :
package kim.hsl.bm.utils;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
/**
* Bitmap 尺寸縮小
*/
public class BitmapSizeReduce {
private static final String TAG = "BitmapSizeReduce";
/**
* 獲取指定尺寸於鏊求的 Bitmap 對象
* 該方法有缺陷 , 計算值的時候沒有考慮像素密度
* 如果從不同像素密度的資源文件中加載
* 可能計算出的值與指定的 maxBitmapWidth maxBitmapHeight 略有出入
*
* @param context 上下文對象
* @param iamgeResId 要解析的圖片資源 id
* @param maxBitmapWidth Bitmap 的最大寬度
* @param maxBitmapHeight Bitmap 的最大高度
* @param hasAlphaChannel 是否包含 ALPHA 通道, 即透明度信息
* @param inBitmap 複用的 Bitmap, 將新的 Bitmap 對象解析到該 Bitmap 內存中
* @return 返回新的 Bitmap 對象
*/
public static Bitmap getResizedBitmap(Context context,
int iamgeResId, int maxBitmapWidth, int maxBitmapHeight,
boolean hasAlphaChannel, Bitmap inBitmap){
// 0. 聲明方法中使用的局部變量
// 用於解析資源
Resources resources = context.getResources();
// Bitmap 圖片加載選項
BitmapFactory.Options options = new BitmapFactory.Options();
// 圖片寬度
int imageWidth;
// 圖片高度
int imageHeight;
/*
根據 圖片寬度 imageWidth , 圖片高度 imageHeight ,
最大寬度 maxBitmapWidth , 最大高度 maxBitmapHeight ,
計算出的圖片縮放係數 , 該值最終要設置到 BitmapFactory.Options 對象中
*/
int inSampleSize = 1;
// 1. 解析圖片參數 : 該階段不解析所有的數據 , 否則會將實際的圖片數據解析到內存中 , 這裏只解析圖片的寬高信息
/*
設置 inJustDecodeBounds 爲 true , 解析器會返回 null
但是 outXxx 字段會被設置對應的圖片屬性值 ,
如 : outWidth 輸出圖像的 寬度 , outHeight 輸出高度 , outMimeType 輸出類型 ,
outConfig 像素格式 , outColorSpace 輸出顏色空間
*/
options.inJustDecodeBounds = true;
/*
由於設置了 inJustDecodeBounds = true , 該方法返回值爲空 ;
但是傳入的 BitmapFactory.Options 對象中的 outXxx 字段都會被賦值 ;
如 outWidth , outHeight , outConfig , outColorSpace 等 ;
可以獲取該圖片的寬高 , 像素格式 , 顏色空間等信息
*/
BitmapFactory.decodeResource(resources, iamgeResId, options);
// 獲取 iamgeResId 圖片資源對應的圖片寬度
imageWidth = options.outWidth;
// 獲取 iamgeResId 圖片資源對應的圖片高度
imageHeight = options.outHeight;
// 2. 計算圖片縮小比例
/*
計算縮小的比例
寬度和高度只要存在一個大於限定的最大值時 , 就進行縮小操作
要求指定的圖片必須能放到 maxBitmapWidth 寬度 , maxBitmapHeight 高度的矩形框中
最終要求就是 寬度必須小於 maxBitmapWidth, 同時高度也要小於 maxBitmapHeight
*/
if(imageWidth > maxBitmapWidth || imageHeight > maxBitmapHeight){
// 如果需要啓動縮小功能 , 那麼進入如下循環 , 試探最小的縮放比例是多少
while ( imageWidth / inSampleSize > maxBitmapWidth ||
imageHeight / inSampleSize > maxBitmapHeight ){
// 注意該值必須是 2 的冪次方值 , 1 , 2 , 4 , 8 , 16 , 32 , 64
inSampleSize = inSampleSize * 2;
}
// 執行到此處 , 說明已經找到了最小的縮放比例 , 打印下最小比例
Log.w(TAG, "getResizedBitmap inSampleSize=" + inSampleSize);
}
// 3. 設置圖像解碼參數
/*
inSampleSize 設置大於 1 : 如果值大於 1 , 那麼就會縮小圖片 ;
解碼器操作 : 此時解碼器對原始的圖片數據進行子採樣 , 返回較小的 Bitmap 對象 ;
樣本個數 : 樣本的大小是在兩個維度計算的像素個數 , 每個像素對應一個解碼後的圖片中的單獨的像素點 ;
樣本個數計算示例 :
如果 inSampleSize 值爲 2 , 那麼寬度的像素個數會縮小 2 倍 , 高度也會縮小兩倍 ;
整體像素個數縮小 4 倍 , 內存也縮小了 4 倍 ;
小於 1 取值 : 如果取值小於 1 , 那麼就會被當做 1 , 1 相當於 2 的 0 次方 ;
取值要求 : 該值必須是 2 的冪次方值 , 2 的次方值 , 如 1 , 2 , 4 , 8 , 16 , 32
如果出現了不合法的值 , 就會就近四捨五入到最近的 2 的冪次方值
*/
options.inSampleSize = inSampleSize;
// 用戶設置的是否保留透明度選項 , 如果不保留透明度選項 , 設置像素格式爲 RGB_565
// 每個像素佔 2 字節內存
if (!hasAlphaChannel){
/*
指定配置解碼 : 如果配置爲非空 , 解碼器會將 Bitmap 的像素解碼成該指定的非空像素格式 ;
自動匹配配置解碼 : 如果該配置爲空 , 或者像素配置無法滿足 , 解碼器會嘗試根據系統的屏幕深度 ,
源圖像的特點 , 選擇合適的像素格式 ;
如果源圖像有透明度通道 , 那麼自動匹配的默認配置也有對應通道 ;
默認配置 : 默認使用 ARGB_8888 進行解碼
*/
options.inPreferredConfig = Bitmap.Config.RGB_565;
}
/*
注意解碼真實圖像的時候 , 要將 inJustDecodeBounds 設置爲 false
否則將不會解碼 Bitmap 數據 , 只會將
outWidth , outHeight , outConfig , outColorSpace 等 outXxx 圖片參數解碼出來
*/
options.inJustDecodeBounds = false;
/*
設置圖片可以被複用
*/
options.inMutable = true;
/*
如果設置了一個 Bitmap 對象給 inBitmap 參數
解碼方法會獲取該 Bitmap 對象 , 當加載圖片內容時 , 會嘗試複用該 Bitmap 對象的內存
如果解碼方法無法複用該 Bitmap 對象 , 解碼方法可能會拋出 IllegalArgumentException 異常 ;
當前的實現是很有必要的 , 被複用的圖片必須是可變的 , 解碼後的 Bitmap 對象也是可變的 ,
即使當解碼一個資源圖片時 , 經常會得到一個不可變的 Bitmap 對象 ;
確保是否解碼成功 :
該解碼方法返回的 Bitmap 對象是可以使用的 ,
鑑於上述約束情況 和 可能發生的失敗故障 , 不能假定該圖片解碼操作是成功的 ;
檢查解碼返回的 Bitmap 對象是否與設置給 Options 對象的 inBitmap 相匹配 ,
來判斷該 inBitmap 是否被複用 ;
不管有沒有複用成功 , 你應該使用解碼函數返回的 Bitmap 對象 , 保證程序的正常運行 ;
與 BitmapFactory 配合使用 :
在 KITKAT 以後的代碼中 , 只要被解碼生成的 Bitmap 對象的字節大小 ( 縮放後的 )
小於等於 inBitmap 的字節大小 , 就可以複用成功 ;
在 KITKAT 之前的代碼中 , 被解碼的圖像必須是
JPEG 或 PNG 格式 ,
並且 圖像大小必須是相等的 ,
inssampleSize 設置爲 1 ,
才能複用成功 ;
另外被複用的圖像的 像素格式 Config ( 如 RGB_565 ) 會覆蓋設置的 inPreferredConfig 參數
*/
options.inBitmap = inBitmap;
// 4. 解碼圖片 , 並返回被解碼的圖片
return BitmapFactory.decodeResource(resources, iamgeResId, options);
}
}
2、Activity 調用工具類代碼
Activity 代碼 :
package kim.hsl.bm;
import androidx.appcompat.app.AppCompatActivity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import kim.hsl.bm.utils.BitmapSizeReduce;
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
// 縮小圖像尺寸
sizeReduce();
}
/**
* 圖像尺寸縮小
*/
private void sizeReduce(){
// 從資源文件中加載內存
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.blog);
// 打印 Bitmap 對象的寬高, 字節大小
Log.i("Bitmap", "blog : " + bitmap.getWidth() + " , " +
bitmap.getHeight() + " , " +
bitmap.getByteCount());
// 從資源文件中加載內存
Bitmap reduceSizeBitmap = BitmapSizeReduce.getResizedBitmap(this, R.drawable.blog,
100, 100 , false , null);
// 打印 Bitmap 對象的寬高, 字節大小
Log.i("Bitmap", "reduceSizeBitmap : " + reduceSizeBitmap.getWidth() + " , " +
reduceSizeBitmap.getHeight() + " , " +
reduceSizeBitmap.getByteCount());
}
public native String stringFromJNI();
}
3、執行結果
執行結果 :
2020-06-30 22:04:22.959 3766-3766/? I/Bitmap: blog : 5224 , 2678 , 55959488
2020-06-30 22:04:22.960 3766-3766/? W/BitmapSizeReduce: getResizedBitmap inSampleSize=32
2020-06-30 22:04:22.980 3766-3766/? I/Bitmap: reduceSizeBitmap : 163 , 81 , 26406
分析結果 :
① 源圖像分析 : 從資源中加載 , 普通情況下寬度 5224 像素 , 高度 2678 像素 , ARGB_8888 像素格式 , 每個像素 字節 , 計算公式爲
② 縮小後的圖像分析 : 從資源中加載 , 普通情況下寬度 163 像素 , 高度 81 像素 , RGB_565 像素格式 , 每個像素 字節 , 計算公式爲