文章目录

  • 一、Bitmap 图像数据处理
  • 二、Java 层 Bitmap 对象转为 JNI 层 bitmap 对象
  • 三、获取 bitmap 中的图像数据
  • 四、过滤 bitmap 中的图像数据 ( 获取 RGB 数据 剔除 A 通道数据 )
  • 五、释放资源
  • 六、Bitmap 图像数据处理

在上一篇博客 【Android 内存优化】libjpeg-turbo 函数库交叉编译与使用 ( 交叉编译脚本编写 | 函数库头文件拷贝 | 构建脚本配置 | Android Studio 测试函数库 ) 中 对 libjpeg-turbo 函数库进行了交叉编译 , 拷贝了相应的头文件和静态库到 Android Studio 项目中 , 并配置了 CMakeList.txt 构建脚本 , 和 build.gradle 构建脚本 , 本篇博客中开始进行代码编写 ;

一、Bitmap 图像数据处理


Bitmap 图像数据处理 :

① 获取 Bitmap 图像对象 : Java 传递到 JNI 层的是 jobject 对象 , 需要将其转为 JNI 中的 bitmap 对象 ;

② 数据提取 : 从 bitmap 图像中提取 RGB 像素值 , 也就是剔除 ALPHA 通道 ( 透明度 ) 的数据 ;

③ 使用 libjpeg-turbo 压缩图片 : 调用 libjpeg-turbo 函数库 , 对上述提取的图片 RGB 像素数据进行压缩 ;

④ 释放资源 : 图片压缩完毕后 , 释放相关资源 ;

二、Java 层 Bitmap 对象转为 JNI 层 bitmap 对象


1. Bitmap 信息 : 在 AndroidBitmapInfo 结构体中 , 封装了图像宽度 , 图像高度 , 像素格式等信息 ;

/** Bitmap info, see AndroidBitmap_getInfo(). */
typedef struct {/** 图像像素宽度. */uint32_t    width;/** 图像像素高度. */uint32_t    height;/** 每行字节数. */uint32_t    stride;/** 像素格式. See {@link AndroidBitmapFormat} */int32_t     format;/** 保留位. */uint32_t    flags;      // 0 for now
} AndroidBitmapInfo;

2. 获取 Bitmap 信息 : 调用 bitmap.h 中的 AndroidBitmap_getInfo 方法 , 可以从 jbitmap 中获取对应的信息 ;

int AndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap,AndroidBitmapInfo* info);

3. 代码示例 :

    // 声明 位图信息, 该变量作为返回值使用// 引用自 bitmap.hAndroidBitmapInfo info;// 从 bitmap 中获得信息位图信息 AndroidBitmapInfoAndroidBitmap_getInfo(env, jbitmap, &info);

三、获取 bitmap 中的图像数据


调用 AndroidBitmap_lockPixels 方法 , 即可从 Java 的 Bitmap 对象中获取数据的首地址 ; 向该函数中传入一个二维指针 , 该二维指针参数作为返回值使用 , 该二维指针最终指向的内存就是图像数据内存 ;

1. AndroidBitmap_lockPixels 函数作用 : 从给定 Java Bitmap 对象中 , 获取其对应的像素数据地址 ; 锁定可以保证像素数据内存是固定不变的 , 直到调用解除锁定方法 , 清除相关数据 ; 该方法必须与 AndroidBitmap_unlockPixels 方法成对使用 , 之后 addrPtr 地址不应该再被使用到 ; 如果执行成功 , *addrPtr 会指向图像像素数据的首地址 , 如果方法失败 , 那么该二维指针是无效的指针 ;

2. AndroidBitmap_lockPixels 函数原型 :

① JNIEnv* env 参数 : 注意这里用到了 JNIEnv* env 参数 , 主线程调用可以直接使用, 子线程调用的话 , 需要使用 JavaVM 调用 AttachCurrentThread 方法 , 传入 JNIEnv 指针 , 然后该 JNIEnv 就是线程对应的 JNI 环境 , 使用完毕后解除绑定 ;

参考 【Android NDK 开发】JNI 线程 ( JNI 线程创建 | 线程执行函数 | 非 JNI 方法获取 JNIEnv 与 Java 对象 | 线程获取 JNIEnv | 全局变量设置 ) 博客 ;

