1. 概述
上一期已講到Android圖片壓縮加密上傳 - JPEG壓縮算法解析,我們不打算采用BitmapFactory去壓縮,而是采用JPEG的壓縮算法,當(dāng)然大家最好是將兩者結(jié)合一下,今天我們直接去網(wǎng)上找一個(gè)已經(jīng)寫好的開源庫,然后我們在他的基礎(chǔ)上再寫一些Native代碼就好,當(dāng)然也可以自己一步一步去寫算法處理。

所有分享大綱:2017Android進(jìn)階之路與你同行
視頻講解地址:http://pan.baidu.com/s/1eR8ZnxS
2. 編譯libjpeg.so庫文件
關(guān)于C和C++以及NDK的基礎(chǔ)我這里就不強(qiáng)調(diào),如果大家覺得我寫的C++代碼看不懂那么也沒關(guān)系,你只需要關(guān)注我這里所實(shí)現(xiàn)的思路,我最終把它編譯成.so庫你能用就行。
打開https://github.com/libjpeg-turbo/libjpeg-turbo 下載已經(jīng)提供好的開源庫,打開后你會看到有很多的.c文件和.h頭文件,把他編譯成.so庫文件即可,當(dāng)然道路曲折會出現(xiàn)很多問題,這里我就不寫過程了,等到增量更新的時(shí)候我會一步一步去帶大家編譯的。
我們將已經(jīng)編譯好的libjpeg.so以及.h頭文件拷貝到j(luò)ni目錄下面,就可以開始寫壓縮代碼了:

3. 編寫imgcompcrypt.cpp
我使用的是AS,網(wǎng)上關(guān)于AS的NDK方面的資料比較少,需要多花一些功夫,最好把AS升級到2.2版本以上,然后下載NDK、CMake、LLDB,一個(gè)支持、一個(gè)插件、一個(gè)調(diào)試:

