使用 JNI 和 NDK 旋转位图
Posted
技术标签:
【中文标题】使用 JNI 和 NDK 旋转位图【英文标题】:Rotating a bitmap using JNI & NDK 【发布时间】:2013-01-02 03:33:45 【问题描述】:背景:
我已经决定,由于位图占用大量内存,很容易导致内存不足错误,我将把繁重的、消耗内存的工作放在 C/C++ 代码上。
我用于旋转位图的步骤是:
-
读取位图信息(宽度、高度)
将位图像素存储到数组中。
回收位图。
创建一个相反大小的新位图。
将像素放入新位图中。
释放像素并返回位图。
问题:
即使一切似乎运行没有任何错误,输出图像也不是原始图像的旋转。事实上,它完全毁了它。
应该逆时针旋转 90 度。
我得到的示例(放大的屏幕截图):
如您所见,不仅颜色变得更奇怪,而且尺寸与我设置的不匹配。这里真的很奇怪。
也许我没有正确读取/放置数据?
当然这只是一个例子。只要设备有足够的内存来保存它,该代码应该可以在任何位图上正常工作。另外,我可能想对位图进行其他操作,而不是旋转它。
我创建的代码:
android.mk 文件:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := JniTest
LOCAL_SRC_FILES := JniTest.cpp
LOCAL_LDLIBS := -llog
LOCAL_LDFLAGS += -ljnigraphics
include $(BUILD_SHARED_LIBRARY)
APP_OPTIM := debug
LOCAL_CFLAGS := -g
cpp 文件:
#include <jni.h>
#include <jni.h>
#include <android/log.h>
#include <stdio.h>
#include <android/bitmap.h>
#include <cstring>
#include <unistd.h>
#define LOG_TAG "DEBUG"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
extern "C"
JNIEXPORT jobject JNICALL Java_com_example_jnitest_MainActivity_rotateBitmapCcw90(JNIEnv * env, jobject obj, jobject bitmap);
JNIEXPORT jobject JNICALL Java_com_example_jnitest_MainActivity_rotateBitmapCcw90(JNIEnv * env, jobject obj, jobject bitmap)
//
//getting bitmap info:
//
LOGD("reading bitmap info...");
AndroidBitmapInfo info;
int ret;
if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0)
LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
return NULL;
LOGD("width:%d height:%d stride:%d", info.width, info.height, info.stride);
if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888)
LOGE("Bitmap format is not RGBA_8888!");
return NULL;
//
//read pixels of bitmap into native memory :
//
LOGD("reading bitmap pixels...");
void* bitmapPixels;
if ((ret = AndroidBitmap_lockPixels(env, bitmap, &bitmapPixels)) < 0)
LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
return NULL;
uint32_t* src = (uint32_t*) bitmapPixels;
uint32_t* tempPixels = new uint32_t[info.height * info.width];
int stride = info.stride;
int pixelsCount = info.height * info.width;
memcpy(tempPixels, src, sizeof(uint32_t) * pixelsCount);
AndroidBitmap_unlockPixels(env, bitmap);
//
//recycle bitmap - using bitmap.recycle()
//
LOGD("recycling bitmap...");
jclass bitmapCls = env->GetObjectClass(bitmap);
jmethodID recycleFunction = env->GetMethodID(bitmapCls, "recycle", "()V");
if (recycleFunction == 0)
LOGE("error recycling!");
return NULL;
env->CallVoidMethod(bitmap, recycleFunction);
//
//creating a new bitmap to put the pixels into it - using Bitmap Bitmap.createBitmap (int width, int height, Bitmap.Config config) :
//
LOGD("creating new 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, info.height, info.width, bitmapConfig);
//
// putting the pixels into the new bitmap:
//
if ((ret = AndroidBitmap_lockPixels(env, newBitmap, &bitmapPixels)) < 0)
LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
return NULL;
uint32_t* newBitmapPixels = (uint32_t*) bitmapPixels;
int whereToPut = 0;
for (int x = info.width - 1; x >= 0; --x)
for (int y = 0; y < info.height; ++y)
uint32_t pixel = tempPixels[info.width * y + x];
newBitmapPixels[whereToPut++] = pixel;
AndroidBitmap_unlockPixels(env, newBitmap);
//
// freeing the native memory used to store the pixels
//
delete[] tempPixels;
return newBitmap;
java 文件:
static
System.loadLibrary("JniTest");
/**
* rotates a bitmap by 90 degrees counter-clockwise . <br/>
* notes:<br/>
* -the input bitmap will be recycled and shouldn't be used anymore <br/>
* -returns the rotated bitmap . <br/>
* -could take some time , so do the operation in a new thread
*/
public native Bitmap rotateBitmapCcw90(Bitmap bitmap);
...
Bitmap rotatedImage=rotateBitmapCcw90(bitmapToRotate);
编辑:在我得到答案后,我希望将这段代码和关于它的注释分享给大家:
为了使其正常工作,我已将代码中的每个“uint16_t”实例替换为“uint32_t”(这是我询问的代码中的错误)。
李>输入和输出位图必须使用 8888 配置(即 ARGB)
输入位图将在此过程中被回收。
代码将图像逆时针旋转 90 度。当然你可以根据自己的需要来改变它。
更好的解决方案
我写了一篇很好的帖子,包含此功能和其他功能,here。
【问题讨论】:
既然您使用ARGB_8888
格式,您应该使用uint32_t
创建旋转位图吗?
是什么让您认为在 C 中保存位图比在 Java 中占用更少的内存?
harism ,改变它,它的工作!谢谢 。请给出一个答案,以便我可以勾选它。 @Ridcully:它没有。它消除了最大堆大小的限制,这在某些情况下可能太低。例如,8MP 相机图像可能需要 30MB 或 RAM,使用正常旋转技术将使其使用两倍的内存量。
@harism ,你知道我如何还可以使用其他位图配置,以及我是否需要为它们使用 uint16_t 吗?
@androiddeveloper 不幸的是,我不确定如何将 RGB_565 打包到内存中。我猜他们将它们打包为uint16_t
以节省内存,但最好先验证这一点。
【参考方案1】:
由于您使用的是ARGB_8888
格式,因此每个像素都是uint32_t
而不是uint16_t
。尝试将您的旋转位图创建更改为使用 uint32_t
作为源和目标数组,它应该会更好。
【讨论】:
谢谢。效果很好。我不敢相信我有这么多的工作,我没有做这件事。 你对这段代码有什么好的速度优化技巧吗?以上是关于使用 JNI 和 NDK 旋转位图的主要内容,如果未能解决你的问题,请参考以下文章