② jobject jbitmap 参数 : Java 中的 Bitmap 对象 ;

③ void** addrPtr 参数 : 二维指针 , 执行成功后指向图像像素数据的首地址 , 同时用于当做返回值 , 让用户可以调用到像素数据 ;

int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr);

3. 代码示例 :

    // 该类型最终类型是 unsigned char, 相当于 Java 中的 byte// 这是个 byte 指针, 指向一个数组// 此处作为返回值使用uint8_t *addrPtr;// 注意该获取的信息中包含透明度信息, 像素格式是 ARGBAndroidBitmap_lockPixels(env, jbitmap, (void **) &addrPtr);

四、过滤 bitmap 中的图像数据 ( 获取 RGB 数据 剔除 A 通道数据 )


1. 数据过滤需求 : 之前已经获取到了图像数据 , 存储在了 addrPtr 指针中 , 现在需要将 RGB 数据取出, 剔除 ALPHA 透明度通道数据 , 只保留 RGB 通道数据 ;

2. 两块内存 : uint8_t* addrPtr 指针指向的内存是源数据 , uint8_t* data 指针指向的的数据是目标数据 , 最终要压缩的数据是 data 目标数据 ;

3. 像素格式 : 源数据中存储的 BGRA 像素格式的数据 , 目标数据中存储的是 BGR 像素格式的数据 ;

4. 循环像素数据 : 开启循环 , 直接循环遍历每个像素点 , 注意像素点存放格式是 BGRA , 然后将数据存储到另一块内存中 , 存储顺序是 BGR ; 注意每次循环后 , 都需要移动对应的指针 ;

    // JPEG 格式的图片, 没有透明度信息, 像素格式是 RGB// 这里需要去掉透明度信息// 获取图片的像素宽度int width = info.width;// 获取图片的像素高度int height = info.height;// 存储 RGB 数据uint8_t* data = (uint8_t *) malloc(width * height * 3);// data 指针在后续发生了移动, 这里保存下初始的指针, 方便在之后释放该指针uint8_t* temp = data;// JPEG 像素中的 RGB 三原色, 红绿蓝uint8_t red, green, blue;// 遍历从 Bitmap 内存 addrPtr 中读取 BGRA 数据, 然后向 data 内存存储 BGR 数据中for(int i = 0; i < height; i++){for (int j = 0; j < width; ++j) {// 处理 i 行 j 列像素点信息 // 在 Bitmap 中内存存储序列是 BGRAblue = *( addrPtr );green = *( addrPtr + 1 );red = *( addrPtr + 2 );// libturbojpeg 中 JPEG 图像内存排列格式是 BGR*data = blue;*( data + 1 ) = green;*( data + 2 ) = red;// 移动 data 指针data += 3;//移动 addrPtr 指针, 为下一次读取数据做准备addrPtr +=4;}}// 截止到此处, 已经读取出 JPEG 图片所需的数据, 在 data 指针中

五、释放资源


之前还有个步骤是压缩 jpeg 格式图片 , 这个过程比较复杂单开一个博客讲解 , 该章节讲解压缩完毕后的内存释放操作 ;

1. 锁定 / 解锁 像素数据 : AndroidBitmap_unlockPixels 方法与 AndroidBitmap_lockPixels 方法成对使用 , 表示之后不再需要使用 Bitmap 对象的数据了 ;

2. 释放压缩数据 : 释放掉存储要压缩的 JPEG 图片 RGB 数据的内存 , 此时已经压完毕 , 可以将之前申请的内存都释放掉了 ; 注意之前申请的 data 指针 , 在拷贝数据过程中 , 将该指针移动过了 , 不能释放 data 指针 , 只能释放之前 data 内存申请后的备份指针 , 否则报错 ;

3. 释放字符串 : env->GetStringUTFChars 创建的字符串是局部引用 , 这里需要释放掉 , 及时回收内存是个好习惯 ;

    // 解锁AndroidBitmap_unlockPixels(env,jbitmap);// 注意要释放 temp 指针 , 不要释放成 data 指针, 否则会出错free(temp);// 释放局部引用, 不释放, GC 也会回收, 但是有延迟env->ReleaseStringUTFChars(path, filePath);

