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阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容