項目開發過程當中發現Android的質量壓縮算法在圖片過大,色彩豐富的前提下,壓縮的性能不是特別好,通過調查發現Android底層實現使用Skia引擎,封裝了了libjpeg圖像庫。爲了適配低版本的Android手機,其內部的壓縮算法並無採用廣泛的哈夫曼算法,由於哈夫曼算法比較佔CPU,從而選擇了其餘的算法B,而算法B的效果並無達到項目預期,因此這裏研究一下經過自編譯libjpeg來使用哈夫曼算法進行圖片壓縮的操做。java
libjpeg-turbo是針對libjpeg庫的一個優化版本,具體的介紹能夠移步官方網站。接下來記錄如何編譯出對應的so包文件,這裏採用Cmake的方式進行。android
首先下載libjpeg-turbo源碼,將源碼中的全部文件拷貝到cpp文件夾目錄下:git
這裏須要注意的是須要把項目的CmakeList文件改變成libjpeg-turbo文件夾下面的CmakeList文件,而後進行編譯,就能夠在以下目錄中產生so文件了:github
第二步是要複製對應的頭文件到新項目當中,主要的頭文件包括以下幾個,固然若是調用時候須要用到其餘的頭文件,那麼在複製進去便可:算法
而後在CmakeList中增長so庫連接,鏈接到咱們項目中的so包中去:ide
cmake_minimum_required(VERSION 3.4.1) set(distribution_DIR ../../../../libs) #添加lib,SHARED類型,是IMPORTED 引入的庫 add_library(libjpeg SHARED IMPORTED) #設置 庫的屬性 裏面是名稱 ,屬性:引入地址把咱們的真實地址填寫進去 set_target_properties(libjpeg PROPERTIES IMPORTED_LOCATION ${distribution_DIR}/arm64-v8a/libjpeg.so) #添加lib,SHARED類型,是IMPORTED 引入的庫 add_library(libturbojpeg SHARED IMPORTED) #設置 庫的屬性 裏面是名稱 ,屬性:引入地址把咱們的真實地址填寫進去 set_target_properties(libturbojpeg PROPERTIES IMPORTED_LOCATION ${distribution_DIR}/arm64-v8a/libturbojpeg.so) add_library( # Sets the name of the library. native-lib # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). native-lib.cpp) find_library( # Sets the name of the path variable. log-lib # Specifies the name of the NDK library that # you want CMake to locate. log) target_link_libraries( # Specifies the target library. native-lib libjpeg -ljnigraphics libturbojpeg # Links the target library to the log library # included in the NDK. ${log-lib})
進行來編譯便可,編譯經過說明導入成功。而後進行JNI編碼:性能
//java public native int nativeCompressBitmap(Bitmap bitmap, int quality, String destFile); //jni #include <jni.h> #include <string> #include "turbojpeg.h" #include "jpeglib.h" #include <android/bitmap.h> #include <android/log.h> #include <csetjmp> #define LOG_TAG "C_TAG" #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) typedef u_int8_t BYTE; struct my_error_mgr { struct jpeg_error_mgr pub; jmp_buf setjmp_buffer; }; typedef struct my_error_mgr *my_error_ptr; extern "C" JNIEXPORT jstring JNICALL Java_com_lin_libjpeg_MainActivity_stringFromJNI( JNIEnv *env, jobject /* this */, jstring name) { std::string hello = "Hello from C++"; const char *destFile = env->GetStringUTFChars(name, 0); env->ReleaseStringUTFChars(name, destFile); return env->NewStringUTF(hello.c_str()); } int generateJPEG(BYTE *data, int w, int h, jint quality, const char *location, jint quality1) { int nComponent = 3; struct jpeg_compress_struct jcs; //自定義的error struct my_error_mgr jem; jcs.err = jpeg_std_error(&jem.pub); if (setjmp(jem.setjmp_buffer)) { return 0; } //爲JPEG對象分配空間並初始化 jpeg_create_compress(&jcs); //獲取文件信息 FILE *f = fopen(location, "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; jcs.in_color_space = JCS_RGB; jpeg_set_defaults(&jcs); jcs.optimize_coding = quality; //爲壓縮設定參數,包括圖像大小,顏色空間 jpeg_set_quality(&jcs, quality, true); //開始壓縮 jpeg_start_compress(&jcs, true); JSAMPROW row_point[1]; int row_stride; row_stride = jcs.image_width * nComponent; while (jcs.next_scanline < jcs.image_height) { row_point[0] = &data[jcs.next_scanline * row_stride]; jpeg_write_scanlines(&jcs, row_point, 1); } if (jcs.optimize_coding) { LOGD("使用了哈夫曼算法完成壓縮"); } else { LOGD("未使用哈夫曼算法"); } //壓縮完畢 jpeg_finish_compress(&jcs); //釋放資源 jpeg_destroy_compress(&jcs); fclose(f); return 1; } const char *jstringToString(JNIEnv *env, jstring jstr) { char *ret; const char *tempStr = env->GetStringUTFChars(jstr, NULL); jsize len = env->GetStringUTFLength(jstr); if (len > 0) { ret = (char *) malloc(len + 1); memcpy(ret, tempStr, len); ret[len] = 0; } env->ReleaseStringUTFChars(jstr, tempStr); return ret; } extern "C" JNIEXPORT jint JNICALL Java_com_lin_libjpeg_MainActivity_nativeCompressBitmap(JNIEnv *env, jobject, jobject bitmap, jint optimize, jstring destFile_) { AndroidBitmapInfo androidBitmapInfo; BYTE *pixelsColor; int ret; BYTE *data; BYTE *tmpData; const char *dstFileName = jstringToString(env, destFile_); //解碼Android Bitmap信息 if ((ret = AndroidBitmap_getInfo(env, bitmap, &androidBitmapInfo)) < 0) { LOGD("AndroidBitmap_getInfo() failed error=%d", ret); return ret; } if ((ret = AndroidBitmap_lockPixels(env, bitmap, reinterpret_cast<void **>(&pixelsColor))) < 0) { LOGD("AndroidBitmap_lockPixels() failed error=%d", ret); return ret; } LOGD("bitmap: width=%d,height=%d,size=%d , format=%d ", androidBitmapInfo.width, androidBitmapInfo.height, androidBitmapInfo.height * androidBitmapInfo.width, androidBitmapInfo.format); BYTE r, g, b; int color; int w, h, format; w = androidBitmapInfo.width; h = androidBitmapInfo.height; format = androidBitmapInfo.format; data = (BYTE *) malloc(androidBitmapInfo.width * androidBitmapInfo.height * 3); tmpData = data; // 將bitmap轉換爲rgb數據 for (int i = 0; i < h; ++i) { for (int j = 0; j < w; ++j) { //只處理 RGBA_8888 if (format == ANDROID_BITMAP_FORMAT_RGBA_8888) { color = (*(int *) (pixelsColor)); // 這裏取到的顏色對應的 A B G R 各佔8位 b = (color >> 16) & 0xFF; g = (color >> 8) & 0xFF; r = (color >> 0) & 0xFF; *data = r; *(data + 1) = g; *(data + 2) = b; data += 3; pixelsColor += 4; } else { return -2; } } } AndroidBitmap_unlockPixels(env, bitmap); //進行壓縮 ret = generateJPEG(tmpData, w, h, optimize, dstFileName, optimize); free((void *) dstFileName); free((void *) tmpData); return ret; }
接下來編寫Java代碼測試效果:測試
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Example of a call to a native method TextView tv = findViewById(R.id.sample_text); tv.setText(stringFromJNI("da")); Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.timg); String result = getSaveLocation() + "/compress1.png"; long time = System.currentTimeMillis(); int qu = 40; nativeCompressBitmap(bitmap, qu, result); Log.e("C_TAG", "NAtive" + (System.currentTimeMillis() - time)); time = System.currentTimeMillis(); compressByDefault(bitmap,qu); Log.e("C_TAG", "Java" + (System.currentTimeMillis() - time)); } private void compressByDefault(Bitmap bitmap,int quality) { File file = new File(getSaveLocation() + "/compress2.png"); if (file.exists()) { try { file.delete(); file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } try { OutputStream stream = new FileOutputStream(file); bitmap.compress(Bitmap.CompressFormat.JPEG, quality, stream); } catch (FileNotFoundException e) { e.printStackTrace(); } }
上面分別測試了使用源生以及libjpeg-turbo的效率以及壓縮圖片大小問題,針對效率而言,確實速度是慢了很多:優化
03-15 12:59:11.544 6427-6427/com.lin.libjpeg D/C_TAG: bitmap: width=2048,height=1536,size=3145728 , format=1 03-15 12:59:13.053 6427-6427/com.lin.libjpeg D/C_TAG: 使用了哈夫曼算法完成壓縮 03-15 12:59:13.142 6427-6427/com.lin.libjpeg E/C_TAG: NAtive1598 03-15 12:59:13.362 6427-6427/com.lin.libjpeg E/C_TAG: Java215
圖片在內存中佔用的大小爲3m,同等壓縮質量下, 源生花了215毫秒,而libjpeg-turbo花了1.6秒。網站
接下來看圖片效果以及大小:
使用libjpeg-turbo壓縮出來的圖片大小明顯小於源生的大小,大小約爲源生的80%左右。
Demo代碼地址:Github