升級到2.2之后我們寫C和C++的代碼才會有提示,之前的版本我還沒找到解決的方案,網(wǎng)上搜索了很多也沒有相關(guān)答案,而且它也支持Cmake和ndk-build兩種方式,相比與以前的gradle去配置ndk編譯目錄什么的簡直是方便多了。對于老的通過Android.mk文件編譯的NDK項(xiàng)目,直接一條配置整個(gè)項(xiàng)目就可以被AS支持了。如果覺得Cmake的方式不習(xí)慣還是可以采用ndk-build的方式這點(diǎn)倒是無所謂,至于代碼提示肯定是要的這個(gè)很致命,要不然寫代碼會比較慢。怎么生成頭文件我就不講了,這里直接上代碼:
#include "imgcompcrypt.h"
#include <string.h>
#include <android/bitmap.h>
#include <android/log.h>
#include <stdio.h>
#include <setjmp.h>
#include <math.h>
#include <stdint.h>
#include <time.h>
//統(tǒng)一編譯方式
extern "C" {
#include "jpeg/jpeglib.h"
#include "jpeg/cdjpeg.h" /* Common decls for cjpeg/djpeg applications */
#include "jpeg/jversion.h" /* for version message */
#include "jpeg/jconfig.h"
#include "filecrypt.c"
}
// log打印
#define LOG_TAG "jni"
#define LOGW(...) __android_log_write(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#define true 1
#define false 0
typedef uint8_t BYTE;
// error 結(jié)構(gòu)體
char *error;
struct my_error_mgr {
struct jpeg_error_mgr pub;
jmp_buf setjmp_buffer;
};
typedef struct my_error_mgr *my_error_ptr;
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]);
longjmp(myerr->setjmp_buffer, 1);
}
int generateJPEG(BYTE *data, int w, int h, int quality,
const char *outfilename, jboolean optimize) {
// 結(jié)構(gòu)體相當(dāng)于Java類
struct jpeg_compress_struct jcs;
//當(dāng)讀完整個(gè)文件的時(shí)候就會回調(diào)my_error_exit這個(gè)退出方法。
struct my_error_mgr jem;
jcs.err = jpeg_std_error(&jem.pub);
jem.pub.error_exit = my_error_exit;
// setjmp是一個(gè)系統(tǒng)級函數(shù),是一個(gè)回調(diào)。
if (setjmp(jem.setjmp_buffer)) {
return 0;
}
//初始化jsc結(jié)構(gòu)體
jpeg_create_compress(&jcs);
//打開輸出文件 wb 可寫 rb 可讀
FILE *f = fopen(outfilename, "wb");
if (f == NULL) {
return 0;
}
//設(shè)置結(jié)構(gòu)體的文件路徑,以及寬高
jpeg_stdio_dest(&jcs, f);
jcs.image_width = w;
jcs.image_height = h;
// /* TRUE=arithmetic coding, FALSE=Huffman */
jcs.arith_code = false;
int nComponent = 3;
/* 顏色的組成 rgb,三個(gè) # of color components in input image */
jcs.input_components = nComponent;
//設(shè)置顏色空間為rgb
jcs.in_color_space = JCS_RGB;
///* Default parameter setup for compression */
jpeg_set_defaults(&jcs);
//是否采用哈弗曼
jcs.optimize_coding = optimize;
//設(shè)置質(zhì)量
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);
}
// 壓縮結(jié)束
jpeg_finish_compress(&jcs);
// 銷毀回收內(nèi)存
jpeg_destroy_compress(&jcs);
//關(guān)閉文件
fclose(f);
return 1;
}
jint Java_net_bither_util_NativeUtil_compressBitmap(JNIEnv *env,
jclass thiz, jobject bitmap, int quality,
jstring fileNameStr, jboolean optimize) {
// 1.獲取Bitmap信息
AndroidBitmapInfo android_bitmap_info;
AndroidBitmap_getInfo(env, bitmap, &android_bitmap_info);
// 獲取bitmap的 寬,高,format
int bitmap_width = android_bitmap_info.width;
int bitmap_height = android_bitmap_info.height;
int format = android_bitmap_info.format;
if (format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
return -1;
}
// 2.解析Bitmap的像素信息,并轉(zhuǎn)換成RGB數(shù)據(jù),保存到二維byte數(shù)組里面
BYTE *pixelscolor;
// 2.1 鎖定畫布
AndroidBitmap_lockPixels(env, bitmap, (void **) &pixelscolor);
// 2.2 解析初始化參數(shù)值
BYTE *data;
BYTE r, g, b;
data = (BYTE *) malloc(bitmap_width * bitmap_height * 3);//每一個(gè)像素都有三個(gè)信息RGB
BYTE *tmpData;
tmpData = data;//臨時(shí)保存data的首地址
int i = 0, j = 0;
int color;
//2.3 解析每一個(gè)像素點(diǎn)里面的rgb值(去掉alpha值),保存到一維數(shù)組data里面
for (i = 0; i < bitmap_height; ++i) {
for (j = 0; j < bitmap_width; ++j) {
//獲取二維數(shù)組的每一個(gè)像素信息首地址
color = *((int *) pixelscolor);
r = ((color & 0x00FF0000) >> 16);
g = ((color & 0x0000FF00) >> 8);
b = ((color & 0x000000FF));
//保存到data數(shù)據(jù)里面
*data = b;
*(data + 1) = g;
*(data + 2) = r;
data = data + 3;
// 一個(gè)像素包括argb四個(gè)值,每+4就是取下一個(gè)像素點(diǎn)
pixelscolor += 4;
}
}
// 2.4. 解鎖Bitmap
AndroidBitmap_unlockPixels(env, bitmap);
// jstring --> c char
char *fileName = (char*)(env)->GetStringUTFChars(fileNameStr, 0);
//3. 調(diào)用libjpeg核心方法實(shí)現(xiàn)壓縮
int resultCode = generateJPEG(tmpData, bitmap_width, bitmap_height, quality, fileName, optimize);
//4.釋放資源
env->ReleaseStringUTFChars(fileNameStr, fileName);
free((void *) tmpData);
// 4.2 釋放Bitmap
// 4.2.1 通過對象獲取類
jclass bitmap_clz = env->GetObjectClass(bitmap);
// 4.2.2 通過類和方法簽名獲取方法id
jmethodID recycle_mid = env->GetMethodID(bitmap_clz, "recycle", "()V");
// 4.2.3 執(zhí)行回收釋放方法
env->CallVoidMethod(bitmap, recycle_mid);
// 5.返回結(jié)果
if (resultCode == 0) {
return -1;
}
return 1;
}
3. 圖片文件加密
文件的加密相對來說就比較簡單了,因?yàn)榭赡芸珊芏嗟胤缴婕暗轿募用?,這里我就把文件加密單獨(dú)分開了,當(dāng)然也可以寫到圖片壓縮一起,可以用C寫也可以用C++因?yàn)樯厦媸怯玫腃++,那么加密我們就采用C:
#include <jni.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "net_bither_util_FileCrypt.h"
// 加密的秘鑰
char password[] = "Big god take me fly!";
// 加密文件
void crypt_file(char *normal_path, char *crypt_path) {
//打開文件
FILE *normal_fp = fopen(normal_path, "rb");
FILE *crypt_fp = fopen(crypt_path, "wb");
//一次讀取一個(gè)字符
int ch;
int i = 0; //循環(huán)使用密碼中的字母進(jìn)行異或運(yùn)算
int pwd_len = strlen(password); //密碼的長度
while ((ch = fgetc(normal_fp)) != EOF) { //End of File
//寫入(異或運(yùn)算)
fputc(ch ^ password[i % pwd_len], crypt_fp);
i++;
}
// 關(guān)閉
fclose(crypt_fp);
fclose(normal_fp);
}
// 加密文件,jfile_path 源文件路徑 jcrypt_path 加密后文件路徑
JNIEXPORT void JNICALL Java_com_hc_filecrypt_FileCrypt_cryptFile
(JNIEnv *env, jclass jclazz, jstring jfile_path, jstring jcrypt_path) {
char *normal_path = (char*)(*env)->GetStringUTFChars(env, jfile_path, JNI_FALSE);
char *crypt_path = (char*)(*env)->GetStringUTFChars(env, jcrypt_path, JNI_FALSE);
crypt_file(normal_path, crypt_path);
}
4. 最后的測試
接近3M的原圖壓縮到30K,可以找找哪一張是被壓縮過的,會比我們使用BitmapFarctory或者Bitmap.compress()壓縮出來的一些效果要好很多:

所有分享大綱:2017Android進(jìn)階之路與你同行
視頻講解鏈接:https://pan.baidu.com/s/1VQhMemYubQfldEcu9gVMEg 密碼:8abx