性能优化--哈夫曼算法原理

大家都知道,使用哈夫曼压缩能达到无损压缩,也就是说。保证了原图质量的同时,能够降低图片的大小。这是什么原理呢?首先我们需要了解的是Android系统加载图片使用的是Skia加载库,当然这个库的底层还是是用的jpeg对图片进行压缩处理,但是为了能够适配低端的手机(这里的低端是指以前的硬件配置不高的手机,CPU和内存在手机上都非常吃紧 性能差),由于哈夫曼算法非常吃CPU,被迫用了其他的算法。所以Skia在进行图片处理并没有去使用到哈夫曼压缩。但是解码还是保留了哈夫曼算法,这就导致了图片处理后文件变大了。

一、原理

看一张图,如下:
在这里插入图片描述
这里解释一下,一张图片都是有argb组成,这经过哈夫曼压缩之前,可以将图片的a通道拿掉,剩下的 rgb 进行压缩。每一个通道的取值范围都是0~255,也就是说每种颜色都是有256种表现形态,这里以红色 r 为例。假如一张图片红色 r 共有6个等级,每个等级对应的像素点数如上图。那么根据哈夫曼算法会形成一个哈夫曼树。如下图:
在这里插入图片描述
哈夫曼树形成的规则,首先从6个等级中找出两个像素点最少的两个(1和10),放在末端,然后将两个像素点相加放在左端,然后再找出原像素点中较少的一个(100),放在右端,并用线将节点和分支连接起来。依次类推,直到找到顶点。其中左边的分支用0标记,右边的分支用1标记。

1个rgb对应3个字节,每一个字节都会形成一个像素表,像素表中存储每个像素等级对应的像素点数。比如根据哈夫曼树,我们如何找到红色的等级1的像素,我们从顶点出发,只需要在表中查找1,就可以找到5000,那么如何找到1000呢?查找01!如何查找500呢?查找001!如何查找100呢?0001?…等等。就是根据分支一直找下去。

优点:对于原始一个像素点占8位,对于5000个像素点,所有那个内存为8*5000 ,当使用哈夫曼压缩后,只要一位(比如1)就可以表示这5000个像素点。并且没有减少图片的像素点数,所以叫做无损压缩。另外,对于颜色值越单一的图片,压缩率越高。颜色值越多,树会越来越长,找到末端的像素点占用的位数会越来越多。所以它的压缩效率一般在20%~90%。

二、实现

1、导库

这里我们需要libjpeg在android环境下的so库,所以需要事先在linux环境下编译好。将编译好的so库放到项目中,如下:
在这里插入图片描述
其中x86支持的模拟器,arm64真机。导入需要支持的头文件在libjpeg目录下:
在这里插入图片描述

2、链接库

需要在CMakeList.txt文件中配置,如下;
在这里插入图片描述
红色框框的内容,就是需要添加的。

3、调用
/**
     * @param bitmap 要压缩的图片
     * @param path 压缩后存放的文件名称
     * @param quality 压缩质量
     */
    public native void compressImage(Bitmap bitmap, String path, int quality);

    public void compress(View view) {

        //sd卡目录下的一张图片
        File input = new File(Environment.getExternalStorageDirectory(), "timg750.png");
        inputBitmap = BitmapFactory.decodeFile(input.getAbsolutePath());

        //开始压缩
        compressImage(inputBitmap,Environment.getExternalStorageDirectory() + "/timg751.png",50);
    }

我在SD卡目录下有一张timg750.png的图片,压缩后调用jni的compressImage()方法,并且压缩后的文件存为SD卡的timg751.png,压缩质量为50。

4、压缩代码

(1)得到原始图片信息,将原始图片的argb的a通道信息移除,并存入到新的图片信息data(包含r、g、b)中。

extern "C"
JNIEXPORT void JNICALL
Java_com_xinyartech_myhuffman_MainActivity_compressImage(JNIEnv *env, jobject instance,
                                                        jobject bitmap, jstring path_,
                                                        jint q) {

    const char *path = env->GetStringUTFChars(path_, 0);
    //从bitmap获取argb数据
    AndroidBitmapInfo info;//info=new 对象();
    //获取里面的信息
    AndroidBitmap_getInfo(env, bitmap, &info);//  void method(list)
    //得到图片中的像素信息
    uint8_t *pixels;//uint8_t char    java   byte     *pixels可以当byte[]
    AndroidBitmap_lockPixels(env, bitmap, (void **) &pixels);
    //jpeg argb中去掉他的a ===>rgb
    int w = info.width;
    int h = info.height;
    int color;
    //开一块内存用来存入rgb信息
    uint8_t* data = (uint8_t *) malloc(w * h * 3);//data中可以存放图片的所有内容
    uint8_t* temp = data;
    uint8_t r, g, b;//byte
    //循环取图片的每一个像素
    for (int i = 0; i < h; i++) {
        for (int j = 0; j < w; j++) {
            color = *(int *) pixels;//0-3字节  color4 个字节  一个点
            //取出rgb
            r = (color >> 16) & 0xFF;//    #00rrggbb  16  0000rr   8  00rrgg
            g = (color >> 8) & 0xFF;
            b = color & 0xFF;
            //存放,以前的主流格式jpeg    bgr
            *data = b;
            *(data + 1) = g;
            *(data + 2) = r;
            data += 3;
            //指针跳过4个字节
            pixels += 4;
        }
    }
    //把得到的新的图片的信息存入一个新文件 中
    write_JPEG_file(temp, w, h, q, path);
    //释放内存
    free(temp);
    AndroidBitmap_unlockPixels(env, bitmap);
    env->ReleaseStringUTFChars(path_, path);
}

(2)将新的图片信息存入到新的文件中(就是 timg751.png )

void write_JPEG_file(uint8_t *data, int w, int h, jint q, const char *path) {
//    3.1、创建jpeg压缩对象
    jpeg_compress_struct jcs;
    //错误回调
    jpeg_error_mgr error;
    jcs.err = jpeg_std_error(&error);
    //创建压缩对象
    jpeg_create_compress(&jcs);
//    3.2、指定存储文件  write binary
    FILE *f = fopen(path, "wb");
    jpeg_stdio_dest(&jcs, f);
//    3.3、设置压缩参数
    jcs.image_width = w;
    jcs.image_height = h;
    //bgr
    jcs.input_components = 3;
    jcs.in_color_space = JCS_RGB;
    jpeg_set_defaults(&jcs);
    //开启哈夫曼功能
    jcs.optimize_coding = true;
    //设置压缩质量
    jpeg_set_quality(&jcs, q, 1);
//    3.4、开始压缩
    jpeg_start_compress(&jcs, 1);
//    3.5、循环写入每一行数据
    int row_stride = w * 3;//一行的字节数
    JSAMPROW row[1];
    while (jcs.next_scanline < jcs.image_height) {
        //取一行数据
        uint8_t *pixels = data + jcs.next_scanline * row_stride;
        row[0]=pixels;
        jpeg_write_scanlines(&jcs,row,1);
    }
//    3.6、压缩完成
    jpeg_finish_compress(&jcs);
//    3.7、释放jpeg对象
    fclose(f);
    jpeg_destroy_compress(&jcs);
}

这里面的关键代码为 jcs.optimize_coding = true;将开启哈夫曼压缩。

5、压缩结果

在这里插入图片描述
压缩前的大小为521kb,压缩后38kb。但是图片完全看不出差异。

三、注意

  • 动态库的环境配置地址一定要正确
  • app要配置好文件的读写权限
  • 我的环境是根据 AS 3.5构建。
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值