六、Bitmap 图像数据处理


GitHub 项目地址 : han1202012/PictureCompression

libjpeg-turbo 压缩 JPEG 代码示例 :

#include <jni.h>
#include <string>
#include <jpeglib.h>
#include <android/bitmap.h>
#include <malloc.h>
#include <android/log.h>
#include <bitset>
#include <iosfwd>// 声明函数
void compressJpegFile(uint8_t *data, int imageWidth, int imageHeight,jint compressQuality, const char *filename);extern "C" JNIEXPORT jstring JNICALL
Java_kim_hsl_pc_MainActivity_stringFromJNI(JNIEnv* env,jobject /* this */) {std::string hello = "Hello from C++";// 测试 libturbojpeg.a 函数库jpeg_compress_struct jcs;__android_log_print(ANDROID_LOG_INFO, "JPEG", "jpeg_compress_struct jcs = %d", jcs.image_width);hello = hello + " , jpeg_compress_struct jcs = " + std::to_string(jcs.image_width);return env->NewStringUTF(hello.c_str());
}/*** 图片压缩方法*/
extern "C"
JNIEXPORT void JNICALL
Java_kim_hsl_pc_MainActivity_native_1pictureCompress(JNIEnv *env, jobject thiz,jobject jbitmap,jint quality, jstring path) {// 将 Java 字符串转为 C 字符串, 注意这是局部引用const char *filePath = env->GetStringUTFChars(path, 0);// 声明 位图信息, 该变量作为返回值使用// 引用自 bitmap.hAndroidBitmapInfo info;// 从 bitmap 中获得信息位图信息 AndroidBitmapInfoAndroidBitmap_getInfo(env, jbitmap, &info);// 该类型最终类型是 unsigned char, 相当于 Java 中的 byte// 这是个 byte 指针, 指向一个数组// 此处作为返回值使用uint8_t *addrPtr;// 注意该获取的信息中包含透明度信息, 像素格式是 ARGBAndroidBitmap_lockPixels(env, jbitmap, (void **) &addrPtr);// JPEG 格式的图片, 没有透明度信息, 像素格式是 RGB// 这里需要去掉透明度信息// 获取图片的像素宽度int width = info.width;// 获取图片的像素高度int height = info.height;//rgbuint8_t* data = (uint8_t *) malloc(width * height * 3);// data 指针在后续发生了移动, 这里保存下初始的指针, 方便在之后释放该指针uint8_t* temp = data;// JPEG 像素中的 RGB 三原色, 红绿蓝uint8_t red, green, blue;// 遍历从 Bitmap 内存 addrPtr 中读取 BGRA 数据, 然后向 data 内存存储 BGR 数据中for(int i = 0; i < height; i++){for (int j = 0; j < width; ++j) {// 在 Bitmap 中内存存储序列是 BGRAblue = *( addrPtr );green = *( addrPtr + 1 );red = *( addrPtr + 2 );// libturbojpeg 中 JPEG 图像内存排列格式是 BGR*data = blue;*( data + 1 ) = green;*( data + 2 ) = red;// 移动 data 指针data += 3;//移动 addrPtr 指针, 为下一次读取数据做准备addrPtr +=4;}}// 截止到此处, 已经读取出 JPEG 图片所需的数据, 在 data 指针中// 将 data 指针中的数据压缩到 JPEG 格式图片中compressJpegFile(temp, width, height, quality, filePath);// 解锁AndroidBitmap_unlockPixels(env,jbitmap);// 注意要释放 temp 指针 , 不要释放成 data 指针, 否则会出错free(temp);// 释放局部引用, 不释放, GC 也会回收, 但是有延迟env->ReleaseStringUTFChars(path, filePath);
}

