【Android 內存優化】Android 工程中使用 libjpeg-turbo 壓縮圖片 ( 初始化壓縮對象 | 打開文件 | 設置壓縮參數 | 寫入壓縮圖像數據 | 完成壓縮 | 釋放資源 )



上一篇博客 【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 . 計算每行數據字節數 : 像素寬度乘以 33 , 33 表示每個像素點有 BGR 三個顏色值 , 每個顏色 11 字節 ;

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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章