Android使用libjpeg实现图片压缩

一、Android中使用的图片压缩库
Android和IOS 中图片处理使用了一个叫做skia的开源图形处理引擎。他位于android源码的/external/skia 目录。我们平时在java层使用一个图片处理的函数实际上底层就是调用了这个开源引擎中的相关的函数。
二、Android 中常用的压缩方式
Android中常用压缩方法分为2种:一种是降采样率压缩,另外一种是质量压缩
代码:
1.降采样率压缩的一般写法:

  public static Bitmap obtainImageFromPath(String path, int width, int height) {        BitmapFactory.Options o = new BitmapFactory.Options();        o.inJustDecodeBounds = true;        BitmapFactory.decodeFile(path, o);        o.inSampleSize = calculateSampleSize(o, width, height);        o.inJustDecodeBounds = false;        return BitmapFactory.decodeFile(path, o);    }    private static int calculateSampleSize(BitmapFactory.Options o, int reqWidth, int reqHeight) {        int sampleSize = 1;        if (o.outWidth > reqWidth || o.outHeight > reqHeight) {            final int halfWidth = o.outWidth / 2;            final int halfHeight = o.outHeight / 2;            while ((halfHeight / sampleSize) >= reqHeight                    && (halfWidth / sampleSize) >= reqWidth) {                sampleSize *= 2;            }        }        return sampleSize;    }

2.质量压缩的一般写法:

bitmap.compress(Bitmap.CompressFormat.JPEG, 20, new FileOutputStream("sdcard/result.jpg"));

三、libjpeg
我们使用质量压缩的话它的底层就是用skia引擎进行处理,加入我们调用bitmap.compress(Bitmap.CompressFormat.JPEG,…..) 他实际会 使用一个libjpeg.so 的动态库进行编码压缩。
android在进行jpeg压缩编码的时候,考虑到了效率问题使用了定长编码方式进行编码(因为当时的手机性能都比较低),而IOS使用了变长编码的算法——哈夫曼算法。而且IOS对skia引擎也做了优化。所有我们看到同样的图片在ios上压缩会好一点。

四、优化思路
上文我们知道之所以在android机进行质量压缩没有IOS上压缩好的原因,那么我们也就应该有了相应的优化思路。我们的思路如下:
1、下载开源的libjpeg,进行移植、编译得到libjpeg.so
2、使用jni编写一个函数用来图片压缩
3、在函数中添加一个开关选项,可以让我们选择是否使用哈夫曼算法。
4、打包,搞成sdk供我们以后使用。

五、实现
1、下载libjpeg 编译
使用git clone最新的android分支

git clone git://git.linaro.org/people/tomgall/libjpeg-turbo/libjpeg-turbo.git -b linaro-android

2、编译
记得配置好ndk,设置环境变量

1、把文件名变成 jnimv libjpeg-turbo jni2、编译ndk-build APP_ABI=armeabi-v7a,armeabi

3、使用AndroidStudio创建一个项目,记得勾选c++ support

4、加入编译好的 动态库,和头文件并且在在配置文件中引用

5、编写代码:

#include "compress.h"#include "lang.h"#include #include #include #include #define true 1#define false 0typedef 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, boolean optimize);const char *jstringToString(JNIEnv *env, jstring jstr);JNIEXPORT jintJNICALLJava_com_blueberry_compress_ImageCompress_nativeCompressBitmap(JNIEnv *env, jclass type,                                                               jobject bitmap, jint quality,                                                               jstring dstFile_,                                                               jboolean optimize) {    AndroidBitmapInfo androidBitmapInfo;    BYTE *pixelsColor;    int ret;    BYTE *data;    BYTE *tmpData;    const char *dstFileName = jstringToString(env, dstFile_);    //解码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, &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, quality, dstFileName, optimize);    free((void *) dstFileName);    free((void *) tmpData);    return ret;}int generateJPEG(BYTE *data, int w, int h, int quality, const char *name, 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);    //获取文件信息    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;}const char *jstringToString(JNIEnv *env, jstring jstr) {    char *ret;    const char *tempStr = (*env)->GetStringUTFChars(env, jstr, NULL);    jsize len = (*env)->GetStringUTFLength(env, jstr);    if (len > 0) {        ret = (char *) malloc(len + 1);        memcpy(ret, tempStr, len);        ret[len] = 0;    }    (*env)->ReleaseStringUTFChars(env, jstr, tempStr);    return ret;}

6、最后测试

我测试发现确实有些改善,使用同样的压缩等级,采用哈夫曼算法的话,会压缩的更小一些。

7、最后
代码地址:
https://github.com/blueberryCoder/Compress