Drehen einer Bitmap mit JNI & NDK
Ich habe beschlossen, dass ich, da Bitmaps viel Speicher benötigen, was leicht zu Fehlern führen kann, die harte, speicherintensive Arbeit an C / C ++ - Code ausführen werde.
Die Schritte zum Drehen einer Bitmap sind:Bitmap-Info lesen (Breite, Höhe)Speichern Sie Bitmap-Pixel in einem Array.recyceln Sie die Bitmap.Erstellen Sie eine neue Bitmap mit entgegengesetzter Größe.Fügen Sie die Pixel in die neue Bitmap ein.Geben Sie die Pixel frei und geben Sie die Bitmap zurück.Das Problem:Auch wenn alles fehlerfrei zu laufen scheint, ist das Ausgabebild keine Drehung des Originals. Tatsächlich ruiniert es es vollständig.
Die Drehung sollte gegen den Uhrzeigersinn um 90 Grad erfolgen.
Beispiel (Screenshot ist vergrößert) für das, was ich bekomme:Wie Sie sehen, sind nicht nur die Farben seltsamer geworden, sondern auch die Größe stimmt nicht mit meiner Einstellung überein. Hier ist etwas wirklich komisch.
Vielleicht lese ich die Daten nicht richtig?
Dies ist natürlich nur ein Beispiel. Der Code sollte auf jeder Bitmap einwandfrei funktionieren, solange das Gerät über genügend Speicher verfügt, um ihn zu speichern. Außerdem möchte ich möglicherweise andere Operationen an der Bitmap ausführen, als sie zu drehen.
Code, den ich erstellt habe:Android.mk-Datei:
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-Datei:
#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-Datei:
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);
BEARBEITEN: Nachdem ich meine Antwort erhalten habe, möchte ich diesen Code und Notizen darüber an alle weitergeben:
Damit es funktioniert, habe ich im Code jede Instanz von "uint16_t" durch "uint32_t" ersetzt (das ist der Fehler in meinem Code, nach dem ich gefragt habe).
Eingabe- und Ausgabebitmap müssen mit 8888-Konfiguration sein (das ist ARGB)
Die Eingabe-Bitmap wird während des Vorgangs wiederverwendet.
Der Code dreht das Bild um 90 Grad gegen den Uhrzeigersinn. Natürlich können Sie es je nach Ihren Bedürfnissen ändern.
bessere lösungIch habe einen schönen Beitrag mit dieser Funktionalität und anderen gemacht,Hier .