最近在看了動腦學院的圖片優化之後,感覺通過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目錄下面
當然這個目錄不是必須的,放在其他目錄也行;然後用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, 壓縮之後的圖片大小是58kb