【Android 内存优化】Android 工程中使用 libjpeg-turbo 压缩图片 ( JNI 传递 Bitmap | 获取位图信息 | 获取图像数据 | 图像数据过滤 | 释放资源 )
文章目錄
- 一、Bitmap 圖像數據處理
- 二、Java 層 Bitmap 對象轉為 JNI 層 bitmap 對象
- 三、獲取 bitmap 中的圖像數據
- 四、過濾 bitmap 中的圖像數據 ( 獲取 RGB 數據 剔除 A 通道數據 )
- 五、釋放資源
- 六、Bitmap 圖像數據處理
在上一篇博客 【Android 內存優化】libjpeg-turbo 函數庫交叉編譯與使用 ( 交叉編譯腳本編寫 | 函數庫頭文件拷貝 | 構建腳本配置 | Android Studio 測試函數庫 ) 中 對 libjpeg-turbo 函數庫進行了交叉編譯 , 拷貝了相應的頭文件和靜態庫到 Android Studio 項目中 , 并配置了 CMakeList.txt 構建腳本 , 和 build.gradle 構建腳本 , 本篇博客中開始進行代碼編寫 ;
一、Bitmap 圖像數據處理
Bitmap 圖像數據處理 :
① 獲取 Bitmap 圖像對象 : Java 傳遞到 JNI 層的是 jobject 對象 , 需要將其轉為 JNI 中的 bitmap 對象 ;
② 數據提取 : 從 bitmap 圖像中提取 RGB 像素值 , 也就是剔除 ALPHA 通道 ( 透明度 ) 的數據 ;
③ 使用 libjpeg-turbo 壓縮圖片 : 調用 libjpeg-turbo 函數庫 , 對上述提取的圖片 RGB 像素數據進行壓縮 ;
④ 釋放資源 : 圖片壓縮完畢后 , 釋放相關資源 ;
二、Java 層 Bitmap 對象轉為 JNI 層 bitmap 對象
1. Bitmap 信息 : 在 AndroidBitmapInfo 結構體中 , 封裝了圖像寬度 , 圖像高度 , 像素格式等信息 ;
/** Bitmap info, see AndroidBitmap_getInfo(). */ typedef struct {/** 圖像像素寬度. */uint32_t width;/** 圖像像素高度. */uint32_t height;/** 每行字節數. */uint32_t stride;/** 像素格式. See {@link AndroidBitmapFormat} */int32_t format;/** 保留位. */uint32_t flags; // 0 for now } AndroidBitmapInfo;2. 獲取 Bitmap 信息 : 調用 bitmap.h 中的 AndroidBitmap_getInfo 方法 , 可以從 jbitmap 中獲取對應的信息 ;
int AndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap,AndroidBitmapInfo* info);3. 代碼示例 :
// 聲明 位圖信息, 該變量作為返回值使用// 引用自 bitmap.hAndroidBitmapInfo info;// 從 bitmap 中獲得信息位圖信息 AndroidBitmapInfoAndroidBitmap_getInfo(env, jbitmap, &info);三、獲取 bitmap 中的圖像數據
調用 AndroidBitmap_lockPixels 方法 , 即可從 Java 的 Bitmap 對象中獲取數據的首地址 ; 向該函數中傳入一個二維指針 , 該二維指針參數作為返回值使用 , 該二維指針最終指向的內存就是圖像數據內存 ;
1. AndroidBitmap_lockPixels 函數作用 : 從給定 Java Bitmap 對象中 , 獲取其對應的像素數據地址 ; 鎖定可以保證像素數據內存是固定不變的 , 直到調用解除鎖定方法 , 清除相關數據 ; 該方法必須與 AndroidBitmap_unlockPixels 方法成對使用 , 之后 addrPtr 地址不應該再被使用到 ; 如果執行成功 , *addrPtr 會指向圖像像素數據的首地址 , 如果方法失敗 , 那么該二維指針是無效的指針 ;
2. AndroidBitmap_lockPixels 函數原型 :
① JNIEnv* env 參數 : 注意這里用到了 JNIEnv* env 參數 , 主線程調用可以直接使用, 子線程調用的話 , 需要使用 JavaVM 調用 AttachCurrentThread 方法 , 傳入 JNIEnv 指針 , 然后該 JNIEnv 就是線程對應的 JNI 環境 , 使用完畢后解除綁定 ;
參考 【Android NDK 開發】JNI 線程 ( JNI 線程創建 | 線程執行函數 | 非 JNI 方法獲取 JNIEnv 與 Java 對象 | 線程獲取 JNIEnv | 全局變量設置 ) 博客 ;
② jobject jbitmap 參數 : Java 中的 Bitmap 對象 ;
③ void** addrPtr 參數 : 二維指針 , 執行成功后指向圖像像素數據的首地址 , 同時用于當做返回值 , 讓用戶可以調用到像素數據 ;
int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr);3. 代碼示例 :
// 該類型最終類型是 unsigned char, 相當于 Java 中的 byte// 這是個 byte 指針, 指向一個數組// 此處作為返回值使用uint8_t *addrPtr;// 注意該獲取的信息中包含透明度信息, 像素格式是 ARGBAndroidBitmap_lockPixels(env, jbitmap, (void **) &addrPtr);四、過濾 bitmap 中的圖像數據 ( 獲取 RGB 數據 剔除 A 通道數據 )
1. 數據過濾需求 : 之前已經獲取到了圖像數據 , 存儲在了 addrPtr 指針中 , 現在需要將 RGB 數據取出, 剔除 ALPHA 透明度通道數據 , 只保留 RGB 通道數據 ;
2. 兩塊內存 : uint8_t* addrPtr 指針指向的內存是源數據 , uint8_t* data 指針指向的的數據是目標數據 , 最終要壓縮的數據是 data 目標數據 ;
3. 像素格式 : 源數據中存儲的 BGRA 像素格式的數據 , 目標數據中存儲的是 BGR 像素格式的數據 ;
4. 循環像素數據 : 開啟循環 , 直接循環遍歷每個像素點 , 注意像素點存放格式是 BGRA , 然后將數據存儲到另一塊內存中 , 存儲順序是 BGR ; 注意每次循環后 , 都需要移動對應的指針 ;
// JPEG 格式的圖片, 沒有透明度信息, 像素格式是 RGB// 這里需要去掉透明度信息// 獲取圖片的像素寬度int width = info.width;// 獲取圖片的像素高度int height = info.height;// 存儲 RGB 數據uint8_t* data = (uint8_t *) malloc(width * height * 3);// data 指針在后續發生了移動, 這里保存下初始的指針, 方便在之后釋放該指針uint8_t* temp = data;// JPEG 像素中的 RGB 三原色, 紅綠藍uint8_t red, green, blue;// 遍歷從 Bitmap 內存 addrPtr 中讀取 BGRA 數據, 然后向 data 內存存儲 BGR 數據中for(int i = 0; i < height; i++){for (int j = 0; j < width; ++j) {// 處理 i 行 j 列像素點信息 // 在 Bitmap 中內存存儲序列是 BGRAblue = *( addrPtr );green = *( addrPtr + 1 );red = *( addrPtr + 2 );// libturbojpeg 中 JPEG 圖像內存排列格式是 BGR*data = blue;*( data + 1 ) = green;*( data + 2 ) = red;// 移動 data 指針data += 3;//移動 addrPtr 指針, 為下一次讀取數據做準備addrPtr +=4;}}// 截止到此處, 已經讀取出 JPEG 圖片所需的數據, 在 data 指針中五、釋放資源
之前還有個步驟是壓縮 jpeg 格式圖片 , 這個過程比較復雜單開一個博客講解 , 該章節講解壓縮完畢后的內存釋放操作 ;
1. 鎖定 / 解鎖 像素數據 : AndroidBitmap_unlockPixels 方法與 AndroidBitmap_lockPixels 方法成對使用 , 表示之后不再需要使用 Bitmap 對象的數據了 ;
2. 釋放壓縮數據 : 釋放掉存儲要壓縮的 JPEG 圖片 RGB 數據的內存 , 此時已經壓完畢 , 可以將之前申請的內存都釋放掉了 ; 注意之前申請的 data 指針 , 在拷貝數據過程中 , 將該指針移動過了 , 不能釋放 data 指針 , 只能釋放之前 data 內存申請后的備份指針 , 否則報錯 ;
3. 釋放字符串 : env->GetStringUTFChars 創建的字符串是局部引用 , 這里需要釋放掉 , 及時回收內存是個好習慣 ;
// 解鎖AndroidBitmap_unlockPixels(env,jbitmap);// 注意要釋放 temp 指針 , 不要釋放成 data 指針, 否則會出錯free(temp);// 釋放局部引用, 不釋放, GC 也會回收, 但是有延遲env->ReleaseStringUTFChars(path, filePath);六、Bitmap 圖像數據處理
GitHub 項目地址 : han1202012/PictureCompression
libjpeg-turbo 壓縮 JPEG 代碼示例 :
#include <jni.h> #include <string> #include <jpeglib.h> #include <android/bitmap.h> #include <malloc.h> #include <android/log.h> #include <bitset> #include <iosfwd>// 聲明函數 void compressJpegFile(uint8_t *data, int imageWidth, int imageHeight,jint compressQuality, const char *filename);extern "C" JNIEXPORT jstring JNICALL Java_kim_hsl_pc_MainActivity_stringFromJNI(JNIEnv* env,jobject /* this */) {std::string hello = "Hello from C++";// 測試 libturbojpeg.a 函數庫jpeg_compress_struct jcs;__android_log_print(ANDROID_LOG_INFO, "JPEG", "jpeg_compress_struct jcs = %d", jcs.image_width);hello = hello + " , jpeg_compress_struct jcs = " + std::to_string(jcs.image_width);return env->NewStringUTF(hello.c_str()); }/*** 圖片壓縮方法*/ extern "C" JNIEXPORT void JNICALL Java_kim_hsl_pc_MainActivity_native_1pictureCompress(JNIEnv *env, jobject thiz,jobject jbitmap,jint quality, jstring path) {// 將 Java 字符串轉為 C 字符串, 注意這是局部引用const char *filePath = env->GetStringUTFChars(path, 0);// 聲明 位圖信息, 該變量作為返回值使用// 引用自 bitmap.hAndroidBitmapInfo info;// 從 bitmap 中獲得信息位圖信息 AndroidBitmapInfoAndroidBitmap_getInfo(env, jbitmap, &info);// 該類型最終類型是 unsigned char, 相當于 Java 中的 byte// 這是個 byte 指針, 指向一個數組// 此處作為返回值使用uint8_t *addrPtr;// 注意該獲取的信息中包含透明度信息, 像素格式是 ARGBAndroidBitmap_lockPixels(env, jbitmap, (void **) &addrPtr);// JPEG 格式的圖片, 沒有透明度信息, 像素格式是 RGB// 這里需要去掉透明度信息// 獲取圖片的像素寬度int width = info.width;// 獲取圖片的像素高度int height = info.height;//rgbuint8_t* data = (uint8_t *) malloc(width * height * 3);// data 指針在后續發生了移動, 這里保存下初始的指針, 方便在之后釋放該指針uint8_t* temp = data;// JPEG 像素中的 RGB 三原色, 紅綠藍uint8_t red, green, blue;// 遍歷從 Bitmap 內存 addrPtr 中讀取 BGRA 數據, 然后向 data 內存存儲 BGR 數據中for(int i = 0; i < height; i++){for (int j = 0; j < width; ++j) {// 在 Bitmap 中內存存儲序列是 BGRAblue = *( addrPtr );green = *( addrPtr + 1 );red = *( addrPtr + 2 );// libturbojpeg 中 JPEG 圖像內存排列格式是 BGR*data = blue;*( data + 1 ) = green;*( data + 2 ) = red;// 移動 data 指針data += 3;//移動 addrPtr 指針, 為下一次讀取數據做準備addrPtr +=4;}}// 截止到此處, 已經讀取出 JPEG 圖片所需的數據, 在 data 指針中// 將 data 指針中的數據壓縮到 JPEG 格式圖片中compressJpegFile(temp, width, height, quality, filePath);// 解鎖AndroidBitmap_unlockPixels(env,jbitmap);// 注意要釋放 temp 指針 , 不要釋放成 data 指針, 否則會出錯free(temp);// 釋放局部引用, 不釋放, GC 也會回收, 但是有延遲env->ReleaseStringUTFChars(path, filePath); }總結
以上是生活随笔為你收集整理的【Android 内存优化】Android 工程中使用 libjpeg-turbo 压缩图片 ( JNI 传递 Bitmap | 获取位图信息 | 获取图像数据 | 图像数据过滤 | 释放资源 )的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Android 内存优化】libjpe
- 下一篇: 【Android 内存优化】Androi