用ndk实现android的图片压缩

最近在看了动脑学院的图片优化之后,感觉通过ndk来实现的压缩,压缩比例还是很好的

我们知道android的图片显示离不开bitmap,而在android开发中避免不了对图片的压缩处理,当然bitmap有自带的压缩方法,调用bitmap的compress方法就可以实现简单的压缩,但是这种压缩出来的图片从清晰度上来说,效果不是很好,原因在于这种压缩的底层处理是由google的skia图片处理引擎来做的,而skia库又是基于jpeg库,我们都知道libjpeg是一个被广泛使用的JPEG压缩/解压缩函数库,应用程序可以每次从JPEG压缩图像中读取一个或多个扫描线(scanline,所谓扫描线,是指由一行像素点构成的一条图像线条),而诸如颜色空间转换、降采样/增采样、颜色量化之类的工作则都由libjpeg去完成的;但是skia在开发的时候阉割来jpeg的一部分功能,使得android原有的压缩方法不尽人意,下面通过代码来实现一个脱离android 底层的skia库,基于ndk libjpeg压缩的图片压缩方案。

导入相关的依赖库

由于要使用到libjpeg相关的函数,所以要导入相关的头文件与so文件, 由于libjpeg也是在android源码里面的,找到android源码中的/external/libjpeg-turbo/jpeglib.h头文件复制到android studio 的cpp目录下面,当然也可以在cpp下面新建一个include目录将头文件放进去也行,然后将jpeglib.h里面依赖的其他头文件都引入进来,接着就是进入libjpeg.so文件,我这里是将so放在来app目录下的libs目录下面
头文件与so文件目录
当然这个目录不是必须的,放在其他目录也行;然后用cmake将头文件与目录的路径引入就行了;

cmake的配置

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

include_directories( src/main/cpp/ )

add_library(jpeg-and SHARED IMPORTED)
set_target_properties(jpeg-and
  PROPERTIES IMPORTED_LOCATION
  ${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libjpeg.so)

find_library(
              log-lib
              log )

#添加jnigraphics,c++代码中使用到了AndroidBitmap,所以需要添加jnigrapics库
find_library(
              andbitmap
              jnigraphics )

add_library(
             native-lib
             SHARED
             src/main/cpp/native-lib.cpp )

target_link_libraries( # Specifies the target library.
                       native-lib
                       ${andbitmap}
                       jpeg-and
                       ${log-lib} )

实现图片压缩

这里我们在java层声明一个native方法

    public native void nativeCompress(Bitmap inputBitmap, String absolutePath);

该方法传入原图的bitmap与输出压缩文件的路径,然后在jni层实现压缩代码

#include <jni.h>
#include <string>
#include <android/bitmap.h>
#include <android/log.h>
#include <malloc.h>

extern "C" {
    #include "jpeglib.h"
}

typedef uint8_t BYTE;

void writeImg(BYTE *data, const char *path, int w, int h) {
    //压缩

    //-------------初始化----------------
    struct jpeg_compress_struct jpeg_struct;

    struct jpeg_error_mgr err;
    jpeg_create_compress(&jpeg_struct);

    jpeg_struct.err = jpeg_std_error(&err);

    FILE *file = fopen(path, "wb");
    //设置输出路径
    jpeg_stdio_dest(&jpeg_struct, file);

    //设置宽高
    jpeg_struct.image_height = h;
    jpeg_struct.image_width = w;


    //重要的地方,skia阉割的地方
    jpeg_struct.arith_code = FALSE;
    jpeg_struct.optimize_coding = true;
    jpeg_struct.in_color_space = JCS_RGB;

    jpeg_struct.input_components = 3;
    //设置压缩质量

    jpeg_set_defaults(&jpeg_struct);
    jpeg_set_quality(&jpeg_struct, 20, TRUE);

    //-------------开始压缩----------------
    jpeg_start_compress(&jpeg_struct, TRUE);

    //-------------写入数据----------------

    //记录一行的首地址
    JSAMPROW row_pointer[1];
    //一行的rgb数量
    int row_stride = jpeg_struct.image_width * 3;

    while (jpeg_struct.next_scanline < jpeg_struct.image_height){

        row_pointer[0] = &data[jpeg_struct.next_scanline * row_stride];
        jpeg_write_scanlines(&jpeg_struct, row_pointer, 1);
    }

    jpeg_finish_compress(&jpeg_struct);
    jpeg_destroy_compress(&jpeg_struct);
    fclose(file);

}

extern "C"
JNIEXPORT void JNICALL
Java_com_kangxin_doctor_bitmaphufdemo_MainActivity_nativeCompress(JNIEnv *env, jobject instance,
                                                                  jobject inputBitmap,
                                                                  jstring absolutePath_) {
    const char *absolutePath = env->GetStringUTFChars(absolutePath_, 0);

//压缩所需要的原材料->bitmap 像素数组  rgb数组

    BYTE *pixels;

    AndroidBitmapInfo bitmapInfo;
    AndroidBitmap_getInfo(env, inputBitmap, &bitmapInfo);
    //将bitmap转换成pixels的数组
    AndroidBitmap_lockPixels(env, inputBitmap, reinterpret_cast<void **>(&pixels));

    int h = bitmapInfo.height;
    int w = bitmapInfo.width;

    int i = 0;
    int j = 0;

    int color;

    BYTE *data = NULL, *tmpData = NULL;

    data = static_cast<BYTE *>(malloc(w * h * 3));
    tmpData = data;

    BYTE r, g, b;

    for (int i = 0; i < h; ++i) {
        for (int j = 0; j < w; ++j) {
            color = *((int *)pixels);
            r = ((color & 0x00FF0000) >> 16);
            g = ((color & 0x0000FF00) >> 8);
            b = (color & 0x000000FF);


            *data = b;
            *(data + 1) = g;
            *(data + 2) = r;

            data += 3;
            pixels += 4;


        }
    }


    AndroidBitmap_unlockPixels(env, inputBitmap);

    //jpeg 压缩
    writeImg(tmpData, absolutePath, w, h);

    env->ReleaseStringUTFChars(absolutePath_, absolutePath);
}

首先我们通过bitmap函数将bitmap转换成了rgb像素数组,然后通过循环位移计算将每个像素里面的rgb取出来,以bgr的顺序存到了data容器里面,然后调用writeImg函数进行压缩,在这个函数中,一开始就进行了压缩的一些处理话操作,在这里要注意的是 jpeg_struct.arith_code = FALSE;

这个很重要,这个字段在头文件里面是这样描述的boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */
由此可见这个字段应该是设置图片压缩的编码方式,TRUE的话是arithmetic coding编码,FALSE的话是Huffman 而Huffman就是著名的哈夫曼编码,Huffman编码用的最普遍的就是在压缩领域,它的压缩效果相当的好;而android 底层的skia库将这个字段设置成了TRUE 也就是说skia的压缩不是用的哈夫曼编码压缩,所以这也就是android原生压缩出来的图片不尽人意的原因所在。

下面就是我经过测试压缩出来的原图与压缩图的对比

原图-大小397kb

压缩图

经过对比,原图大小是397kb, 压缩之后的图片大小是58kb

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章