【Android 内存优化】Android 工程中使用 libjpeg-turbo 压缩图片 ( 初始化压缩对象 | 打开文件 | 设置压缩参数 | 写入压缩图像数据 | 完成压缩 | 释放资源 )

简介: 【Android 内存优化】Android 工程中使用 libjpeg-turbo 压缩图片 ( 初始化压缩对象 | 打开文件 | 设置压缩参数 | 写入压缩图像数据 | 完成压缩 | 释放资源 )

文章目录

一、使用 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);

1


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);





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);

五、开始压缩


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, 如果进行定制压缩, 可以设置 FALSE
    jpeg_start_compress(&cinfo, TRUE);





六、循环写入压缩数据


1 . 写入压缩数据原理 : 使用函数库的状态变量, cinfo.next_scanline 作为循环控制变量 , 这样就可以不同自己实现循环控制 , 为了保持代码简单, 每次传递一行图像数据 ;



2 . 计算每行数据字节数 : 像素宽度乘以 3 33 , 3 33 表示每个像素点有 BGR 三个颜色值 , 每个颜色 1 11 字节 ;


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 自动加 1
        jpeg_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, 如果进行定制压缩, 可以设置 FALSE
    jpeg_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 自动加 1
        jpeg_write_scanlines(&cinfo, row, 1);
    }
    // 6. 完成图片压缩
    jpeg_finish_compress(&cinfo);
    // 7. 释放相关资源
    fclose(outfile);
    jpeg_destroy_compress(&cinfo);
}




目录
相关文章
|
7月前
|
Android开发 开发者
Android设置View是否可用
在Android开发中,有时需要将布局设置为不可点击状态(失去焦点)。常见的解决方法是使用`setOnClickListener(null)`,但本文介绍一种更通用的方式:通过封装`setViewEnabled`方法实现。该方法可递归设置View及其子View的启用状态,支持传入目标View和布尔值(`true`为可用,`false`为禁用)。例如,调用`setViewEnabled(edittext, false)`即可禁用EditText。文章附有源码及示例动图,帮助开发者快速理解与应用。
159 1
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
2048 1
|
7月前
|
Android开发 开发者
Android中Dialog位置+样式的设置
本文介绍了在Android开发中如何设置Dialog的位置和样式。通过自定义`MyDialog`类,可以灵活调整Dialog的显示位置,例如将其固定在屏幕底部,并设置宽度匹配父布局。同时,文章还展示了如何模仿Android原生Dialog样式,通过定义`MyDialogStyle`去除标题栏、设置背景透明度、添加阴影效果以及配置点击外部关闭等功能,从而实现更加美观和符合需求的Dialog效果。代码示例详细,便于开发者快速上手实现。
402 2
|
10月前
|
存储 IDE Java
java设置栈内存大小
在Java应用中合理设置栈内存大小是确保程序稳定性和性能的重要措施。通过JVM参数 `-Xss`,可以灵活调整栈内存大小,以适应不同的应用场景。本文介绍了设置栈内存大小的方法、应用场景和注意事项,希望能帮助开发者更好地管理Java应用的内存资源。
497 4
|
12月前
|
运维 监控 Ubuntu
【运维】如何在Ubuntu中设置一个内存守护进程来确保内存不会溢出
通过设置内存守护进程,可以有效监控和管理系统内存使用情况,防止内存溢出带来的系统崩溃和服务中断。本文介绍了如何在Ubuntu中编写和配置内存守护脚本,并将其设置为systemd服务。通过这种方式,可以在内存使用超过设定阈值时自动采取措施,确保系统稳定运行。
426 4
|
弹性计算 Kubernetes Perl
k8s 设置pod 的cpu 和内存
在 Kubernetes (k8s) 中,设置 Pod 的 CPU 和内存资源限制和请求是非常重要的,因为这有助于确保集群资源的合理分配和有效利用。你可以通过定义 Pod 的 `resources` 字段来设置这些限制。 以下是一个示例 YAML 文件,展示了如何为一个 Pod 设置 CPU 和内存资源请求(requests)和限制(limits): ```yaml apiVersion: v1 kind: Pod metadata: name: example-pod spec: containers: - name: example-container image:
1567 2
|
Android开发
Android经典实战之Textview文字设置不同颜色、下划线、加粗、超链接等效果
本文介绍了 `SpannableString` 在 Android 开发中的强大功能,包括如何在单个字符串中应用多种样式,如颜色、字体大小、风格等,并提供了详细代码示例,展示如何设置文本颜色、添加点击事件等,助你实现丰富文本效果。
1038 4
|
2月前
|
移动开发 前端开发 Android开发
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
269 12
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
2月前
|
移动开发 JavaScript 应用服务中间件
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
226 5
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
2月前
|
移动开发 Rust JavaScript
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
562 4
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡

热门文章

最新文章