Android JNI 之 Bitmap 操作
在 Android 中通过 JNI 去操作 Bitmap。
在 Android 通过 JNI 去调用 Bitmap,通过 CMake 去编 so 动态链接库的话,需要添加 jnigraphics 图像库。
1target_link_libraries( # Specifies the target library.
2 native-operation
3 jnigraphics
4 ${log-lib} )
在 Android 中关于 JNI Bitmap 的操作,都定义在
bitmap.h
的头文件里面了,主要就三个函数,明白它们的含义之后就可以去实践体会了。检索 Bitmap 对象信息
AndroidBitmap_getInfo 函数允许原生代码检索 Bitmap 对象信息,如它的大小、像素格式等,函数签名如下:
1/**
2 * Given a java bitmap object, fill out the AndroidBitmapInfo struct for it.
3 * If the call fails, the info parameter will be ignored.
4 */
5intAndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap,
6 AndroidBitmapInfo* info);
其中,第一个参数就是 JNI 接口指针,第二个参数就是 Bitmap 对象的引用,第三个参数是指向 AndroidBitmapInfo 结构体的指针。
AndroidBitmapInfo 结构体如下:
1/** Bitmap info, see AndroidBitmap_getInfo(). */
2typedefstruct {
3/** The bitmap width in pixels. */
4uint32_t width;
5/** The bitmap height in pixels. */
6uint32_t height;
7/** The number of byte per row. */
8uint32_t stride;
9/** The bitmap pixel format. See {@link AndroidBitmapFormat} */
10int32_t format;
11/** Unused. */
12uint32_t flags; // 0 for now
13} AndroidBitmapInfo;
其中,width 就是 Bitmap 的宽,height 就是高,format 就是图像的格式,而 stride 就是每一行的字节数。
图像的格式有如下支持:
1/** Bitmap pixel format. */
2enum AndroidBitmapFormat {
3/** No format. */
4 ANDROID_BITMAP_FORMAT_NONE = 0,
5/** Red: 8 bits, Green: 8 bits, Blue: 8 bits, Alpha: 8 bits. **/
6 ANDROID_BITMAP_FORMAT_RGBA_8888 = 1,
7/** Red: 5 bits, Green: 6 bits, Blue: 5 bits. **/
8 ANDROID_BITMAP_FORMAT_RGB_565 = 4,
9/** Deprecated in API level 13. Because of the poor quality of this configuration, it is advised to use ARGB_8888 instead. **/
10 ANDROID_BITMAP_FORMAT_RGBA_4444 = 7,
11/** Alpha: 8 bits. */
12 ANDROID_BITMAP_FORMAT_A_8 = 8,
13};
如果 AndroidBitmap_getInfo 执行成功的话,会返回 0 ,否则返回一个负数,代表执行的错误码列表如下:
1/** AndroidBitmap functions result code. */
2enum {
3/** Operation was successful. */
4 ANDROID_BITMAP_RESULT_SUCCESS = 0,
5/** Bad parameter. */
6 ANDROID_BITMAP_RESULT_BAD_PARAMETER = -1,
7/** JNI exception occured. */
8 ANDROID_BITMAP_RESULT_JNI_EXCEPTION = -2,
9/** Allocation failed. */
10 ANDROID_BITMAP_RESULT_ALLOCATION_FAILED = -3,
11};
访问原生像素缓存
AndroidBitmap_lockPixels 函数锁定了像素缓存以确保像素的内存不会被移动。
如果 Native 层想要访问像素数据并操作它,该方法返回了像素缓存的一个原生指针,
1/**
2 * Given a java bitmap object, attempt to lock the pixel address.
3 * Locking will ensure that the memory for the pixels will not move
4 * until the unlockPixels call, and ensure that, if the pixels had been
5 * previously purged, they will have been restored.
6 *
7 * If this call succeeds, it must be balanced by a call to
8 * AndroidBitmap_unlockPixels, after which time the address of the pixels should
9 * no longer be used.
10 *
11 * If this succeeds, *addrPtr will be set to the pixel address. If the call
12 * fails, addrPtr will be ignored.
13 */
14intAndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr);
其中,第一个参数就是 JNI 接口指针,第二个参数就是 Bitmap 对象的引用,第三个参数是指向像素缓存地址的指针。
AndroidBitmap_lockPixels 执行成功的话返回 0 ,否则返回一个负数,错误码列表就是上面提到的。
释放原生像素缓存
对 Bitmap 调用完 AndroidBitmap_lockPixels 之后都应该对应调用一次 AndroidBitmap_unlockPixels 用来释放原生像素缓存。
当完成对原生像素缓存的读写之后,就应该释放它,一旦释放后,Bitmap Java 对象又可以在 Java 层使用了,函数签名如下:
1/**
2 * Call this to balance a successful call to AndroidBitmap_lockPixels.
3 */
4intAndroidBitmap_unlockPixels(JNIEnv* env, jobject jbitmap);
其中,第一个参数就是 JNI 接口指针,第二个参数就是 Bitmap 对象的引用,如果执行成功返回 0,否则返回 1。
对 Bitmap 的操作,最重要的就是 AndroidBitmap_lockPixels 函数拿到所有像素的缓存地址,然后对每个像素值进行操作,从而更改 Bitmap 。
实践
通过对 Bitmap 进行旋转,上下翻转,左右镜像来体验 JNI 的开发。
效果如下:
具体代码可以参考我的 Github 项目,欢迎 Star。
https://github.com/glumes/AndroidDevWithCpp
通过 JNI 将 Bitmap 旋转
首先定义一个这样的 native 函数:
1// 顺时针旋转 90° 的操作
2publicnative Bitmap rotateBitmap(Bitmap bitmap);
传入一个 Bitmap 对象,然后返回一个 Bitmap 对象。
然后在 C++ 代码中,首先检索 Bitmap 的信息,看看是否成功。
1 AndroidBitmapInfo bitmapInfo;
2int ret;
3if ((ret = AndroidBitmap_getInfo(env, bitmap, &bitmapInfo)) < 0) {
4 LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
5returnNULL;
6 }
接下来就是获得 Bitmap 的像素缓存指针:
1// 读取 bitmap 的像素内容到 native 内存
2void *bitmapPixels;
3if ((ret = AndroidBitmap_lockPixels(env, bitmap, &bitmapPixels)) < 0) {
4 LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
5returnNULL;
6 }
这个指针指向的就是 Bitmap 像素内容,它是一个以一维数组的形式保存所有的像素点的值,但是我们在定义 Bitmap 图像时,都会定义宽和高,这就相对于是一个二维的了,那么就存在 Bitmap 的像素内容如何转成指针指向的一维内容,是按照行排列还是按照列排列呢?
在这里是按照行进行排列的,而且行的排列是从左往右,列的排列是从上往下,起始点就和屏幕坐标原点一样,位于左上角。
通过 AndroidBitmap_lockPixels 方法,bitmapPixels 指针就指向了 Bitmap 的像素内容,它的长度就是 Bitmap 的宽和高的乘积。
要将 Bitmap 进行旋转,可以通过直接更改 bitmapPixels 指针指向的像素点的值,也可以通过创建一个新的 Bitmap 对象,然后将像素值填充到 Bitmap 对象中,这里选择后者的实现方式。
首先创建一个新的 Bitmap 对象,参考之前文章中提到的方式:Android 通过 JNI 访问 Java 字段和方法调用。
在 Java 代码中,通过 createBitmap 方法可以创建一个 Bitmap,如下所示:
1 Bitmap.createBitmap(int width, int height, Config config)`
所以在 JNI 中就需要调用 Bitmap 的静态方法来创建一个 Bitmap 对象。
1jobject generateBitmap(JNIEnv *env, uint32_t width, uint32_t height){
2
3 jclass bitmapCls = env->FindClass("android/graphics/Bitmap");
4 jmethodID createBitmapFunction = env->GetStaticMethodID(bitmapCls,
5"createBitmap",
6"(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
7 jstring configName = env->NewStringUTF("ARGB_8888");
8 jclass bitmapConfigClass = env->FindClass("android/graphics/Bitmap$Config");
9 jmethodID valueOfBitmapConfigFunction = env->GetStaticMethodID(
10 bitmapConfigClass, "valueOf",
11"(Ljava/lang/String;)Landroid/graphics/Bitmap$Config;");
12
13 jobject bitmapConfig = env->CallStaticObjectMethod(bitmapConfigClass,
14 valueOfBitmapConfigFunction, configName);
15
16 jobject newBitmap = env->CallStaticObjectMethod(bitmapCls,
17 createBitmapFunction,
18 width,
19 height, bitmapConfig);
20return newBitmap;
21}
首先通过
FindClass
方法找到 Config 类,得到一个 ARGB_8888 的配置,然后得到 Bitmap 类,调用它的静态方法 createBitmap
创建一个新的 Bitmap 对象,具体可以参考之前的文章。在这里要传入新 Bitmap 的宽高,这个宽高也是通过
AndroidBitmap_getInfo
方法得到原来的宽高之后,根据不同的操作计算后得到的。1// 旋转操作,新 Bitmap 的宽等于原来的高,新 Bitmap 的高等于原来的宽
2uint32_t newWidth = bitmapInfo.height;
3uint32_t newHeight = bitmapInfo.width;
有了新的 Bitmap 对象,又有了原有的 Bitmap 像素指针,接下来就是创建新的像素指针,并填充像素内容,然后把这个像素内容再填充到 Bitmap 上。
1// 创建一个新的数组指针,把这个新的数组指针填充像素值
2uint32_t *newBitmapPixels = newuint32_t[newWidth * newHeight];
3int whereToGet = 0;
4for (int y = 0; y < newHeight; ++y) {
5for (int x = newWidth - 1; x >= 0; x--) {
6uint32_t pixel = ((uint32_t *) bitmapPixels)[whereToGet++];
7 newBitmapPixels[newWidth * y + x] = pixel;
8 }
9 }
在这两个
for
循环里面就是从原来的像素指针中取出像素值,然后把它按照特定的排列顺序填充到新的像素指针中对应位置的值,这里也就是前面强调的像素指针是按照行进行排列的,起点是 Bitmap 的左上角。1void *resultBitmapPixels;
2if ((ret = AndroidBitmap_lockPixels(env, newBitmap, &resultBitmapPixels)) < 0) {
3 LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
4returnNULL;
5 }
6int pixelsCount = newWidth * newHeight;
7memcpy((uint32_t *) resultBitmapPixels, newBitmapPixels, sizeof(uint32_t) * pixelsCount);
8 AndroidBitmap_unlockPixels(env, newBitmap);
再次创建一个 resultBitmapPixels 指针,并调用 AndroidBitmap_lockPixels 方法获取新的 Bitmap 的像素指针缓存,然后调用
memcpy
方法,将待填充的像素指针填充到 resultBitmapPixels 上,这样就完成了像素的赋值,最后调用 AndroidBitmap_unlockPixels
方法释放像素指针缓存,完成整个赋值过程。就这样通过读取原有 Bitmap 的像素内容然后进行操作后再赋值给新的 Bitmap 对象就完成了 JNI 操作 Bitmap 。
通过 JNI 将 Bitmap 上下翻转和左右镜像
将 Bitmap 进行上下翻转以及左右镜像和旋转操作类似了,只是针对像素指针的操作方式不同。
上下翻转的操作:
1int whereToGet = 0;
2for (int y = 0; y < newHeight; ++y) {
3for (int x = 0; x < newWidth; x++) {
4uint32_t pixel = ((uint32_t *) bitmapPixels)[whereToGet++];
5 newBitmapPixels[newWidth * (newHeight - 1 - y) + x] = pixel;
6 }
7 }
左右镜像的操作:
1int whereToGet = 0;
2for (int y = 0; y < newHeight; ++y) {
3for (int x = newWidth - 1; x >= 0; x--) {
4uint32_t pixel = ((uint32_t *) bitmapPixels)[whereToGet++];
5 newBitmapPixels[newWidth * y + x] = pixel;
6 }
7 }
其他的操作都相同了,具体还是看项目代码吧。
欢迎关注微信公众号:【纸上浅谈】,获得最新文章推送~~~
最新评论
推荐文章
作者最新文章
你可能感兴趣的文章
Copyright Disclaimer: The copyright of contents (including texts, images, videos and audios) posted above belong to the User who shared or the third-party website which the User shared from. If you found your copyright have been infringed, please send a DMCA takedown notice to [email protected]. For more detail of the source, please click on the button "Read Original Post" below. For other communications, please send to [email protected].
版权声明:以上内容为用户推荐收藏至CareerEngine平台,其内容(含文字、图片、视频、音频等)及知识版权均属用户或用户转发自的第三方网站,如涉嫌侵权,请通知[email protected]进行信息删除。如需查看信息来源,请点击“查看原文”。如需洽谈其它事宜,请联系[email protected]。
版权声明:以上内容为用户推荐收藏至CareerEngine平台,其内容(含文字、图片、视频、音频等)及知识版权均属用户或用户转发自的第三方网站,如涉嫌侵权,请通知[email protected]进行信息删除。如需查看信息来源,请点击“查看原文”。如需洽谈其它事宜,请联系[email protected]。