文章目錄
上一篇博客 【Android 內存優化】Android 工程中使用 libjpeg-turbo 壓縮圖片 ( JNI 傳遞 Bitmap | 獲取位圖信息 | 獲取圖像數據 | 圖像數據過濾 | 釋放資源 ) 介紹了從 Java 層傳入 Bitmap 對象到 JNI 層 , JNI 層獲取到了圖像對應的 RGB 像素數據 , 本篇博客中將獲取的圖像數據進行壓縮 , 存儲到 JPEG 格式圖片中 ;
一、使用 libjpeg-turbo 壓縮圖片流程
使用 libjpeg-turbo 壓縮圖片流程 :
① 初始化壓縮對象 : 初始化 JPEG 圖片壓縮對象 ;
② 打開文件 : 使用 Linux C API 打開壓縮圖片寫出文件 ;
③ 設置壓縮參數 : 設置圖片壓縮參數 , 如圖片寬高 , 像素格式 , 數據格式 , 質量等 ;
④ 開始壓縮 : 啓動壓縮 ;
⑤ 寫入壓縮數據 : 圖像數據逐行輸入 , 並壓縮 ;
⑥ 壓縮完畢 : 壓縮完畢後調用對應方法 ;
⑦ 釋放資源 : 文件資源 , 及壓縮相關的內存資源 , 需要釋放掉 ;
二、初始化 JPEG 壓縮對象
1. 初始化 JPEG 壓縮對象 :
① JPEG 壓縮對象概念 : jpeg_compress_struct 結構體和與其關聯的工作數據 , 該對象中存儲了 JPEG 壓縮參數 , 還包含了指向工作空間的指針 , JPEG 庫會在需要時分配該指針;
② 壓縮對象個數 : 該結構體可能會存在多個 , 每個結構體對象都表示了一個壓縮或解壓縮的工作;
2. 錯誤處理機制 :
① 錯誤處理程序 : jpeg_error_mgr 結構體表示錯誤處理程序 , 將其單獨定義成一個結構體 , 是因爲應用經常需要提供一個專門的錯誤處理程序;
② 處理處理機制 : 在這裏我們採用最簡單的方法 , 使用標準的錯誤處理程序 , 如果 壓縮失敗 , 在 stderr 上打印失敗信息, 並調用 exit() 退出程序 ;
③ 結構體生命週期 : 該結構體的生命週期必須與 jpeg_compress_struct 結構體的生命週期保持一致 , 以免產生野指針問題 ;
④ 錯誤處理設置時間 : 在所有操作之前 , 設置錯誤處理程序 , 爲了防止 JPEG 壓縮對象初始化時出錯, 越早設置錯誤處理程序越好 , 在內存不足時, 創建 jpeg_compress_struct 可能會失敗 ;
2. 代碼示例 :
/* 該對象中存儲了 JPEG 壓縮參數, 還包含了指向工作空間的指針, JPEG 庫會在需要時分配該指針;
* 該結構體可能會存在多個, 每個結構體對象都表示了一個壓縮或解壓縮的工作;
* JPEG 對象 : jpeg_compress_struct 結構體和與其關聯的工作數據
*/
struct jpeg_compress_struct cinfo;
/* 錯誤處理程序 : jpeg_error_mgr 結構體表示錯誤處理程序,
* 將其單獨定義成一個結構體, 是因爲應用經常需要提供一個專門的錯誤處理程序;
* 處理處理機制 : 在這裏我們採用最簡單的方法, 使用標準的錯誤處理程序,
* 如果壓縮失敗, 在 stderr 上打印失敗信息, 並調用 exit() 退出程序 ;
* 結構體聲明週期 : 該結構體的生命週期必須與 jpeg_compress_struct 結構體的生命週期保持一致,
* 以免產生野指針問題 ;
*/
struct jpeg_error_mgr jerr;
/* 爲了防止 JPEG 壓縮對象初始化時出錯, 這裏首先設置錯誤處理
* 在內存不足時, 創建 jpeg_compress_struct 可能會失敗
*/
cinfo.err = jpeg_std_error(&jerr);
// 初始化 JPEG 壓縮對象
jpeg_create_compress(&cinfo);
三、打開文件
1. 打開文件 : 使用 Linux C 中的文件操作 , 調用 fopen 函數打開文件 , 傳入兩個參數 , 文件路徑名稱 , 和 打開模式 , 打開模式中 “wb” , w 代表寫出數據 , b 代表二進制數據 , 該模式的函數以是寫出二進制數據 ;
2. 爲 JPEG 壓縮對象設置文件輸出 : 調用 jpeg_stdio_dest 函數 , 爲 JPEG 對象設置輸出文件 ; 調用該函數的調用者需要負責文件打開 , 和文件關閉操作 ;
EXTERN(void) jpeg_stdio_dest(j_compress_ptr cinfo, FILE *outfile);
3. 代碼示例 :
FILE *outfile;
if ((outfile = fopen(filename, "wb")) == NULL) {
fprintf(stderr, "can't open %s\n", filename);
exit(1);
}
// 設置文件輸出
jpeg_stdio_dest(&cinfo, outfile);
四、設置壓縮參數
1 . 設置默認參數 :
- 圖片寬度 : cinfo.image_width ; 像素寬度 ;
- 圖片高度 : cinfo.image_height ; 像素高度 ;
- 像素組件 : cinfo.input_components ; 單個像素 BGR 3個組件 ;
- 顏色空間 : cinfo.in_color_space ; 輸入圖像的顏色空間 ;
上述四個參數設置完畢後 , 調用 jpeg_set_defaults 方法 , 設置默認參數 ;
2 . 設置非默認參數 :
- 哈夫曼編碼 : cinfo.optimize_coding = TRUE;
- 編碼質量 : 調用 jpeg_set_quality 方法設置壓縮質量 ;
// 下面的四個參數是必須設置的參數
// 設置圖片的寬度
cinfo.image_width = imageWidth;
// 設置圖片的高度
cinfo.image_height = imageHeight;
// 設置每個像素的顏色組件, BGR 3個
cinfo.input_components = 3;
// 輸入圖像數據的顏色空間
cinfo.in_color_space = JCS_RGB;
// 設置默認的壓縮參數, 該操作是函數庫的常規步驟
// 設置該參數前需要設置 cinfo.in_color_space 輸入數據的顏色空間
jpeg_set_defaults(&cinfo);
// 打開哈夫曼編碼
cinfo.optimize_coding = TRUE;
// 設置非默認參數, 該方法設置質量
jpeg_set_quality(&cinfo, compressQuality, 1);
五、開始壓縮
1 . 開始壓縮 : 調用 jpeg_start_compress 方法 , 開始進行圖片壓縮工作 ;
2 . 函數原型 :
- j_compress_ptr cinfo 參數 : jpeg_compress_struct 結構指針 ;
- boolean write_all_tables 參數 : 設置 TRUE 參數, 表示將完整的圖片進行壓縮 ; 一般情況下都是設置 TRUE, 如果進行定製壓縮, 可以設置 FALSE ;
typedef struct jpeg_compress_struct *j_compress_ptr;
EXTERN(void) jpeg_start_compress(j_compress_ptr cinfo,
boolean write_all_tables);
3 . 代碼示例 :
// 4. 開始壓縮 JPEG 格式圖片, 設置 TRUE 參數, 表示將完整的圖片進行壓縮
// 一般情況下都是設置 TRUE, 如果進行定製壓縮, 可以設置 FALSE
jpeg_start_compress(&cinfo, TRUE);
六、循環寫入壓縮數據
1 . 寫入壓縮數據原理 : 使用函數庫的狀態變量, cinfo.next_scanline 作爲循環控制變量 , 這樣就可以不同自己實現循環控制 , 爲了保持代碼簡單, 每次傳遞一行圖像數據 ;
2 . 計算每行數據字節數 : 像素寬度乘以 , 表示每個像素點有 BGR 三個顏色值 , 每個顏色 字節 ;
int row_stride = imageWidth * 3;
3 . 計算每次循環拷貝的行數據首地址 : uint8_t *pixels = data + cinfo.next_scanline * row_stride 是計算過程 ;
- data 是圖像的起始位置
- row_stride 是每一行的字節數
- cinfo.next_scanline 是當前的行數
- 計算出來的 pixels 指針, 指向要寫出行的首地址
4 . 循環控制變量自增 : jpeg_write_scanlines(&cinfo, row, 1) , 調用 jpeg_write_scanlines 方法後, cinfo.next_scanline 自動加 1 ;
5 . 代碼示例 :
// 每一個行的數據個數
int row_stride = imageWidth * 3;
// 指向圖像數據中的某一行數據
JSAMPROW row[1];
while (cinfo.next_scanline < cinfo.image_height) {
/* 獲取一行圖像數據
* data 是圖像的起始位置
* row_stride 是每一行的字節數
* cinfo.next_scanline 是當前的行數
* 計算出來的 pixels 指針, 指向要寫出行的首地址
*/
uint8_t *pixels = data + cinfo.next_scanline * row_stride;
row[0] = pixels;
// 調用 jpeg_write_scanlines 方法後, cinfo.next_scanline 自動加 1
jpeg_write_scanlines(&cinfo, row, 1);
}
七、完成圖片壓縮及收尾
1 . 完成圖片壓縮及收尾 :
-
調用 jpeg_finish_compress 結束圖片壓縮過程 ;
-
調用 fclose 關閉之前 fopen 打開的文件 ;
-
調用 jpeg_destroy_compress 方法銷燬之前使用的 JPEG 壓縮對象 ;
2 . 代碼示例 :
// 6. 完成圖片壓縮
jpeg_finish_compress(&cinfo);
// 7. 釋放相關資源
fclose(outfile);
jpeg_destroy_compress(&cinfo);
八、libjpeg-turbo 圖片壓縮案例 ( 官方示例 )
在源碼 libjpeg-turbo-2.0.5/example.txt 文件中 , 有詳細的 JPEG 圖片壓縮流程 , 可以直接拷貝上述代碼進行使用 ;
九、libjpeg-turbo 壓縮圖片代碼示例
/**
* 壓縮 Jpeg 圖片
*
* 完整的帶詳細註釋的代碼示例參考源碼 libjpeg-turbo-2.0.5/example.txt 示例文件
* 裏面有詳細的定義圖片壓縮的過程
*
* @param data 要壓縮的圖片數據, 像素格式是 BGR
* @param imageWidth 輸出的 JPEG 圖片寬度
* @param imageHeight 輸出的 JPEG 圖片高度
* @param compressQuality 輸出的 JPEG 圖片質量
* @param filename 輸出文件路徑
*/
void compressJpegFile(uint8_t *data, int imageWidth, int imageHeight,
jint compressQuality, const char *filename) {
// 1. 爲 JPEG 圖片壓縮對象, 分配內存空間
/* 該對象中存儲了 JPEG 壓縮參數, 還包含了指向工作空間的指針, JPEG 庫會在需要時分配該指針;
* 該結構體可能會存在多個, 每個結構體對象都表示了一個壓縮或解壓縮的工作;
* JPEG 對象 : jpeg_compress_struct 結構體和與其關聯的工作數據
*/
struct jpeg_compress_struct cinfo;
/* 錯誤處理程序 : jpeg_error_mgr 結構體表示錯誤處理程序,
* 將其單獨定義成一個結構體, 是因爲應用經常需要提供一個專門的錯誤處理程序;
* 處理處理機制 : 在這裏我們採用最簡單的方法, 使用標準的錯誤處理程序,
* 如果壓縮失敗, 在 stderr 上打印失敗信息, 並調用 exit() 退出程序 ;
* 結構體聲明週期 : 該結構體的生命週期必須與 jpeg_compress_struct 結構體的生命週期保持一致,
* 以免產生野指針問題 ;
*/
struct jpeg_error_mgr jerr;
/* 爲了防止 JPEG 壓縮對象初始化時出錯, 這裏首先設置錯誤處理
* 在內存不足時, 創建 jpeg_compress_struct 可能會失敗
*/
cinfo.err = jpeg_std_error(&jerr);
// 初始化 JPEG 壓縮對象
jpeg_create_compress(&cinfo);
// 2. 打開文件, 準備向文件寫出二進制數據
// w 代表寫出數據, b 代表二進制數據
FILE *outfile;
if ((outfile = fopen(filename, "wb")) == NULL) {
fprintf(stderr, "can't open %s\n", filename);
exit(1);
}
// 設置文件輸出
jpeg_stdio_dest(&cinfo, outfile);
// 3. 設置壓縮參數
// 下面的四個參數是必須設置的參數
// 設置圖片的寬度
cinfo.image_width = imageWidth;
// 設置圖片的高度
cinfo.image_height = imageHeight;
// 設置每個像素的顏色組件, BGR 3個
cinfo.input_components = 3;
// 輸入圖像數據的顏色空間
cinfo.in_color_space = JCS_RGB;
// 設置默認的壓縮參數, 該操作是函數庫的常規步驟
// 設置該參數前需要設置 cinfo.in_color_space 輸入數據的顏色空間
jpeg_set_defaults(&cinfo);
// 打開哈夫曼編碼
cinfo.optimize_coding = TRUE;
// 設置非默認參數, 該方法設置質量
jpeg_set_quality(&cinfo, compressQuality, 1);
// 4. 開始壓縮 JPEG 格式圖片, 設置 TRUE 參數, 表示將完整的圖片進行壓縮
// 一般情況下都是設置 TRUE, 如果進行定製壓縮, 可以設置 FALSE
jpeg_start_compress(&cinfo, TRUE);
// 5. 循環寫入數據
/* 循環原理 : 使用函數庫的狀態變量, cinfo.next_scanline 作爲循環控制變量
* 這樣就可以不同自己實現循環控制
* 爲了保持簡單, 每次傳遞一行圖像數據
*/
// 每一個行的數據個數
int row_stride = imageWidth * 3;
// 指向圖像數據中的某一行數據
JSAMPROW row[1];
while (cinfo.next_scanline < cinfo.image_height) {
/* 獲取一行圖像數據
* data 是圖像的起始位置
* row_stride 是每一行的字節數
* cinfo.next_scanline 是當前的行數
* 計算出來的 pixels 指針, 指向要寫出行的首地址
*/
uint8_t *pixels = data + cinfo.next_scanline * row_stride;
row[0] = pixels;
// 調用 jpeg_write_scanlines 方法後, cinfo.next_scanline 自動加 1
jpeg_write_scanlines(&cinfo, row, 1);
}
// 6. 完成圖片壓縮
jpeg_finish_compress(&cinfo);
// 7. 釋放相關資源
fclose(outfile);
jpeg_destroy_compress(&cinfo);
}