Android NDK编译libjpeg-turbo压缩图片
Android开发中,我们经常要面对图片压缩,大部分人使用Android Bitmap进行压缩,还有一些使用libjpeg压缩,之前有用过libjpeg,压缩效果相当惊艳,在保证图片损失较小的同时,还极大的减小了图片体积,不过这次我们基于libjpeg-turbo做图片压缩,据官方说速度提升2-6倍。
libjpeg-turbo** is a JPEG image codec that uses SIMD instructions (MMX, SSE2, AVX2, NEON, AltiVec) to accelerate baseline JPEG compression and decompression on x86, x86-64, ARM, and PowerPC systems, as well as progressive JPEG compression on x86 and x86-64 systems. On such systems, libjpeg-turbo** is generally 2-6x as fast as libjpeg, all else being equal. On other types of systems, libjpeg-turbo** can still outperform libjpeg by a significant amount, by virtue of its highly-optimized Huffman coding routines. In many cases, the performance of libjpeg-turbo** rivals that of proprietary high-speed JPEG codecs.
开始
1.Android Studio新建C工程
新建C工程 新建C工程 新建Module Module添加C支持2.编译libjpeg-turbo
复制源码下载源码,将源码copy到Module的cpp目录
libjpeg-turbo动态库重新Build项目,找到Module编译的apk,解压apk,得到libjpeg-turbo的so动态链接库。
3.使用libjpeg-turbo
新建native方法
package peak.chao.picturecompression;
import android.graphics.Bitmap;
public class CompressUtil {
static {
System.loadLibrary("native-lib");
}
public native static int compressBitmap(Bitmap bitmap, int quality, String destFile);
}
生成头文件javah 生成头文件
引入头文件将生成的头文件移动到cpp目录,并且将需要使用的依赖头文件一并引入
修改native-lib.cpp,实现压缩方法
//
// Created by peakchao on 2019/3/22.
//
#include <jni.h>
#include <string>
#include "turbojpeg.h"
#include "jpeglib.h"
#include <android/bitmap.h>
#include <android/log.h>
#include <csetjmp>
#include <setjmp.h>
#include "peak_chao_picturecompression_CompressUtil.h"
#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;
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_peak_chao_picturecompression_CompressUtil_compressBitmap(JNIEnv *env, jclass,
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;
}
修改CMakeLists.txt进行编译配置
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}/x86/libjpeg.so)
#添加lib,SHARED类型,是IMPORTED 引入的库
add_library(libturbojpeg
SHARED
IMPORTED)
#设置 库的属性 里面是名称 ,属性:引入地址把我们的真实地址填写进去
set_target_properties(libturbojpeg
PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/x86/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})
清单文件加入权限,目标sdk版本大于等于23需要动态权限申请,为了测试,我这里在设置中手动授权。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
修改MainActivity和activity_main布局文件,做图片压缩测试。
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/native_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="compressNative"
android:text="本地压缩"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:textSize="30sp"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/system_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:onClick="compressSystem"
android:text="系统压缩"
android:textSize="30sp"
app:layout_constraintLeft_toLeftOf="@id/native_tv"
app:layout_constraintRight_toRightOf="@id/native_tv"
app:layout_constraintTop_toBottomOf="@id/native_tv" />
</android.support.constraint.ConstraintLayout>
package peak.chao.picturecompression;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class MainActivity extends AppCompatActivity {
private int qu = 40;
private Bitmap bitmap;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AssetManager manager = getResources().getAssets();
InputStream open = null; //得到输出流
try {
open = manager.open("max_image.jpg");
} catch (IOException e) {
e.printStackTrace();
}
bitmap = BitmapFactory.decodeStream(open);
}
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();
}
}
private String getSaveLocation() {
return Environment.getExternalStorageDirectory().getAbsolutePath();
}
public void compressNative(View view) {
String result = getSaveLocation() + "/compress.png";
long time = System.currentTimeMillis();
int i = CompressUtil.compressBitmap(bitmap, qu, result);
Log.e("C_TAG", "Native" + (System.currentTimeMillis() - time));
if (i == 1) {
Toast.makeText(this, "压缩完成,耗时:" + (System.currentTimeMillis() - time) + "毫秒", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this, "压缩失败", Toast.LENGTH_LONG).show();
}
}
public void compressSystem(View view) {
long time = System.currentTimeMillis();
compressByDefault(bitmap, qu);
Log.e("C_TAG", "Java" + (System.currentTimeMillis() - time));
Toast.makeText(this, "压缩完成,耗时:" + (System.currentTimeMillis() - time) + "毫秒", Toast.LENGTH_LONG).show();
}
}