对本地图片文件进行压缩,减小文件大小,降低图片上传流量和服务器带宽压力。 libjpeg库的编译就不介绍了,网上很多,搞一个linux的VM,下载安装cmake和libjpeg库的gz压缩包,关于编译脚本,libjpeg的GitHub上有写的很详细了。
-
通过libjpeg库来压缩优化照片的步骤:
- 读取照片的bitmap像素信息。
- 去除照片的Alpha通道,只保留RGB像素信息。
- 创建jpeg压缩对象
- 设置参数,开启哈夫曼压缩编码算法。(压缩质量通常设置为30 ~ 50,太小了图片会失真)
- 指定保存文件
- 开始压缩
- 逐行扫描图片像素字节写入文件中
- 完成压缩,释放相应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接口。最核心的就是两点:
- 把原始的bitmap图片像素,过滤掉alpha通道,通常手机拍摄的照片是不需要alpha通道,去掉alpha通道字节后,图片占用缩小了1/4。
- 再通过libjpeg库,开启哈夫曼编码算法,且设置图片压缩的质量,再进一步去优化压缩图片。通常压缩质量设置为:30 ~ 50,比较合适,既保证了压缩率,又保证了图像不过分失真,稳定性较好。关于哈夫曼编码算法的逻辑介绍可以参考这篇博文:数据压缩----哈夫曼编码