【Android 内存优化】Android 工程中使用 libjpeg-turbo 压缩图片 ( JNI 传递 Bitmap | 获取位图信息 | 获取图像数据 | 图像数据过滤 | 释放资源 )相关推荐

  1. ANDROID内存优化(大汇总——中)

    转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 写在最前: 本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上 ...

  2. Android内存优化—Android的内存管理方式

    内存管理机制 从操作系统的角度来说,内存就是一块数据存储区域,属于可被操作系统调度的资源.现代多任务(进程)的操作系统中,内存管理尤为重要,操作系统需要为每一个进程合理的分配内存资源,所以可以从两方面 ...

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

    文章目录 一.使用 libjpeg-turbo 压缩图片流程 二.初始化 JPEG 压缩对象 三.打开文件 四.设置压缩参数 五.开始压缩 六.循环写入压缩数据 七.完成图片压缩及收尾 八.libjp ...

  4. ANDROID内存优化(大汇总——全)

    转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 写在最前: 本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上 ...

  5. Android内存优化汇总

    写在最前: 本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上把网上搜集的各种内存零散知识点进行汇总.挑选.简化后整理而成. 所以我将本文定义为一个工具类的文章,如果你在A ...

  6. ANDROID内存优化(大汇总——上)

    转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 写在最前: 本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上 ...

  7. 【Android 内存优化】Android 原生 API 图片压缩原理 ( Bitmap_compress 方法解析 | Skia 二维图形库 | libjpeg 函数库 | libpng 函数库 )

    文章目录 一. 图片质量压缩方法 二. Skia 二维图形库 三. libjpeg.libpng 函数库引入 在博客 [Android 内存优化]图片文件压缩 ( Android 原生 API 提供的 ...

  8. 【Android 内存优化】Bitmap 图像尺寸缩小 ( 考虑像素密度、针对从不同像素密度资源中解码对应的 Bitmap 对象 | inDensity | inTargetDensity )

    文章目录 一.像素密度对解码图片的影响 二.不考虑像素密度会导致图片缩小尺寸不准确 三.DisplayMetrics 源码阅读.研究手机资源获取规则 四.像素密度参数设置取值 ( inDensity ...

  9. 【Android 内存优化】Android 原生 API 图片压缩原理 ( 图片质量压缩方法 | 查找 Java 源码中的 native 方法对应的 C++ 源码 )

    文章目录 一. 图片质量压缩方法 二. 查找对应的 Native 方法源码 三. 分析 Bitmap.cpp 中动态注册 Native 方法 在博客 [Android 内存优化]图片文件压缩 ( An ...

最新文章

  1. 你知道怎么分库分表吗?如何做到永不迁移数据和避免热点吗?
  2. Update of SharePoint Me
  3. Liunx中进程和计划任务管理
  4. 类的初始化列表_【Flutter 111】Flutter手把手教程Dart语言——类、类的的成员变量和方法、类的构造函数...
  5. 5G多输入多输出技术,到底是个啥东东?
  6. 剑指offer 面试题61. 扑克牌中的顺子
  7. 2020年9月北京计算机等级考试考点,2020年9月北京计算机等级考试考点设置
  8. 【论文写作】精品课程教学网站中用户管理如何写
  9. 后缀自动机(学习笔记)
  10. python 平方根_数的Python平方根
  11. java的JDBC简单案例
  12. 【雷达装备】A800 无人机探测雷达
  13. Apache安装apr和apr-util作用
  14. mysql/hive求实际活动时间
  15. Cubieboard2折腾手记(一)
  16. Cocos Creator中的Tween
  17. IBM TSM 6.3学习笔记
  18. jenkins自动打包并向Harbor推送镜像
  19. 【从零开始学习 SystemVerilog】2.8、SystemVerilog 数据类型—— Unpacked Arrays(非压缩数组)
  20. 网易云音乐插件现已登陆 Visual Studio Code

热门文章

  1. Sms中关于操作系统的部署之上
  2. 学习IOS开问题篇--视图的模型控件属性写在私有分类中的原因
  3. 为什么阿里巴巴建议集合初始化时,指定集合容量大小
  4. ResDepot CRC码
  5. 【bzoj2463】 谁能赢呢?
  6. Linux soft lockup分析
  7. MySQL集群节点宕机,数据库脑裂!如何排障?
  8. 作业二/Git的安装以及使用
  9. easyui datebox不可编辑设置
  10. 使用axis2进行WebService的开发