Android开发中,我们需要处理图片压缩,今天要介绍的是基于NDK层利用libjpeg-turbo(基于哈夫曼算法)类库做图片体积的极限压缩,跟libjpeg相比速度提升2-6倍。
1. 编译libjpeg-turbo准备工作
(1)NDK下载 ,去官方网站下载 (官网),笔者用的是android-ndk-r16c版本。NDK,Android Native Development Kit一套允许使用原生代码语言C/C++,实现部分应用的集。 C/C++与Java通过JNI交互。
(2)LibJpeg库。下载路径 wget https://github.com/libjpeg-turbo/libjpeg-turbo/archive/2.0.2.tar.gz,建议使用release版本,解压 tar xvf 2.0.2.tar.gz;编译可参考官方文档说明 (编辑官方文档说明)
(3)安装NASM。选择最新的发布版本,压缩包下载路径(下载路径),解压下载的文件,进入解压后的目录可以看到一个文件configure 用于生成Makefile文件,编译出该文件 ./configure
(4)安装cmake。官网地址(下载)
2.接下来可以进入libjepg目录进行编译了
(1)生成shell脚本。如下:
笔者mac环境下脚本如图:
执行该脚本后,去libjpeg-turbo目录下查看,找到如下图几个文件,并copy到项目的相应目录
以上几个文件就是我们编译libjpeg-turbo所得到的压缩图片的核心类库,接下来要实现native方法去对图片进行压缩
3.项目用使用。
(1)注意你的项目在创建的时候要勾选 Include C++ support。接下来配置CMakeLists,如下图:
注意:如果你的Gradle编译不过,检查下gradle版本(要更高版本),笔者用的是 classpath'com.android.tools.build:gradle:3.2.0'(供参考)
(2)开始压缩,大致分为下面几个步骤:
1、获得待压缩Bitmap
2、获得Bitmap中像素数据(ARGB->BGR)
3、Libjpeg压缩。
创建jpeg压缩对象;指定存储文件;设置压缩参数;开始压缩;循环写入每一行数据;压缩完成;释放jpeg对象。
压缩核心方法代码:
extern "C"
JNIEXPORTvoid JNICALL
Java_com_example_administrator_demo_MainActivity_nativeCompress(JNIEnv *env,
jobject instance,
jobject bitmap, jint q,
jstring path_) {
const char *path = env->GetStringUTFChars(path_, 0);
//从bitmap获取argb数据
AndroidBitmapInfo info;//info=new 对象();
//获取里面的信息
AndroidBitmap_getInfo(env, bitmap, &info);// void method(list)
//得到图片中的像素信息
uint8_t *pixels;//uint8_t char java byte *pixels可以当byte[]
AndroidBitmap_lockPixels(env, bitmap, (void **) &pixels);
//jpeg argb中去掉他的a ===>rgb
int w = info.width;
int h = info.height;
int color;
//开一块内存用来存入rgb信息
uint8_t *data = (uint8_t *) malloc(w * h *3);//data中可以存放图片的所有内容
uint8_t *temp = data;
uint8_t r, g, b;//byte
//循环取图片的每一个像素
for (int i =0; i < h; i++) {
for (int j =0; j < w; j++) {
color = *(int *) pixels;//0-3字节 color4 个字节 一个点
//取出rgb
r = (color >>16) &0xFF;// #00rrggbb 16 0000rr 8 00rrgg
g = (color >>8) &0xFF;
b = color &0xFF;
//存放,以前的主流格式jpeg bgr
*data = b;
*(data +1) = g;
*(data +2) = r;
data +=3;
//指针跳过4个字节
pixels +=4;
}
}
//把得到的新的图片的信息存入一个新文件 中
write_JPEG_file(temp, w, h, q, path);
//释放内存
free(temp);
AndroidBitmap_unlockPixels(env, bitmap);
env->ReleaseStringUTFChars(path_, path);
}
void write_JPEG_file(uint8_t *data, int w, int h, jint q, const char *path) {
// 3.1、创建jpeg压缩对象
jpeg_compress_struct jcs;
//错误回调
jpeg_error_mgr error;
jcs.err = jpeg_std_error(&error);
//创建压缩对象
jpeg_create_compress(&jcs);
// 3.2、指定存储文件 write binary
FILE *f = fopen(path, "wb");
jpeg_stdio_dest(&jcs, f);
// 3.3、设置压缩参数
jcs.image_width = w;
jcs.image_height = h;
//bgr
jcs.input_components =3;
jcs.in_color_space = JCS_RGB;
jpeg_set_defaults(&jcs);
//开启哈夫曼功能
jcs.optimize_coding =true;
jpeg_set_quality(&jcs, q, 1);
// 3.4、开始压缩
jpeg_start_compress(&jcs, 1);
// 3.5、循环写入每一行数据
int row_stride = w *3;//一行的字节数
JSAMPROW row[1];
while (jcs.next_scanline < jcs.image_height) {
//取一行数据
uint8_t *pixels = data + jcs.next_scanline * row_stride;
row[0]=pixels;
jpeg_write_scanlines(&jcs,row,1);
}
// 3.6、压缩完成
jpeg_finish_compress(&jcs);
// 3.7、释放jpeg对象
fclose(f);
jpeg_destroy_compress(&jcs);
}
在java层调用native方法,如下图:
以上是对图片大小进行压缩,实质上是体积的一种压缩,图片显示效果根据上图方法的q(质量参数)参数来写,一般情况下50左右就可以达到效果与原图没有区别,一般情况下比如2M的图片,经过这样的压缩 ,可以压缩到1M以下,大大减少了图片所占有存储空间。至于应用跑起来的时候,怎么去对图片加载需要的内存压缩,后续会补上图片内存压缩相关博客!