JNI實現圖片壓縮

Ricky_發表於2018-04-15

原文連結:blog.csdn.net/u014011112/…

專案連結:github.com/zengfw/Effe…

直接使用專案或直接複製libs中的so庫到專案中即可(當前只構建了armeabi),需要其他ABI可檢下專案另外使用CMake構建即可。

結果預覽:

效果圖.png

jni_278KB.png

jni_278KB.png

quality_484KB.png

quality_484KB.png

sample_199KB.png

sample_199KB.png

size_238KB.png

size_238KB.png

原圖大小5.99M~~ 我們把所有經過壓縮的圖片放到同等大小的情況後,很明顯,取樣壓縮跟尺寸壓縮都不是我們想要的結果,而質量壓縮跟JNI壓縮我設定的質量壓縮值都是30,JNI壓縮出來只有278KB,直接質量壓縮出來的有484KB,綜合之後,JNI才是綜合最優的方式,當然,如果只是頭像,我們設定可以把配置值設定得更小,圖片就更小。

為什麼iPhone手機圖片的質量比Android的好?

首先了解兩個影象處理庫:libjpegSkia

Skia:影象處理引擎,Google在Android系統上就是採用Skia,它是基於libjpeg的二次封裝,Google在很多其它產品也使用了這個庫,比如Chorme,Firefox等等。

libjpeg:早期的影象處理引擎,用於PC端。

官方文件可以看到libjpeg.doc這樣一段話:

boolean optimize_coding
        TRUE causes the compressor to compute optimal Huffman coding tables
        for the image.  This requires an extra pass over the data and
        therefore costs a good deal of space and time.  The default is
        FALSE, which tells the compressor to use the supplied or default
        Huffman tables.  In most cases optimal tables save only a few percent
        of file size compared to the default tables.  Note that when this is
        TRUE, you need not supply Huffman tables at all, and any you do
        supply will be overwritten.
複製程式碼

boolean optimize_coding:

  • 引數為TRUE時,圖片壓縮演算法使用最優的哈夫曼編碼表,它需要額外傳遞資料,因此會耗費CPU運算時間,以及開闢很多臨時記憶體空間。
  • 引數為FALSE時,使用預設的哈夫曼編碼表。在大多數情況,使用最優哈夫曼編碼表相比預設哈夫曼編碼表,能節省影象檔案很大比例的大小。

為什麼使用最優哈夫曼編碼表可以節省影象檔案很大的比例大小呢?

可以先了解下什麼是哈夫曼樹和哈夫曼編碼

看完部落格之後,可以知道最優哈夫曼編碼其實是使用了可變長編碼方式,而預設的哈夫曼編碼使用了定長編碼方式,因此需要更多的儲存空間,呈現出來的手機圖片自然會大很大。

libjpeg把optimize_coding引數預設設定為FALSE是因為10多年前的Android手機CPU跟記憶體都非常吃緊,所以當年沒有設定為TRUE。如今的手機CPU跟記憶體都“起飛了”,Goolge的Skia影象處理引擎卻還是使用optimize_coding的預設值FALSE。我們無法修改系統Skia的這個引數值,所以只能默默忍受size很大的影象檔案。

經過大量影象壓縮測試結果,得到兩個結論:

1.圖片壓縮到相同的質量,FALSE所產出的影象檔案大小是TRUE的5-10倍。 2.圖片壓縮到相同的質量,Android所產出的影象檔案大小比iOS也是大5-10倍。

所以,通過使用libjpeg編譯自己的native library修改optimize_coding引數的值,達影象質量相同,所產出的影象卻能節省5-10倍空間大小的效果。

實現的步驟:

1.構建libjpeg的so庫 到官方下載對應自己電腦系統型別的壓縮包,建立Android專案匯入壓縮包裡頭的xx.h、xx.c檔案構建so庫。bither/bither-android-lib已經做了這個工作,因此我們只需直接拿他的libjpegbither.so即可。

2.匯入libjpeg的宣告標頭檔案,因為步驟1的libjpegbither.so是對這些標頭檔案的實現,因此需要匯入這些標頭檔案。

3.建立CMake指令碼

cmake_minimum_required(VERSION 3.4.1)

add_library( effective-bitmap
             SHARED
             src/main/cpp/effective-bitmap.c )


include_directories( src/main/cpp/jpeg/
                     )

add_library(jpegbither SHARED IMPORTED)
set_target_properties(jpegbither
  PROPERTIES IMPORTED_LOCATION
  ${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libjpegbither.so)


find_library( log-lib
              log )

find_library( jnigraphics-lib jnigraphics )

target_link_libraries( effective-bitmap
                       jpegbither
                       ${log-lib}
                       ${jnigraphics-lib})
複製程式碼

4.配置gradle關聯CMakeLists.txt構建指令碼,以及指定產出的ABI的型別

android {
    // ...
    defaultConfig {
        // ...
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }

        ndk {
            abiFilters 'armeabi'
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}
複製程式碼

5.核心C/C++程式碼

int generateJPEG(BYTE* data, int w, int h, int quality,
                 const char* outfilename, jboolean optimize) {
    int nComponent = 3;
    // jpeg的結構體,儲存的比如寬、高、位深、圖片格式等資訊
    struct jpeg_compress_struct jcs;

    struct my_error_mgr jem;

    jcs.err = jpeg_std_error(&jem.pub);
    jem.pub.error_exit = my_error_exit;
    if (setjmp(jem.setjmp_buffer)) {
        return 0;
    }
    jpeg_create_compress(&jcs);
    // 開啟輸出檔案 wb:可寫byte
    FILE* f = fopen(outfilename, "wb");
    if (f == NULL) {
        return 0;
    }
    // 設定結構體的檔案路徑
    jpeg_stdio_dest(&jcs, f);
    jcs.image_width = w;
    jcs.image_height = h;

    // 設定哈夫曼編碼
    jcs.arith_code = false;
    jcs.input_components = nComponent;
    if (nComponent == 1)
        jcs.in_color_space = JCS_GRAYSCALE;
    else
        jcs.in_color_space = JCS_RGB;

    jpeg_set_defaults(&jcs);
    jcs.optimize_coding = optimize;
    jpeg_set_quality(&jcs, quality, true);
    // 開始壓縮,寫入全部畫素
    jpeg_start_compress(&jcs, TRUE);

    JSAMPROW row_pointer[1];
    int row_stride;
    row_stride = jcs.image_width * nComponent;
    while (jcs.next_scanline < jcs.image_height) {
        row_pointer[0] = &data[jcs.next_scanline * row_stride];
        jpeg_write_scanlines(&jcs, row_pointer, 1);
    }

    jpeg_finish_compress(&jcs);
    jpeg_destroy_compress(&jcs);
    fclose(f);

    return 1;
}
複製程式碼

6.構建so庫

原始碼:github.com/zengfw/Effe…

參考連結: Why the image quality of iPhone is much better than Android?

相關文章