用ndk實現android的圖片壓縮

最近在看了動腦學院的圖片優化之後,感覺通過ndk來實現的壓縮,壓縮比例還是很好的

我們知道android的圖片顯示離不開bitmap,而在android開發中避免不了對圖片的壓縮處理,當然bitmap有自帶的壓縮方法,調用bitmap的compress方法就可以實現簡單的壓縮,但是這種壓縮出來的圖片從清晰度上來說,效果不是很好,原因在於這種壓縮的底層處理是由google的skia圖片處理引擎來做的,而skia庫又是基於jpeg庫,我們都知道libjpeg是一個被廣泛使用的JPEG壓縮/解壓縮函數庫,應用程序可以每次從JPEG壓縮圖像中讀取一個或多個掃描線(scanline,所謂掃描線,是指由一行像素點構成的一條圖像線條),而諸如顏色空間轉換、降採樣/增採樣、顏色量化之類的工作則都由libjpeg去完成的;但是skia在開發的時候閹割來jpeg的一部分功能,使得android原有的壓縮方法不盡人意,下面通過代碼來實現一個脫離android 底層的skia庫,基於ndk libjpeg壓縮的圖片壓縮方案。

導入相關的依賴庫

由於要使用到libjpeg相關的函數,所以要導入相關的頭文件與so文件, 由於libjpeg也是在android源碼裏面的,找到android源碼中的/external/libjpeg-turbo/jpeglib.h頭文件複製到android studio 的cpp目錄下面,當然也可以在cpp下面新建一個include目錄將頭文件放進去也行,然後將jpeglib.h裏面依賴的其他頭文件都引入進來,接着就是進入libjpeg.so文件,我這裏是將so放在來app目錄下的libs目錄下面
頭文件與so文件目錄
當然這個目錄不是必須的,放在其他目錄也行;然後用cmake將頭文件與目錄的路徑引入就行了;

cmake的配置

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

include_directories( src/main/cpp/ )

add_library(jpeg-and SHARED IMPORTED)
set_target_properties(jpeg-and
  PROPERTIES IMPORTED_LOCATION
  ${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libjpeg.so)

find_library(
              log-lib
              log )

#添加jnigraphics,c++代碼中使用到了AndroidBitmap,所以需要添加jnigrapics庫
find_library(
              andbitmap
              jnigraphics )

add_library(
             native-lib
             SHARED
             src/main/cpp/native-lib.cpp )

target_link_libraries( # Specifies the target library.
                       native-lib
                       ${andbitmap}
                       jpeg-and
                       ${log-lib} )

實現圖片壓縮

這裏我們在java層聲明一個native方法

    public native void nativeCompress(Bitmap inputBitmap, String absolutePath);

該方法傳入原圖的bitmap與輸出壓縮文件的路徑,然後在jni層實現壓縮代碼

#include <jni.h>
#include <string>
#include <android/bitmap.h>
#include <android/log.h>
#include <malloc.h>

extern "C" {
    #include "jpeglib.h"
}

typedef uint8_t BYTE;

void writeImg(BYTE *data, const char *path, int w, int h) {
    //壓縮

    //-------------初始化----------------
    struct jpeg_compress_struct jpeg_struct;

    struct jpeg_error_mgr err;
    jpeg_create_compress(&jpeg_struct);

    jpeg_struct.err = jpeg_std_error(&err);

    FILE *file = fopen(path, "wb");
    //設置輸出路徑
    jpeg_stdio_dest(&jpeg_struct, file);

    //設置寬高
    jpeg_struct.image_height = h;
    jpeg_struct.image_width = w;


    //重要的地方,skia閹割的地方
    jpeg_struct.arith_code = FALSE;
    jpeg_struct.optimize_coding = true;
    jpeg_struct.in_color_space = JCS_RGB;

    jpeg_struct.input_components = 3;
    //設置壓縮質量

    jpeg_set_defaults(&jpeg_struct);
    jpeg_set_quality(&jpeg_struct, 20, TRUE);

    //-------------開始壓縮----------------
    jpeg_start_compress(&jpeg_struct, TRUE);

    //-------------寫入數據----------------

    //記錄一行的首地址
    JSAMPROW row_pointer[1];
    //一行的rgb數量
    int row_stride = jpeg_struct.image_width * 3;

    while (jpeg_struct.next_scanline < jpeg_struct.image_height){

        row_pointer[0] = &data[jpeg_struct.next_scanline * row_stride];
        jpeg_write_scanlines(&jpeg_struct, row_pointer, 1);
    }

    jpeg_finish_compress(&jpeg_struct);
    jpeg_destroy_compress(&jpeg_struct);
    fclose(file);

}

extern "C"
JNIEXPORT void JNICALL
Java_com_kangxin_doctor_bitmaphufdemo_MainActivity_nativeCompress(JNIEnv *env, jobject instance,
                                                                  jobject inputBitmap,
                                                                  jstring absolutePath_) {
    const char *absolutePath = env->GetStringUTFChars(absolutePath_, 0);

//壓縮所需要的原材料->bitmap 像素數組  rgb數組

    BYTE *pixels;

    AndroidBitmapInfo bitmapInfo;
    AndroidBitmap_getInfo(env, inputBitmap, &bitmapInfo);
    //將bitmap轉換成pixels的數組
    AndroidBitmap_lockPixels(env, inputBitmap, reinterpret_cast<void **>(&pixels));

    int h = bitmapInfo.height;
    int w = bitmapInfo.width;

    int i = 0;
    int j = 0;

    int color;

    BYTE *data = NULL, *tmpData = NULL;

    data = static_cast<BYTE *>(malloc(w * h * 3));
    tmpData = data;

    BYTE r, g, b;

    for (int i = 0; i < h; ++i) {
        for (int j = 0; j < w; ++j) {
            color = *((int *)pixels);
            r = ((color & 0x00FF0000) >> 16);
            g = ((color & 0x0000FF00) >> 8);
            b = (color & 0x000000FF);


            *data = b;
            *(data + 1) = g;
            *(data + 2) = r;

            data += 3;
            pixels += 4;


        }
    }


    AndroidBitmap_unlockPixels(env, inputBitmap);

    //jpeg 壓縮
    writeImg(tmpData, absolutePath, w, h);

    env->ReleaseStringUTFChars(absolutePath_, absolutePath);
}

首先我們通過bitmap函數將bitmap轉換成了rgb像素數組,然後通過循環位移計算將每個像素裏面的rgb取出來,以bgr的順序存到了data容器裏面,然後調用writeImg函數進行壓縮,在這個函數中,一開始就進行了壓縮的一些處理話操作,在這裏要注意的是 jpeg_struct.arith_code = FALSE;

這個很重要,這個字段在頭文件裏面是這樣描述的boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */
由此可見這個字段應該是設置圖片壓縮的編碼方式,TRUE的話是arithmetic coding編碼,FALSE的話是Huffman 而Huffman就是著名的哈夫曼編碼,Huffman編碼用的最普遍的就是在壓縮領域,它的壓縮效果相當的好;而android 底層的skia庫將這個字段設置成了TRUE 也就是說skia的壓縮不是用的哈夫曼編碼壓縮,所以這也就是android原生壓縮出來的圖片不盡人意的原因所在。

下面就是我經過測試壓縮出來的原圖與壓縮圖的對比

原圖-大小397kb

壓縮圖

經過對比,原圖大小是397kb, 壓縮之後的圖片大小是58kb

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章