JNI實現(xiàn)圖片壓縮

項目鏈接:https://github.com/zengfw/EffectiveBitmap
直接使用項目或直接復制libs中的so庫到項目中即可(當前只構(gòu)建了armeabi),需要其他ABI可檢下項目另外使用CMake構(gòu)建即可。

結(jié)果預覽:


效果圖.png

jni_278KB.png
quality_484KB.png
sample_199KB.png
size_238KB.png

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

為什么iPhone手機圖片的質(zhì)量比Android的好?

首先了解兩個圖像處理庫:libjpeg、Skia。

Skia:圖像處理引擎,Google在Android系統(tǒng)上就是采用Skia,它是基于libjpeg的二次封裝,Google在很多其它產(chǎn)品也使用了這個庫,比如Chorme,F(xiàn)irefox等等。

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:
  • 參數(shù)為TRUE時,圖片壓縮算法使用最優(yōu)的哈夫曼編碼表,它需要額外傳遞數(shù)據(jù),因此會耗費CPU運算時間,以及開辟很多臨時內(nèi)存空間。
  • 參數(shù)為FALSE時,使用默認的哈夫曼編碼表。在大多數(shù)情況,使用最優(yōu)哈夫曼編碼表相比默認哈夫曼編碼表,能節(jié)省圖像文件很大比例的大小。

為什么使用最優(yōu)哈夫曼編碼表可以節(jié)省圖像文件很大的比例大小呢?

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

其次了解下libjpeg使用哈夫曼編碼是對圖片上的每個像素(ARGB)進行編碼,比如
ARGB(每個顏色通道取值范圍0-255)的編碼分別是:
A:001
R:010
G:011
B:100
如果采用定長,那一個圖片下來一個人像素就是001010011100...
如果我們使用可變長編碼方式,遍歷、再嵌套遍歷,再嵌套遍歷每一個像素來獲取前綴編碼(沒錯,這個運算過程很大,而且臨時變量內(nèi)存也需要很大,但相對于今天的手機CPU來說,so easy),我們大致可以得到這樣的編碼:
A:01
R:10
G:11
B:100
那一個圖片下來一個人像素就是011011100...

編碼長度的優(yōu)化后,接下來干的事就是計算權(quán)重以及每個顏色通道對應的編碼的出現(xiàn)頻次構(gòu)建哈夫曼樹了,這里就參考博客里頭的圖片了哈。


哈夫曼樹.png

看完博客之后,可以知道最優(yōu)哈夫曼編碼其實是使用了可變長編碼方式,而默認的哈夫曼編碼使用了定長編碼方式,因此需要更多的存儲空間,呈現(xiàn)出來的手機圖片自然會大很大。

libjpeg把optimize_coding參數(shù)默認設置為FALSE是因為10多年前的Android手機CPU跟內(nèi)存都非常吃緊,所以當年沒有設置為TRUE。如今的手機CPU跟內(nèi)存都“起飛了”,Goolge的Skia圖像處理引擎卻還是使用optimize_coding的默認值FALSE。我們無法修改系統(tǒng)Skia的這個參數(shù)值,所以只能默默忍受size很大的圖像文件。

經(jīng)過大量圖像壓縮測試結(jié)果,得到兩個結(jié)論:

1.圖片壓縮到相同的質(zhì)量,F(xiàn)ALSE所產(chǎn)出的圖像文件大小是TRUE的5-10倍。
2.圖片壓縮到相同的質(zhì)量,Android所產(chǎn)出的圖像文件大小比iOS也是大5-10倍。

所以,通過使用libjpeg編譯自己的native library修改optimize_coding參數(shù)的值,達圖像質(zhì)量相同,所產(chǎn)出的圖像卻能節(jié)省5-10倍空間大小的效果。

實現(xiàn)的步驟:

1.構(gòu)建libjpeg的so庫
到官方下載對應自己電腦系統(tǒng)類型的壓縮包,創(chuàng)建Android項目導入壓縮包里頭的xx.h、xx.c文件構(gòu)建so庫。bither/bither-android-lib已經(jīng)做了這個工作,因此我們只需直接拿他的libjpegbither.so即可。

2.導入libjpeg的聲明頭文件,因為步驟1的libjpegbither.so是對這些頭文件的實現(xiàn),因此需要導入這些頭文件。

3.創(chuàng)建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關聯(lián)CMakeLists.txt構(gòu)建腳本,以及指定產(chǎn)出的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的結(jié)構(gòu)體,保存的比如寬、高、位深、圖片格式等信息
    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;
    }
    // 設置結(jié)構(gòu)體的文件路徑
    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.構(gòu)建so庫

源碼:https://github.com/zengfw/EffectiveBitmap

參考鏈接:
Why the image quality of iPhone is much better than Android?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容