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

推荐阅读更多精彩内容