一 原理
同樣的圖片,在ios手機上顯示的效果要比安卓手機上效果要好?
- 圖片處理引擎用的是pc上的圖片處理引擎skia
- 去掉一個編碼算法—哈夫曼算法。採用定長編碼算法
**原因:**當時由於CPU和內存在手機上都非常吃緊 性能差,由於哈夫曼算法非常吃CPU,被迫用了其他的算法。
需求:,隨着安卓設備硬件水平的提升,可以在性能的安卓設備上使用一些比較耗費性能的算法,使得圖片顯示的效果更佳
**實現方式:**繞過安卓Bitmap API層,來自己編碼實現----修復使用哈夫曼算法
二 哈夫曼編碼引入原因
一個像素點(argb)包涵四個信息:alpha,red,green,blue
- 假設有五個基本字符:
a b c d e - 要表示下面一段內容
abcde acdbe bacde …… - 用3位來表示一個字符信息,屬於定長編碼的最優。
a:001
b:010
c:011
d:100
e:101 - 最終結果:
101010100011100
如果使用:加權信息編碼
a:80%
b:10%
c:10%
d:0%
e:0%
則對應的
a:01
b:10
c:11
優化後的abc:01 10 11
優化前的abc:001 010 011
最終結果:
abcde
001 010 011 100 101
這種情況下,編碼就可以優化了
問題來了: 如何得到每一個字母出現的權重?
哈夫曼編碼: 需要去掃描整個個信息(圖片信息–每一個像素包括ARGB),要大量計算,很吃CPU。
三 引入下載JPEG引擎使用的庫—libjpeg庫
基於該引擎來做一定的開發----自己實現編碼。
略過:NDK環境變量,自行百度
- 導入庫文件libjpegbither.so 和頭文件
- 定義native方法
JniUtil:
public static native String compressBitmap(Bitmap bit, int w, int h, int quality, byte[] fileNameBytes,
boolean optimize);
- 編譯生成.h頭文件
命令行:javac編譯JniUtils.java生產.class文件
C:\rj\workspace\optimization\app\src\main\java\com\bpj\optimization\optimization> javac -classpath C:\rj\sdk\platforms\android-29\android.jar JniUtil.java
注意:
- JniUtils不能用中文註釋,可編譯頭再添加
- 命令行位置:JniUtils.java的位置
- 如果JniUtils有import 記得帶上jar包 -classpath +jar包路徑
生成.h頭文件
javah -classpath +(jar包) -jni + (包名+類名)
-
創建.cpp文件
JniUtil.cpp -
寫代碼
C++: XX.cpp
C: XX.c
四 c層實現圖片處理具體邏輯
- 將android的bitmap解碼,並轉換成RGB數據
一個圖片信息—像素點(argb)
alpha去掉 - JPEG對象分配空間以及初始化
- 指定壓縮數據源
- 獲取文件信息
- 爲壓縮設置參數,比如圖像大小、類型、顏色空間
boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */ - 開始壓縮
jpeg_start_compress() - 壓縮結束
jpeg_finish_compress() - 釋放資源
代碼:
METHODDEF(void) my_error_exit (j_common_ptr cinfo)
{
my_error_ptr myerr = (my_error_ptr) cinfo->err;
(*cinfo->err->output_message) (cinfo);
error=(char*)myerr->pub.jpeg_message_table[myerr->pub.msg_code];
LOGE("jpeg_message_table[%d]:%s", myerr->pub.msg_code,myerr->pub.jpeg_message_table[myerr->pub.msg_code]);
// LOGE("addon_message_table:%s", myerr->pub.addon_message_table);
// LOGE("SIZEOF:%d",myerr->pub.msg_parm.i[0]);
// LOGE("sizeof:%d",myerr->pub.msg_parm.i[1]);
longjmp(myerr->setjmp_buffer, 1);
}
int generateJPEG(BYTE* data, int w, int h, int quality,
const char* outfilename, jboolean optimize) {
//jpeg的結構體,保存的比如寬、高、位深、圖片格式等信息,相當於java的類
struct jpeg_compress_struct jcs;
//當讀完整個文件的時候就會回調my_error_exit這個退出方法。setjmp是一個系統級函數,是一個回調。
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;
}
//初始化jsc結構體
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;
// if (optimize) {
// LOGI("optimize==ture");
// } else {
// LOGI("optimize==false");
// }
//看源碼註釋,設置哈夫曼編碼:/* TRUE=arithmetic coding, FALSE=Huffman */
jcs.arith_code = false;
int nComponent = 3;
/* 顏色的組成 rgb,三個 # of color components in input image */
jcs.input_components = nComponent;
//設置結構體的顏色空間爲rgb
jcs.in_color_space = JCS_RGB;
// if (nComponent == 1)
// jcs.in_color_space = JCS_GRAYSCALE;
// else
// jcs.in_color_space = JCS_RGB;
//全部設置默認參數/* Default parameter setup for compression */
jpeg_set_defaults(&jcs);
//是否採用哈弗曼表數據計算 品質相差5-10倍
jcs.optimize_coding = optimize;
//設置質量
jpeg_set_quality(&jcs, quality, true);
//開始壓縮,(是否寫入全部像素)
jpeg_start_compress(&jcs, TRUE);
JSAMPROW row_pointer[1];
int row_stride;
//一行的rgb數量
row_stride = jcs.image_width * nComponent;
//一行一行遍歷
while (jcs.next_scanline < jcs.image_height) {
//得到一行的首地址
row_pointer[0] = &data[jcs.next_scanline * row_stride];
//此方法會將jcs.next_scanline加1
jpeg_write_scanlines(&jcs, row_pointer, 1);//row_pointer就是一行的首地址,1:寫入的行數
}
jpeg_finish_compress(&jcs);//結束
jpeg_destroy_compress(&jcs);//銷燬 回收內存
fclose(f);//關閉文件
return 1;
}
/**
* byte數組轉C的字符串
*/
char* jstrinTostring(JNIEnv* env, jbyteArray barr) {
char* rtn = NULL;
jsize alen = env->GetArrayLength( barr);
jbyte* ba = env->GetByteArrayElements( barr, 0);
if (alen > 0) {
rtn = (char*) malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
env->ReleaseByteArrayElements( barr, ba, 0);
return rtn;
}
jstring Java_com_bpj_optimization_optimization_JniUtil_compressBitmap(JNIEnv* env,
jclass thiz, jobject bitmapcolor, int w, int h, int quality,
jbyteArray fileNameStr, jboolean optimize) {
BYTE *pixelscolor;
//1.將bitmap裏面的所有像素信息讀取出來,並轉換成RGB數據,保存到二維byte數組裏面
//處理bitmap圖形信息方法1 鎖定畫布
AndroidBitmap_lockPixels(env,bitmapcolor,(void**)&pixelscolor);
//2.解析每一個像素點裏面的rgb值(去掉alpha值),保存到一維數組data裏面
BYTE *data;
BYTE r,g,b;
data = (BYTE*)malloc(w*h*3);//每一個像素都有三個信息RGB
BYTE *tmpdata;
tmpdata = data;//臨時保存data的首地址
int i=0,j=0;
int color;
for (i = 0; i < h; ++i) {
for (j = 0; j < w; ++j) {
//解決掉alpha
//獲取二維數組的每一個像素信息(四個部分a/r/g/b)的首地址
color = *((int *)pixelscolor);//通過地址取值
//0~255:
// a = ((color & 0xFF000000) >> 24);
r = ((color & 0x00FF0000) >> 16);
g = ((color & 0x0000FF00) >> 8);
b = ((color & 0x000000FF));
//改值!!!----保存到data數據裏面
*data = b;
*(data+1) = g;
*(data+2) = r;
data = data + 3;
//一個像素包括argb四個值,每+4就是取下一個像素點
pixelscolor += 4;
}
}
//處理bitmap圖形信息方法2 解鎖
AndroidBitmap_unlockPixels(env,bitmapcolor);
char* fileName = jstrinTostring(env,fileNameStr);
//調用libjpeg核心方法實現壓縮
int resultCode = generateJPEG(tmpdata,w,h,quality,fileName,optimize);
if(resultCode ==0){
jstring result = env->NewStringUTF("-1");
return result;
}
return env->NewStringUTF("1");
}