文章目录

  • 一、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.h    AndroidBitmapInfo info;    // 从 bitmap 中获得信息位图信息 AndroidBitmapInfo    AndroidBitmap_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;    // 注意该获取的信息中包含透明度信息, 像素格式是 ARGB    AndroidBitmap_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 中内存存储序列是 BGRA            blue = *( 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 #include #include #include #include #include #include #include // 声明函数void compressJpegFile(uint8_t *data, int imageWidth, int imageHeight,                      jint compressQuality, const char *filename);extern "C" JNIEXPORT jstring JNICALLJava_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 JNICALLJava_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.h    AndroidBitmapInfo info;    // 从 bitmap 中获得信息位图信息 AndroidBitmapInfo    AndroidBitmap_getInfo(env, jbitmap, &info);    // 该类型最终类型是 unsigned char, 相当于 Java 中的 byte    // 这是个 byte 指针, 指向一个数组    // 此处作为返回值使用    uint8_t *addrPtr;    // 注意该获取的信息中包含透明度信息, 像素格式是 ARGB    AndroidBitmap_lockPixels(env, jbitmap, (void **) &addrPtr);    // 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) {            // 在 Bitmap 中内存存储序列是 BGRA            blue = *( 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);}

更多相关文章

  1. 在Android中利用SQLite实现对数据的增删查改
  2. Android操作SQLite数据库(增、删、改、查、分页等)及ListView显
  3. 打造android ORM框架opendroid(五)——数据更新的实现
  4. Android Studio利用异步任务AsyncTask发送post请求获取json数据
  5. Android通过JSON数据格式和java服务后台进行数据交互

随机推荐

  1. Android编程获取网络连接状态及调用网络
  2. Android 基于源码的科学计算器——Calcul
  3. Linux下的两种timer方法 (Android 下NDK
  4. android 软键盘设置
  5. 使用Android网络编程实现简易聊天室
  6. Android(安卓)自定义View:TopBar
  7. Android(安卓)Message和obtainMessage的
  8. android 通过Intent action 跳转到系统页
  9. Android(安卓)Kotlin入门-属性和字段
  10. Android编译SDL2和demo展示(2.0.5)