文章目录
一、使用 libjpeg-turbo 压缩图片流程二、初始化 JPEG 压缩对象三、打开文件四、设置压缩参数五、开始压缩六、循环写入压缩数据七、完成图片压缩及收尾八、libjpeg-turbo 图片压缩案例 ( 官方示例 )九、libjpeg-turbo 压缩图片代码示例上一篇博客 【Android 内存优化】Android 工程中使用 libjpeg-turbo 压缩图片 ( JNI 传递 Bitmap | 获取位图信息 | 获取图像数据 | 图像数据过滤 | 释放资源 ) 介绍了从 Java 层传入 Bitmap 对象到 JNI 层 , JNI 层获取到了图像对应的 RGB 像素数据 , 本篇博客中将获取的图像数据进行压缩 , 存储到 JPEG 格式图片中 ;
一、使用 libjpeg-turbo 压缩图片流程
使用 libjpeg-turbo 压缩图片流程 :
① 初始化压缩对象 :初始化 JPEG 图片压缩对象 ;
② 打开文件 :使用 Linux C API 打开压缩图片写出文件 ;
③ 设置压缩参数 :设置图片压缩参数 , 如图片宽高 , 像素格式 , 数据格式 , 质量等 ;
④ 开始压缩 :启动压缩 ;
⑤ 写入压缩数据 :图像数据逐行输入 , 并压缩 ;
⑥ 压缩完毕 :压缩完毕后调用对应方法 ;
⑦ 释放资源 :文件资源 , 及压缩相关的内存资源 , 需要释放掉 ;
二、初始化 JPEG 压缩对象
1. 初始化 JPEG 压缩对象 :
① JPEG 压缩对象概念 :jpeg_compress_struct 结构体和与其关联的工作数据 , 该对象中存储了 JPEG 压缩参数 , 还包含了指向工作空间的指针 , JPEG 库会在需要时分配该指针;
② 压缩对象个数 :该结构体可能会存在多个 , 每个结构体对象都表示了一个压缩或解压缩的工作;
2. 错误处理机制 :
① 错误处理程序 :jpeg_error_mgr 结构体表示错误处理程序 , 将其单独定义成一个结构体 , 是因为应用经常需要提供一个专门的错误处理程序;
② 处理处理机制 :在这里我们采用最简单的方法 , 使用标准的错误处理程序 , 如果 压缩失败 , 在 stderr 上打印失败信息, 并调用 exit() 退出程序 ;
③ 结构体生命周期 :该结构体的生命周期必须与 jpeg_compress_struct 结构体的生命周期保持一致 , 以免产生野指针问题 ;
④ 错误处理设置时间 :在所有操作之前 , 设置错误处理程序 , 为了防止 JPEG 压缩对象初始化时出错, 越早设置错误处理程序越好 , 在内存不足时, 创建 jpeg_compress_struct 可能会失败 ;
2. 代码示例 :
/* 该对象中存储了 JPEG 压缩参数, 还包含了指向工作空间的指针, JPEG 库会在需要时分配该指针;* 该结构体可能会存在多个, 每个结构体对象都表示了一个压缩或解压缩的工作;* JPEG 对象 : jpeg_compress_struct 结构体和与其关联的工作数据*/struct jpeg_compress_struct cinfo;/* 错误处理程序 : jpeg_error_mgr 结构体表示错误处理程序,* 将其单独定义成一个结构体, 是因为应用经常需要提供一个专门的错误处理程序;* 处理处理机制 : 在这里我们采用最简单的方法, 使用标准的错误处理程序,* 如果压缩失败, 在 stderr 上打印失败信息, 并调用 exit() 退出程序 ;* 结构体声明周期 : 该结构体的生命周期必须与 jpeg_compress_struct 结构体的生命周期保持一致,* 以免产生野指针问题 ;*/struct jpeg_error_mgr jerr;/* 为了防止 JPEG 压缩对象初始化时出错, 这里首先设置错误处理* 在内存不足时, 创建 jpeg_compress_struct 可能会失败*/cinfo.err = jpeg_std_error(&jerr);// 初始化 JPEG 压缩对象jpeg_create_compress(&cinfo);
三、打开文件
1. 打开文件 :使用 Linux C 中的文件操作 , 调用 fopen 函数打开文件 , 传入两个参数 , 文件路径名称 , 和 打开模式 , 打开模式中 “wb” , w 代表写出数据 , b 代表二进制数据 , 该模式的函数以是写出二进制数据 ;
2. 为 JPEG 压缩对象设置文件输出 :调用 jpeg_stdio_dest 函数 , 为 JPEG 对象设置输出文件 ; 调用该函数的调用者需要负责文件打开 , 和文件关闭操作 ;
EXTERN(void) jpeg_stdio_dest(j_compress_ptr cinfo, FILE *outfile);
3. 代码示例 :
FILE *outfile;if ((outfile = fopen(filename, "wb")) == NULL) {fprintf(stderr, "can't open %s\n", filename);exit(1);}// 设置文件输出jpeg_stdio_dest(&cinfo, outfile);
四、设置压缩参数
1 . 设置默认参数 :
图片宽度 :cinfo.image_width ; 像素宽度 ;图片高度 :cinfo.image_height ; 像素高度 ;像素组件 :cinfo.input_components ; 单个像素 BGR 3个组件 ;颜色空间 :cinfo.in_color_space ; 输入图像的颜色空间 ;
上述四个参数设置完毕后 , 调用 jpeg_set_defaults 方法 , 设置默认参数 ;
2 . 设置非默认参数 :
哈夫曼编码 :cinfo.optimize_coding = TRUE;编码质量 :调用 jpeg_set_quality 方法设置压缩质量 ;
// 下面的四个参数是必须设置的参数// 设置图片的宽度cinfo.image_width = imageWidth;// 设置图片的高度cinfo.image_height = imageHeight;// 设置每个像素的颜色组件, BGR 3个cinfo.input_components = 3;// 输入图像数据的颜色空间cinfo.in_color_space = JCS_RGB;// 设置默认的压缩参数, 该操作是函数库的常规步骤// 设置该参数前需要设置 cinfo.in_color_space 输入数据的颜色空间jpeg_set_defaults(&cinfo);// 打开哈夫曼编码cinfo.optimize_coding = TRUE;// 设置非默认参数, 该方法设置质量jpeg_set_quality(&cinfo, compressQuality, 1);
五、开始压缩
1 . 开始压缩 :调用 jpeg_start_compress 方法 , 开始进行图片压缩工作 ;
2 . 函数原型 :
j_compress_ptr cinfo 参数 :jpeg_compress_struct 结构指针 ;boolean write_all_tables 参数 :设置 TRUE 参数, 表示将完整的图片进行压缩 ; 一般情况下都是设置 TRUE, 如果进行定制压缩, 可以设置 FALSE ;
typedef struct jpeg_compress_struct *j_compress_ptr;EXTERN(void) jpeg_start_compress(j_compress_ptr cinfo,boolean write_all_tables);
3 . 代码示例 :
// 4. 开始压缩 JPEG 格式图片, 设置 TRUE 参数, 表示将完整的图片进行压缩// 一般情况下都是设置 TRUE, 如果进行定制压缩, 可以设置 FALSEjpeg_start_compress(&cinfo, TRUE);
六、循环写入压缩数据
1 . 写入压缩数据原理 :使用函数库的状态变量, cinfo.next_scanline 作为循环控制变量 , 这样就可以不同自己实现循环控制 , 为了保持代码简单, 每次传递一行图像数据 ;
2 . 计算每行数据字节数 :像素宽度乘以 333 , 333 表示每个像素点有 BGR 三个颜色值 , 每个颜色 111 字节 ;
int row_stride = imageWidth * 3;
3 . 计算每次循环拷贝的行数据首地址 :uint8_t *pixels = data + cinfo.next_scanline * row_stride 是计算过程 ;
data 是图像的起始位置row_stride 是每一行的字节数cinfo.next_scanline 是当前的行数计算出来的 pixels 指针, 指向要写出行的首地址
4 . 循环控制变量自增 :jpeg_write_scanlines(&cinfo, row, 1) , 调用 jpeg_write_scanlines 方法后, cinfo.next_scanline 自动加 1 ;
5 . 代码示例 :
// 每一个行的数据个数int row_stride = imageWidth * 3;// 指向图像数据中的某一行数据JSAMPROW row[1];while (cinfo.next_scanline < cinfo.image_height) {/* 获取一行图像数据* data 是图像的起始位置* row_stride 是每一行的字节数* cinfo.next_scanline 是当前的行数* 计算出来的 pixels 指针, 指向要写出行的首地址*/uint8_t *pixels = data + cinfo.next_scanline * row_stride;row[0] = pixels;// 调用 jpeg_write_scanlines 方法后, cinfo.next_scanline 自动加 1jpeg_write_scanlines(&cinfo, row, 1);}
七、完成图片压缩及收尾
1 . 完成图片压缩及收尾 :
调用 jpeg_finish_compress 结束图片压缩过程 ;
调用 fclose 关闭之前 fopen 打开的文件 ;
调用 jpeg_destroy_compress 方法销毁之前使用的 JPEG 压缩对象 ;
2 . 代码示例 :
// 6. 完成图片压缩jpeg_finish_compress(&cinfo);// 7. 释放相关资源fclose(outfile);jpeg_destroy_compress(&cinfo);
八、libjpeg-turbo 图片压缩案例 ( 官方示例 )
在源码 libjpeg-turbo-2.0.5/example.txt 文件中 , 有详细的 JPEG 图片压缩流程 , 可以直接拷贝上述代码进行使用 ;
点击此处连接打开官方示例代码
九、libjpeg-turbo 压缩图片代码示例
/*** 压缩 Jpeg 图片** 完整的带详细注释的代码示例参考源码 libjpeg-turbo-2.0.5/example.txt 示例文件* 里面有详细的定义图片压缩的过程** @param data要压缩的图片数据, 像素格式是 BGR* @param imageWidth输出的 JPEG 图片宽度* @param imageHeight 输出的 JPEG 图片高度* @param compressQuality 输出的 JPEG 图片质量* @param filename 输出文件路径*/void compressJpegFile(uint8_t *data, int imageWidth, int imageHeight,jint compressQuality, const char *filename) {// 1. 为 JPEG 图片压缩对象, 分配内存空间/* 该对象中存储了 JPEG 压缩参数, 还包含了指向工作空间的指针, JPEG 库会在需要时分配该指针;* 该结构体可能会存在多个, 每个结构体对象都表示了一个压缩或解压缩的工作;* JPEG 对象 : jpeg_compress_struct 结构体和与其关联的工作数据*/struct jpeg_compress_struct cinfo;/* 错误处理程序 : jpeg_error_mgr 结构体表示错误处理程序,* 将其单独定义成一个结构体, 是因为应用经常需要提供一个专门的错误处理程序;* 处理处理机制 : 在这里我们采用最简单的方法, 使用标准的错误处理程序,* 如果压缩失败, 在 stderr 上打印失败信息, 并调用 exit() 退出程序 ;* 结构体声明周期 : 该结构体的生命周期必须与 jpeg_compress_struct 结构体的生命周期保持一致,* 以免产生野指针问题 ;*/struct jpeg_error_mgr jerr;/* 为了防止 JPEG 压缩对象初始化时出错, 这里首先设置错误处理* 在内存不足时, 创建 jpeg_compress_struct 可能会失败*/cinfo.err = jpeg_std_error(&jerr);// 初始化 JPEG 压缩对象jpeg_create_compress(&cinfo);// 2. 打开文件, 准备向文件写出二进制数据// w 代表写出数据, b 代表二进制数据FILE *outfile;if ((outfile = fopen(filename, "wb")) == NULL) {fprintf(stderr, "can't open %s\n", filename);exit(1);}// 设置文件输出jpeg_stdio_dest(&cinfo, outfile);// 3. 设置压缩参数// 下面的四个参数是必须设置的参数// 设置图片的宽度cinfo.image_width = imageWidth;// 设置图片的高度cinfo.image_height = imageHeight;// 设置每个像素的颜色组件, BGR 3个cinfo.input_components = 3;// 输入图像数据的颜色空间cinfo.in_color_space = JCS_RGB;// 设置默认的压缩参数, 该操作是函数库的常规步骤// 设置该参数前需要设置 cinfo.in_color_space 输入数据的颜色空间jpeg_set_defaults(&cinfo);// 打开哈夫曼编码cinfo.optimize_coding = TRUE;// 设置非默认参数, 该方法设置质量jpeg_set_quality(&cinfo, compressQuality, 1);// 4. 开始压缩 JPEG 格式图片, 设置 TRUE 参数, 表示将完整的图片进行压缩// 一般情况下都是设置 TRUE, 如果进行定制压缩, 可以设置 FALSEjpeg_start_compress(&cinfo, TRUE);// 5. 循环写入数据/* 循环原理 : 使用函数库的状态变量, cinfo.next_scanline 作为循环控制变量* 这样就可以不同自己实现循环控制* 为了保持简单, 每次传递一行图像数据*/// 每一个行的数据个数int row_stride = imageWidth * 3;// 指向图像数据中的某一行数据JSAMPROW row[1];while (cinfo.next_scanline < cinfo.image_height) {/* 获取一行图像数据* data 是图像的起始位置* row_stride 是每一行的字节数* cinfo.next_scanline 是当前的行数* 计算出来的 pixels 指针, 指向要写出行的首地址*/uint8_t *pixels = data + cinfo.next_scanline * row_stride;row[0] = pixels;// 调用 jpeg_write_scanlines 方法后, cinfo.next_scanline 自动加 1jpeg_write_scanlines(&cinfo, row, 1);}// 6. 完成图片压缩jpeg_finish_compress(&cinfo);// 7. 释放相关资源fclose(outfile);jpeg_destroy_compress(&cinfo);}
【Android 内存优化】Android 工程中使用 libjpeg-turbo 压缩图片 ( 初始化压缩对象 | 打开文件 | 设置压缩参数 | 写入压缩图像数据 | 完成压缩 | 释放资源 )