Android JNI用bitmap来实现Imageview的上下翻转和左右镜像

    技术2022-07-10  118

    Android NDK功能很强大,今天就来学习下jni用bitmap实现Imageview的上下翻转和左右镜像 效果图: 在 Android 通过 JNI 去调用 Bitmap,通过 CMake 去编 so 动态链接库的话,需要添加 jnigraphics 图像库。

    target_link_libraries( # Specifies the target library. native-operation jnigraphics ${log-lib} )

    在 Android 中关于 JNI Bitmap 的操作,都定义在 bitmap.h 的头文件里面了,主要就三个函数,明白它们的含义之后就可以去实践体会了。 检索 Bitmap 对象信息

    AndroidBitmap_getInfo 函数允许原生代码检索 Bitmap 对象信息,如它的大小、像素格式等,函数签名如下:

    /** * Given a java bitmap object, fill out the AndroidBitmapInfo struct for it. * If the call fails, the info parameter will be ignored. */ int AndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap, AndroidBitmapInfo* info);

    其中,第一个参数就是 JNI 接口指针,第二个参数就是 Bitmap 对象的引用,第三个参数是指向 AndroidBitmapInfo 结构体的指针。

    AndroidBitmapInfo 结构体如下: /** Bitmap info, see AndroidBitmap_getInfo(). */ typedef struct { /** The bitmap width in pixels. */ uint32_t width; /** The bitmap height in pixels. */ uint32_t height; /** The number of byte per row. */ uint32_t stride; /** The bitmap pixel format. See {@link AndroidBitmapFormat} */ int32_t format; /** Unused. */ uint32_t flags; // 0 for now } AndroidBitmapInfo;

    其中,width 就是 Bitmap 的宽,height 就是高,format 就是图像的格式,而 stride 就是每一行的字节数。

    图像的格式有如下支持:

    /** Bitmap pixel format. */ enum AndroidBitmapFormat { /** No format. */ ANDROID_BITMAP_FORMAT_NONE = 0, /** Red: 8 bits, Green: 8 bits, Blue: 8 bits, Alpha: 8 bits. **/ ANDROID_BITMAP_FORMAT_RGBA_8888 = 1, /** Red: 5 bits, Green: 6 bits, Blue: 5 bits. **/ ANDROID_BITMAP_FORMAT_RGB_565 = 4, /** Deprecated in API level 13. Because of the poor quality of this configuration, it is advised to use ARGB_8888 instead. **/ ANDROID_BITMAP_FORMAT_RGBA_4444 = 7, /** Alpha: 8 bits. */ ANDROID_BITMAP_FORMAT_A_8 = 8, };

    如果 AndroidBitmap_getInfo 执行成功的话,会返回 0 ,否则返回一个负数,代表执行的错误码列表如下:

    /** AndroidBitmap functions result code. */ enum { /** Operation was successful. */ ANDROID_BITMAP_RESULT_SUCCESS = 0, /** Bad parameter. */ ANDROID_BITMAP_RESULT_BAD_PARAMETER = -1, /** JNI exception occured. */ ANDROID_BITMAP_RESULT_JNI_EXCEPTION = -2, /** Allocation failed. */ ANDROID_BITMAP_RESULT_ALLOCATION_FAILED = -3, };

    访问原生像素缓存

    AndroidBitmap_lockPixels 函数锁定了像素缓存以确保像素的内存不会被移动。

    如果 Native 层想要访问像素数据并操作它,该方法返回了像素缓存的一个原生指针,

    /** * Given a java bitmap object, attempt to lock the pixel address. * Locking will ensure that the memory for the pixels will not move * until the unlockPixels call, and ensure that, if the pixels had been * previously purged, they will have been restored. * * If this call succeeds, it must be balanced by a call to * AndroidBitmap_unlockPixels, after which time the address of the pixels should * no longer be used. * * If this succeeds, *addrPtr will be set to the pixel address. If the call * fails, addrPtr will be ignored. */ int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr);

    其中,第一个参数就是 JNI 接口指针,第二个参数就是 Bitmap 对象的引用,第三个参数是指向像素缓存地址的指针。

    AndroidBitmap_lockPixels 执行成功的话返回 0 ,否则返回一个负数,错误码列表就是上面提到的。 释放原生像素缓存

    对 Bitmap 调用完 AndroidBitmap_lockPixels 之后都应该对应调用一次 AndroidBitmap_unlockPixels 用来释放原生像素缓存。

    当完成对原生像素缓存的读写之后,就应该释放它,一旦释放后,Bitmap Java 对象又可以在 Java 层使用了,函数签名如下:

    /** * Call this to balance a successful call to AndroidBitmap_lockPixels. */ int AndroidBitmap_unlockPixels(JNIEnv* env, jobject jbitmap);

    其中,第一个参数就是 JNI 接口指针,第二个参数就是 Bitmap 对象的引用,如果执行成功返回 0,否则返回 1。

    对 Bitmap 的操作,最重要的就是 AndroidBitmap_lockPixels 函数拿到所有像素的缓存地址,然后对每个像素值进行操作,从而更改 Bitmap 。

    具体代码如下: Activity 部分:

    private void initview(){ source_img = (ImageView)findViewById(R.id.source_img); operation_img = (ImageView)findViewById(R.id.operation_img); Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.newpic); operation_img.setImageBitmap(rotateBitmap(bitmap)); } /* * 上下翻转和左右镜像 */ public native Bitmap rotateBitmap(Bitmap bitmap);

    2.jni部分:

    #include <jni.h> #include <string> #include <stdio.h> #include <pthread.h> #include <unistd.h> #include "AndroidLog.h" #include <android/bitmap.h> jobject generateBitmap(JNIEnv *env, uint32_t width, uint32_t height) { jclass bitmapCls = env->FindClass("android/graphics/Bitmap"); jmethodID createBitmapFunction = env->GetStaticMethodID(bitmapCls, "createBitmap", "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;"); jstring configName = env->NewStringUTF("ARGB_8888"); jclass bitmapConfigClass = env->FindClass("android/graphics/Bitmap$Config"); jmethodID valueOfBitmapConfigFunction = env->GetStaticMethodID( bitmapConfigClass, "valueOf", "(Ljava/lang/String;)Landroid/graphics/Bitmap$Config;"); jobject bitmapConfig = env->CallStaticObjectMethod(bitmapConfigClass, valueOfBitmapConfigFunction, configName); jobject newBitmap = env->CallStaticObjectMethod(bitmapCls, createBitmapFunction, width, height, bitmapConfig); return newBitmap; } extern "C" JNIEXPORT jobject JNICALL Java_com_xinrui_ndkapp_MainActivity_rotateBitmap( JNIEnv *env, jobject /* this */,jobject bitmap) { AndroidBitmapInfo bitmapInfo; int ret; if ((ret = AndroidBitmap_getInfo(env, bitmap, &bitmapInfo)) < 0) { LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret); return NULL; } // 读取 bitmap 的像素内容到 native 内存 void *bitmapPixels; if ((ret = AndroidBitmap_lockPixels(env, bitmap, &bitmapPixels)) < 0) { LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret); return NULL; } // 旋转操作,新 Bitmap 的宽等于原来的高,新 Bitmap 的高等于原来的宽 uint32_t newWidth = bitmapInfo.width; uint32_t newHeight = bitmapInfo.height; // 创建一个新的数组指针,把这个新的数组指针填充像素值 uint32_t *newBitmapPixels = new uint32_t[newWidth * newHeight]; int whereToGet = 0; //上下翻转 for (int y = 0; y < newHeight; ++y) { for (int x = 0; x < newWidth; x++) { uint32_t pixel = ((uint32_t *) bitmapPixels)[whereToGet++]; newBitmapPixels[newWidth * (newHeight - 1 - y) + x] = pixel; } } //左右镜像 // for (int y = 0; y < newHeight; ++y) { // for (int x = newWidth - 1; x >= 0; x--) { // uint32_t pixel = ((uint32_t *) bitmapPixels)[whereToGet++]; // newBitmapPixels[newWidth * y + x] = pixel; // } // } AndroidBitmap_unlockPixels(env, bitmap); jobject newBitmap = generateBitmap(env,newWidth,newHeight); void *resultBitmapPixels; if ((ret = AndroidBitmap_lockPixels(env, newBitmap, &resultBitmapPixels)) < 0) { LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret); return NULL; } int pixelsCount = newWidth * newHeight; memcpy((uint32_t *) resultBitmapPixels, newBitmapPixels, sizeof(uint32_t) * pixelsCount); AndroidBitmap_unlockPixels(env, newBitmap); delete []newBitmapPixels; return newBitmap; }
    Processed: 0.009, SQL: 9