Android图像文件压缩----libjpeg库的编码压缩

541 阅读3分钟

对本地图片文件进行压缩,减小文件大小,降低图片上传流量和服务器带宽压力。 libjpeg库的编译就不介绍了,网上很多,搞一个linux的VM,下载安装cmake和libjpeg库的gz压缩包,关于编译脚本,libjpeg的GitHub上有写的很详细了。

  • 通过libjpeg库来压缩优化照片的步骤:

    1. 读取照片的bitmap像素信息。
    2. 去除照片的Alpha通道,只保留RGB像素信息。
    3. 创建jpeg压缩对象
    4. 设置参数,开启哈夫曼压缩编码算法。(压缩质量通常设置为30 ~ 50,太小了图片会失真)
    5. 指定保存文件
    6. 开始压缩
    7. 逐行扫描图片像素字节写入文件中
    8. 完成压缩,释放相应jpeg对象资源和文件资源。
  • 在Android中通过NDK的具体实现如下:

    const char *outPath = env->GetStringUTFChars(out_path, 0);

    // 获取到bitmap的argb像素数据
    AndroidBitmapInfo bitmapInfo;
    AndroidBitmap_getInfo(env, bitmap, &bitmapInfo);
    // 获取图片中的像素信息
    uint8_t *pixels;// uint8_t表示无符号的char就是java的byte,加指针就是byte数组
    // 1. 锁定图片的像素值
    AndroidBitmap_lockPixels(env, bitmap, (void **) &pixels);
    // 2. 去除图片的alpha像素通道: ARGB ---> RGB
    int w = bitmapInfo.width;
    int h = bitmapInfo.height;
    int color;
    // 开辟一块内存用于存储rgb像素信息,一个像素有rgb三个通道,所以*3
    uint8_t *data = (uint8_t *) malloc(w * h * 3);
    uint8_t *temp = data;
    uint8_t r, g, b; // 用于存储rgb的颜色信息
    for (int i = 0; i < h; i++) {
        for (int j = 0; j < w; j++) {
            color = *(int *) pixels; // 0~3字节,总共一个像素4个字节
            r = (color >> 16) && 0xFF;
            g = (color >> 8) && 0xFF;
            b = color && 0xFF;
            // 通过bgr的形式存放每个像素数据
            *data = b;
            *(data + 1) = g;
            *(data + 2) = r;
            data += 3; // 指针移动到下一个像素该存放的位置
            pixels += 4; // 指针移动到下一个像素,图片原来的像素值还有alpha通道字节,所以是+4个字节位置
        }
    }

    // 3. 把新的图片信息,存储写入文件中
    write_image_file(temp, w, h, quality, outPath);

    // 解锁bitmap对象
    AndroidBitmap_unlockPixels(env, bitmap);
    // 释放开辟的存储图片像素内存地址
    free(data);
void write_image_file(uint8_t *data, int w, int h, jint quality, const char *outpath) {
    // 1. 创建jpeg压缩对象
    jpeg_compress_struct jcs;
    jpeg_error_mgr errorMgr;
    jcs.err = jpeg_std_error(&errorMgr); // 设置错误回调
    jpeg_create_compress(&jcs);
    // 2. 指定存储文件, wb ===> write binary写入二进制
    FILE *file = fopen(outpath, "wb");
    jpeg_stdio_dest(&jcs, file);  // 把压缩的对象信息,写入到file这个文件中
    // 3. 设置压缩参数
    jcs.image_width = w;
    jcs.image_height = h;
    jcs.input_components = 3;
    jcs.jpeg_color_space = JCS_RGB;
    jpeg_set_defaults(&jcs);
    jcs.optimize_coding = true;   // =====> 开启哈夫曼编码算法
    jpeg_set_quality(&jcs, quality, true);  // 设置图片压缩质量
    // 4. 开始压缩
    jpeg_start_compress(&jcs, true);
    // 5. 循环写入每一行数据
    int row_stride = w * 3;  // 一行像素数据的总字节数
    JSAMPROW row[1];
    // jcs.next_scanline 表示当前扫描第几行像素数据
    // jcs.image_height 表示图片像素的总行数
    while(jcs.next_scanline < jcs.image_height) {
        // 取一行的像素数据
        uint8_t *row_pixels = data + jcs.next_scanline * row_stride;
        row[0] = row_pixels;
        jpeg_write_scanlines(&jcs, row, 1); // 写入一行像素数据到jcs对象中
    }
    // 6. 压缩完成
    jpeg_finish_compress(&jcs);
    // 7. 释放jpeg对象
    fclose(file);
    jpeg_destroy_compress(&jcs);
}

这里是使用底层NDK的方式进行压缩,通过JNI接口。最核心的就是两点:

  1. 把原始的bitmap图片像素,过滤掉alpha通道,通常手机拍摄的照片是不需要alpha通道,去掉alpha通道字节后,图片占用缩小了1/4。
  2. 再通过libjpeg库,开启哈夫曼编码算法,且设置图片压缩的质量,再进一步去优化压缩图片。通常压缩质量设置为:30 ~ 50,比较合适,既保证了压缩率,又保证了图像不过分失真,稳定性较好。关于哈夫曼编码算法的逻辑介绍可以参考这篇博文:数据压缩----哈夫曼编码