文章目錄
在上一篇博客 【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.h
AndroidBitmapInfo info;
// 從 bitmap 中獲得信息位圖信息 AndroidBitmapInfo
AndroidBitmap_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;
// 注意該獲取的信息中包含透明度信息, 像素格式是 ARGB
AndroidBitmap_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 中內存存儲序列是 BGRA
blue = *( 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.h
AndroidBitmapInfo info;
// 從 bitmap 中獲得信息位圖信息 AndroidBitmapInfo
AndroidBitmap_getInfo(env, jbitmap, &info);
// 該類型最終類型是 unsigned char, 相當於 Java 中的 byte
// 這是個 byte 指針, 指向一個數組
// 此處作爲返回值使用
uint8_t *addrPtr;
// 注意該獲取的信息中包含透明度信息, 像素格式是 ARGB
AndroidBitmap_lockPixels(env, jbitmap, (void **) &addrPtr);
// 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) {
// 在 Bitmap 中內存存儲序列是 BGRA
blue = *( 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);
}