Android 圖片壓縮
此篇文章不討論質量壓縮,比例壓縮與采樣率壓縮,只考慮使用libjpeg,繞過andriod api層,還原哈夫曼算法
原圖
壓縮后
,失真不明顯
首先得編譯出libjpeg.so文件
如下:
網上說得很復雜,其實很簡單
- 使用git命令:
git clone git://git.linaro.org/people/tomgall/libjpeg-turbo/libjpeg-turbo.git -b linaro-android
克隆最新的分支 - 下載下來后把文件夾libjpeg-turbo重命名為jni
mv libjpeg-turbo jni
- cd到jni目錄下
ndk-build APP_ABI=armeabi-v7a,armeabi
- 有可能出現錯誤:
/root/jpge/jni/Android.mk:11: Extraneous text after `ifeq' directive
Android NDK: WARNING: Unsupported source file extensions in /root/jpge/jni/Android.mk for module jpeg
Android NDK: turbojpeg-mapfile
/root/dev/android-ndk-r14b/build/core/build-binary.mk:687: Android NDK: Module jpeg depends on undefined modules: cutils
/root/dev/android-ndk-r14b/build/core/build-binary.mk:700: *** Android NDK: Aborting (set APP_ALLOW_MISSING_DEPS=true to allow missing dependencies) . Stop.
- 在網上搜了下,不少人遇到此問題,解決方法都是回退版本,如ndk r10就可以解決問題了?但是這樣的回退是有代價的,像不能使用c++11的東西,std::to_string()那么好用的函數都不能用,多么可惜,啰嗦那么多,解決方案是:
在Android.mk中設置APP_ALLOW_MISSING_DEPS :=true
-
再執行ndk-build就可以了,然后在jni的同級目錄出現libs與obj包
然后在libs目錄下放入so庫,開始native方法
考慮到文件夾可能不存在的情況,而在c中拆分字符串難免有些復雜,我們可以先處理了后傳入
public class CompressUtil {
static {
System.loadLibrary("compress");
}
private CompressUtil() {
}
/**
* 使用native方法進行圖片壓縮。
* Bitmap的格式必須是ARGB_8888 {@link Bitmap.Config}。
*
* @param bitmap 圖片數據
* @param quality 壓縮質量
* @param dstFile 壓縮后存放的路徑
* @param optimize 是否使用哈夫曼算法
* @return 是否成功
*/
public static boolean nativeCompressBitmap(Bitmap bitmap, int quality, String dstFile, boolean optimize) {
String dirPath = dstFile.substring(0, dstFile.lastIndexOf(File.separator));
return nativeCompress(bitmap, quality, dstFile, dirPath, optimize) == 1;
}
/**
* 使用native方法進行圖片壓縮。
* Bitmap的格式必須是ARGB_8888 {@link Bitmap.Config}。
*
* @param bitmap 圖片數據
* @param quality 壓縮質量
* @param dstFile 壓縮后存放的路徑
* @param dirPath 文件存放的目錄
* @param optimize 是否使用哈夫曼算法
* @return 成功為1,不為argb_8888為-1,失敗為0
*/
private static native int nativeCompress(Bitmap bitmap, int quality, String dstFile, String dirPath, boolean optimize);
}
生成cpp文件
先寫CMakeLists文件
cmake_minimum_required(VERSION 3.4.1)
set(distribution_DIR ../../../../libs)
set(SOURCE_FILES src/main/cpp/compress.cpp)
set(INC_DIR src/main/cpp/include)
include_directories(${INC_DIR})
find_library( log-lib
log)
find_library(graphics jnigraphics)
add_library( libjpeg
SHARED
IMPORTED)
set_target_properties( libjpeg
PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/${ANDROID_ABI}/libjpeg.so)
add_library( compress
SHARED
${SOURCE_FILES} )
target_link_libraries( # Specifies the target library.
compress
libjpeg
${graphics}
${log-lib} )
導入jpeg的頭文件以及開始寫cpp
cpp如下:
#include <jni.h>
#include <string>
#include <stdlib.h>
#include <unistd.h>
#include <setjmp.h>
#include <dirent.h>
#include <sys/stat.h>
#include <android/bitmap.h>
#include <android/log.h>
extern "C" {
//因為c++編譯器會對函數名進行重整。不然會提示找不到函數名
#include <jpeglib.h>
}
#define TAG "COMPRESS"
#define LOGI(FORMAT, ...) __android_log_print(ANDROID_LOG_INFO,TAG,FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT, ...) __android_log_print(ANDROID_LOG_ERROR,TAG,FORMAT,##__VA_ARGS__);
#define LOGW(FORMAT, ...) __android_log_print(ANDROID_LOG_WARN,TAG,FORMAT,##__VA_ARGS__);
#define LOGD(FORMAT, ...) __android_log_print(ANDROID_LOG_DEBUG,TAG,FORMAT,##__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;
METHODDEF(void)
my_error_exit(j_common_ptr
cinfo) {
my_error_ptr myerr = (my_error_ptr) cinfo->err;
(*cinfo->err->output_message)(cinfo);
LOGW("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, jint quality, const char *name, const char *dir_path,
boolean optimize);
int generateJPEG(BYTE *data, int w, int h, int quality, const char *name, const char *dir_path,
boolean optimize) {
int nComponent = 3;
struct jpeg_compress_struct jcs;
//自定義的error
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;
}
//為JPEG對象分配空間并初始化
jpeg_create_compress(&jcs);
//獲取文件信息
if (0 != access(dir_path, 0)) {//目錄不存在
LOGD("file is not exist");
if (0 != mkdir(dir_path, 777)) {
LOGE("make dir fail")
return 0;
}
}
FILE *f = fopen(name, "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 = optimize;
//為壓縮設定參數,包括圖像大小,顏色空間
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) {
LOGI("使用了哈夫曼算法完成壓縮");
} else {
LOGI("未使用哈夫曼算法");
}
//壓縮完畢
jpeg_finish_compress(&jcs);
//釋放資源
jpeg_destroy_compress(&jcs);
fclose(f);
return 1;
}
/*
* Class: github_com_androidadvanced_ndk_util_ImageUtil
* Method: compressBitmap
* Signature: (Ljava/lang/Object;ILjava/lang/String;B)I
*/
extern "C"
JNIEXPORT jint JNICALL Java_com_apkcore_compress_CompressUtil_nativeCompress
(JNIEnv *env, jclass clazz, jobject bitmap, jint quality, jstring dstFile_,
jstring dirPath_, jboolean optimize) {
LOGD("%s", "===>Java_com_apkcore_commpress_CompressUtil_nativeCompressBitmap");
int ret;
AndroidBitmapInfo bitmapInfo;
//像素點argb
BYTE *pixelsColor;
//bitmap 數據
BYTE *data;
BYTE *tmpData;
//獲取android bitmap 信息
if ((ret = AndroidBitmap_getInfo(env, bitmap, &bitmapInfo)) < 0) {
LOGD("AndroidBitmap_getInfo() failed error=%d", ret);
return ret;
}
//鎖定bitmap,獲取像素點argb,存儲到pixelsColor中
if ((ret = AndroidBitmap_lockPixels(env, bitmap, (void **) &pixelsColor)) < 0) {
LOGD("AndroidBitmap_lockPixels() failed error=%d", ret);
return ret;
}
BYTE r, g, b;
int color;
//獲取圖片信息
int w, h, format;
w = bitmapInfo.width;
h = bitmapInfo.height;
format = bitmapInfo.format;
//只處理 RGBA_8888
if (format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
LOGD("AndroidBitmapInfo format is not ANDROID_BITMAP_FORMAT_RGBA_8888 error=%d", ret);
return -1;
}
LOGD("bitmap: width=%d,height=%d,size=%d , format=%d ", w, h, w * h, bitmapInfo.format);
//分配內存(存放bitmap rgb數據)
data = (BYTE *) malloc(w * h * 3);
//保存內存首地址
tmpData = data;
//將bitmap轉rgb
int i = 0;
int j = 0;
for (i = 0; i < h; ++i) {
for (j = 0; j < w; ++j) {
color = (*(int *) (pixelsColor));
// 這里取到的顏色對應的 A B G R 各占8位 0xffffffff--->0xaarrggbb
b = (color >> 16) & 0xFF;
g = (color >> 8) & 0xFF;
r = (color >> 0) & 0xFF;
*data = r;
*(data + 1) = g;
*(data + 2) = b;
data += 3;
pixelsColor += 4;
}
}
AndroidBitmap_unlockPixels(env, bitmap);
//進行壓縮后文件存放的地址
const char *file_path = env->GetStringUTFChars(dstFile_, NULL);
const char *dir_path = env->GetStringUTFChars(dirPath_, NULL);
//壓縮圖片
ret = generateJPEG(tmpData, w, h, quality, file_path, dir_path, optimize);
//釋放內存
free((void *) tmpData);
env->ReleaseStringUTFChars(dstFile_, file_path
);
env->ReleaseStringUTFChars(dirPath_, dir_path
);
//釋放java-->bitmap
jclass jBitmapClass = env->GetObjectClass(bitmap);
jmethodID jRecycleMethodId = env->GetMethodID(jBitmapClass, "recycle", "()V");
env->CallVoidMethod(bitmap, jRecycleMethodId,NULL);
//成功為1,不為argb_8888為-1,失敗為0
return ret;
}
如何打包成jar以及引用so庫
就可以使用as自帶的工具很好打包,如下圖
在other中,一個為打jar一個為打so,打出來的so不包括引用的libjpeg.so,要注意一下