Android 圖片壓縮

參考

Android 圖片壓縮

此篇文章不討論質量壓縮,比例壓縮與采樣率壓縮,只考慮使用libjpeg,繞過andriod api層,還原哈夫曼算法

先給個結果吧,一張
原圖
圖片,經過這種壓縮方法,能壓縮到
壓縮后

,失真不明顯

首先得編譯出libjpeg.so文件
如下:

網上說得很復雜,其實很簡單
  1. 使用git命令:git clone git://git.linaro.org/people/tomgall/libjpeg-turbo/libjpeg-turbo.git -b linaro-android克隆最新的分支
  2. 下載下來后把文件夾libjpeg-turbo重命名為jnimv libjpeg-turbo jni
  3. cd到jni目錄下ndk-build APP_ABI=armeabi-v7a,armeabi
  4. 有可能出現錯誤:
/root/jpge/jni/Android.mk:11: Extraneous text after `ifeq' directive
Android NDK: WARNING: Unsupported source file extensions in /root/jpge/jni/Android.mk for module jpeg    
Android NDK:   turbojpeg-mapfile    
/root/dev/android-ndk-r14b/build/core/build-binary.mk:687: Android NDK: Module jpeg depends on undefined modules: cutils    
/root/dev/android-ndk-r14b/build/core/build-binary.mk:700: *** Android NDK: Aborting (set APP_ALLOW_MISSING_DEPS=true to allow missing dependencies)    .  Stop.
  1. 在網上搜了下,不少人遇到此問題,解決方法都是回退版本,如ndk r10就可以解決問題了?但是這樣的回退是有代價的,像不能使用c++11的東西,std::to_string()那么好用的函數都不能用,多么可惜,啰嗦那么多,解決方案是:
    在Android.mk中設置APP_ALLOW_MISSING_DEPS :=true
  2. 再執行ndk-build就可以了,然后在jni的同級目錄出現libs與obj包


然后在libs目錄下放入so庫,開始native方法
考慮到文件夾可能不存在的情況,而在c中拆分字符串難免有些復雜,我們可以先處理了后傳入

public class CompressUtil {
    static {
        System.loadLibrary("compress");
    }

    private CompressUtil() {
    }

    /**
     * 使用native方法進行圖片壓縮。
     * Bitmap的格式必須是ARGB_8888 {@link Bitmap.Config}。
     *
     * @param bitmap   圖片數據
     * @param quality  壓縮質量
     * @param dstFile  壓縮后存放的路徑
     * @param optimize 是否使用哈夫曼算法
     * @return 是否成功
     */
    public static boolean nativeCompressBitmap(Bitmap bitmap, int quality, String dstFile, boolean optimize) {
        String dirPath = dstFile.substring(0, dstFile.lastIndexOf(File.separator));
        return nativeCompress(bitmap, quality, dstFile, dirPath, optimize) == 1;
    }

    /**
     * 使用native方法進行圖片壓縮。
     * Bitmap的格式必須是ARGB_8888 {@link Bitmap.Config}。
     *
     * @param bitmap   圖片數據
     * @param quality  壓縮質量
     * @param dstFile  壓縮后存放的路徑
     * @param dirPath  文件存放的目錄
     * @param optimize 是否使用哈夫曼算法
     * @return 成功為1,不為argb_8888為-1,失敗為0
     */
    private static native int nativeCompress(Bitmap bitmap, int quality, String dstFile, String dirPath, boolean optimize);

}

生成cpp文件
先寫CMakeLists文件

cmake_minimum_required(VERSION 3.4.1)

set(distribution_DIR ../../../../libs)
set(SOURCE_FILES src/main/cpp/compress.cpp)
set(INC_DIR src/main/cpp/include)

include_directories(${INC_DIR})

find_library(   log-lib
                log)
find_library(graphics jnigraphics)

add_library(    libjpeg
                SHARED
                IMPORTED)

set_target_properties(  libjpeg
                        PROPERTIES IMPORTED_LOCATION
                        ${distribution_DIR}/${ANDROID_ABI}/libjpeg.so)

add_library( compress
             SHARED
             ${SOURCE_FILES} )

target_link_libraries( # Specifies the target library.
                       compress
                       libjpeg
                       ${graphics}
                       ${log-lib} )

導入jpeg的頭文件以及開始寫cpp
cpp如下:

#include <jni.h>
#include <string>
#include <stdlib.h>
#include <unistd.h>
#include <setjmp.h>
#include <dirent.h>
#include <sys/stat.h>

#include <android/bitmap.h>
#include <android/log.h>
extern "C" {
//因為c++編譯器會對函數名進行重整。不然會提示找不到函數名
#include <jpeglib.h>
}
#define TAG "COMPRESS"

#define LOGI(FORMAT, ...) __android_log_print(ANDROID_LOG_INFO,TAG,FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT, ...) __android_log_print(ANDROID_LOG_ERROR,TAG,FORMAT,##__VA_ARGS__);
#define LOGW(FORMAT, ...) __android_log_print(ANDROID_LOG_WARN,TAG,FORMAT,##__VA_ARGS__);
#define LOGD(FORMAT, ...) __android_log_print(ANDROID_LOG_DEBUG,TAG,FORMAT,##__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;

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, const char *dir_path,
                 boolean optimize);


int generateJPEG(BYTE *data, int w, int h, int quality, const char *name, const char *dir_path,
                 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);
    //獲取文件信息
    if (0 != access(dir_path, 0)) {//目錄不存在
        LOGD("file is not exist");
        if (0 != mkdir(dir_path, 777)) {
            LOGE("make dir fail")
            return 0;
        }
    }
    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;
}

/*
 * Class:     github_com_androidadvanced_ndk_util_ImageUtil
 * Method:    compressBitmap
 * Signature: (Ljava/lang/Object;ILjava/lang/String;B)I
 */
extern "C"
JNIEXPORT jint JNICALL Java_com_apkcore_compress_CompressUtil_nativeCompress
        (JNIEnv *env, jclass clazz, jobject bitmap, jint quality, jstring dstFile_,
         jstring dirPath_, jboolean optimize) {

    LOGD("%s", "===>Java_com_apkcore_commpress_CompressUtil_nativeCompressBitmap");
    int ret;
    AndroidBitmapInfo bitmapInfo;
    //像素點argb
    BYTE *pixelsColor;
    //bitmap 數據
    BYTE *data;
    BYTE *tmpData;


    //獲取android bitmap 信息
    if ((ret = AndroidBitmap_getInfo(env, bitmap, &bitmapInfo)) < 0) {
        LOGD("AndroidBitmap_getInfo() failed error=%d", ret);
        return ret;
    }

    //鎖定bitmap,獲取像素點argb,存儲到pixelsColor中
    if ((ret = AndroidBitmap_lockPixels(env, bitmap, (void **) &pixelsColor)) < 0) {
        LOGD("AndroidBitmap_lockPixels() failed error=%d", ret);
        return ret;
    }

    BYTE r, g, b;
    int color;
    //獲取圖片信息
    int w, h, format;
    w = bitmapInfo.width;
    h = bitmapInfo.height;
    format = bitmapInfo.format;
    //只處理 RGBA_8888
    if (format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
        LOGD("AndroidBitmapInfo  format  is not ANDROID_BITMAP_FORMAT_RGBA_8888 error=%d", ret);
        return -1;
    }

    LOGD("bitmap: width=%d,height=%d,size=%d , format=%d ", w, h, w * h, bitmapInfo.format);

    //分配內存(存放bitmap rgb數據)
    data = (BYTE *) malloc(w * h * 3);
    //保存內存首地址
    tmpData = data;

    //將bitmap轉rgb
    int i = 0;
    int j = 0;
    for (i = 0; i < h; ++i) {
        for (j = 0; j < w; ++j) {
            color = (*(int *) (pixelsColor));
            // 這里取到的顏色對應的 A B G R  各占8位 0xffffffff--->0xaarrggbb
            b = (color >> 16) & 0xFF;
            g = (color >> 8) & 0xFF;
            r = (color >> 0) & 0xFF;
            *data = r;
            *(data + 1) = g;
            *(data + 2) = b;

            data += 3;
            pixelsColor += 4;
        }
    }

    AndroidBitmap_unlockPixels(env, bitmap);

    //進行壓縮后文件存放的地址
    const char *file_path = env->GetStringUTFChars(dstFile_, NULL);
    const char *dir_path = env->GetStringUTFChars(dirPath_, NULL);

    //壓縮圖片
    ret = generateJPEG(tmpData, w, h, quality, file_path, dir_path, optimize);

    //釋放內存
    free((void *) tmpData);
    env->ReleaseStringUTFChars(dstFile_, file_path
    );
    env->ReleaseStringUTFChars(dirPath_, dir_path
    );

    //釋放java-->bitmap
    jclass jBitmapClass = env->GetObjectClass(bitmap);
    jmethodID jRecycleMethodId = env->GetMethodID(jBitmapClass, "recycle", "()V");
    env->CallVoidMethod(bitmap, jRecycleMethodId,NULL);

    //成功為1,不為argb_8888為-1,失敗為0
    return ret;
}

如何打包成jar以及引用so庫

就可以使用as自帶的工具很好打包,如下圖



在other中,一個為打jar一個為打so,打出來的so不包括引用的libjpeg.so,要注意一下




Github

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,763評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,238評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,823評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,604評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,339評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,713評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,712評論 3 445
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,893評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,448評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,201評論 3 357
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,397評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,944評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,631評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,033評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,321評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,128評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,347評論 2 377

推薦閱讀更多精彩內容