1. 程式人生 > >Android使用libjpeg實現圖片壓縮——非常具有參考意義

Android使用libjpeg實現圖片壓縮——非常具有參考意義

http://blog.csdn.net/a992036795/article/details/53814178

一、Android中使用的圖片壓縮庫 
androidiOS 中圖片處理使用了一個叫做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; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

2.質量壓縮的一般寫法:

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

三、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
  • 1
  • 2
  • 1
  • 2

2、編譯 
記得配置好ndk,設定環境變數

1、把檔名變成 jni
mv libjpeg-turbo jni
2、編譯
ndk-build APP_ABI=armeabi-v7a,armeabi
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

3、使用AndroidStudio建立一個專案,記得勾選c++ support

4、加入編譯好的 動態庫,和標頭檔案並且在在配置檔案中引用

5、編寫程式碼:

#include "compress.h"
#include "lang.h"

#include <android/bitmap.h>
#include <setjmp.h>
#include <jpeglib.h>


#include <stdlib.h>


#define true 1
#define false 0


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, boolean optimize);

const char *jstringToString(JNIEnv *env, jstring jstr);

JNIEXPORT jint

JNICALL
Java_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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180

6、最後測試

我測試發現確實有些改善,使用同樣的壓縮等級,採用哈夫曼演算法的話,會壓縮的更小一些。