项目开发过程中发现Android的质量压缩算法在图片过大,色彩丰富的前提下,压缩的性能不是特别好,经过调查发现Android底层实现使用Skia引擎,封装了了libjpeg图像库。为了适配低版本的Android手机,其内部的压缩算法并没有采用普遍的哈夫曼算法,因为哈夫曼算法比较占CPU,从而选择了其他的算法B,而算法B的效果并没有达到项目预期,所以这里研究一下通过自编译libjpeg来使用哈夫曼算法进行图片压缩的操作。
libjpeg-turbo是针对libjpeg库的一个优化版本,具体的介绍可以移步官方网站。接下来记录如何编译出对应的so包文件,这里采用Cmake的方式进行。
首先下载libjpeg-turbo源码,将源码中的所有文件拷贝到cpp文件夹目录下:
这里需要注意的是需要把项目的CmakeList文件改变成libjpeg-turbo文件夹下面的CmakeList文件,然后进行编译,就可以在如下目录中产生so文件了:
第二步是要复制对应的头文件到新项目当中,主要的头文件包括如下几个,当然如果调用时候需要用到其他的头文件,那么在复制进去即可:
然后在CmakeList中增加so库链接,连接到我们项目中的so包中去:
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
来源:oschina
链接:https://my.oschina.net/u/3863980/blog/3021025