JNI operaciones de mapa de bits, para ayudar a evitar OOM cuando se utilizan imágenes grandes

fondo

La mayoría de las veces, obtener MOO en android se debe a la utilización de muchos mapas de bits y / o la creación de grandes mapas de bits.

Recientemente he decidido probar JNI con el fin de permitir la evitación de OOM por el almacenamiento de los datos en sí en el lado JNI.

Después de jugar con JNI por un tiempo, he creado algunos posts en SO pidiendo ayuda y compartiendo mis conocimientos, y ahora he decidido compartir un poco más de código contigo. Aquí están los posts en caso de que alguien esté interesado en leer los hallazgos o contribuir:

  • Cómo almacenar en caché los mapas de bits en la memoria nativa

  • Decodificación y manipulación de imágenes mediante JNI en android

  • JNI – ¿Cómo usar varias instancias de contenedor de Jni con diferentes campos?

  • Rotación de un mapa de bits mediante JNI y NDK

Esta vez, he añadido la capacidad de almacenar, restaurar, recortar y rotar mapas de bits. Debería ser fácil agregar más opciones y estaría feliz si otras personas aquí agregaran su propio código a funciones más útiles .

Así que el código que estoy a punto de mostrar es en realidad la fusión de todas las cosas que he creado.

Ejemplo de código de uso:

Bitmap bitmap=BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher); final int width=bitmap.getWidth(),height=bitmap.getHeight(); // store the bitmap in the JNI "world" final JniBitmapHolder bitmapHolder=new JniBitmapHolder(bitmap); // no need for the bitmap on the java "world", since the operations are done on the JNI "world" bitmap.recycle(); // crop a center square from the bitmap, from (0.25,0.25) to (0.75,0.75) of the bitmap. bitmapHolder.cropBitmap(width/4,height/4,width*3/4,height*3/4); //rotate the bitmap: bitmapHolder.rotateBitmapCcw90(); //get the output java bitmap , and free the one on the JNI "world" bitmap=bitmapHolder.getBitmapAndFree(); 

El proyecto está disponible en github

  • La página del proyecto está disponible en github aquí .

  • No dude en dar consejos y contribuir.

notas importantes

Las mismas notas que se muestran aquí , además de:

  • Funciones actuales que se escriben aquí (más actualizadas en la página del proyecto):

    • almacenar

    • restaurar

    • Gire 90 grados en sentido antihorario

    • cultivo.

  • El enfoque que he tomado para este código es a la vez la eficiencia de la memoria (utilizar sólo la memoria que necesito, y libre cuando no se necesita), y la eficiencia de la CPU (he intentado utilizar punteros y optimizaciones de memoria caché de memoria siempre que sea posible).

  • Para obtener el mejor rendimiento, he hecho muy pocas validaciones, especialmente en la parte JNI. Podría ser mejor administrar las validaciones en el "mundo" java.

  • Todavía hay muchas características que faltan que creo que debe agregarse, y espero que tenga el tiempo para agregarlos. Si alguien desea contribuir, estaré encantado de añadir que el código también. Aquí están las funciones que creo que podrían ser útiles:

    • Obtener información de mapa de bits actual

    • Bitmaps de escala, incluyendo la elección de qué algoritmo utilizar (el vecino más cercano y la interpolación bilineal debe ser suficiente).

    • Utilizar diferentes formatos de mapa de bits

    • Hacer la decodificación dentro de JNI, para evitar la creación del mapa de bits de Java (y no utilizar el montón en el mundo de Java) desde el principio, sólo al final, cuando terminó con todas las operaciones.

    • Detección de rostro

    • Rotación en cualquier ángulo, o al menos los obvios. Actualmente solo he añadido rotación de 90 grados en sentido contrario al reloj.

NOTA: este es un código antiguo. Para el más actualizado, echa un vistazo a la página del proyecto en github .

Jni / Android.mk

 LOCAL_PATH := $(call my-dir) #bitmap operations module include $(CLEAR_VARS) LOCAL_MODULE := JniBitmapOperations LOCAL_SRC_FILES := JniBitmapOperations.cpp LOCAL_LDLIBS := -llog LOCAL_LDFLAGS += -ljnigraphics include $(BUILD_SHARED_LIBRARY) APP_OPTIM := debug LOCAL_CFLAGS := -g #if you need to add more module, do the same as the one we started with (the one with the CLEAR_VARS) 

Jni / JniBitmapOperations.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_jni_bitmap_1operations_JniBitmapHolder_jniStoreBitmapData(JNIEnv * env, jobject obj, jobject bitmap); JNIEXPORT jobject JNICALL Java_com_jni_bitmap_1operations_JniBitmapHolder_jniGetBitmapFromStoredBitmapData(JNIEnv * env, jobject obj, jobject handle); JNIEXPORT void JNICALL Java_com_jni_bitmap_1operations_JniBitmapHolder_jniFreeBitmapData(JNIEnv * env, jobject obj, jobject handle); JNIEXPORT void JNICALL Java_com_jni_bitmap_1operations_JniBitmapHolder_jniRotateBitmapCcw90(JNIEnv * env, jobject obj, jobject handle); JNIEXPORT void JNICALL Java_com_jni_bitmap_1operations_JniBitmapHolder_jniCropBitmap(JNIEnv * env, jobject obj, jobject handle, uint32_t left, uint32_t top, uint32_t right, uint32_t bottom); } class JniBitmap { public: uint32_t* _storedBitmapPixels; AndroidBitmapInfo _bitmapInfo; JniBitmap() { _storedBitmapPixels = NULL; } }; /**crops the bitmap within to be smaller. note that no validations are done*/ // JNIEXPORT void JNICALL Java_com_jni_bitmap_1operations_JniBitmapHolder_jniCropBitmap(JNIEnv * env, jobject obj, jobject handle, uint32_t left, uint32_t top, uint32_t right, uint32_t bottom) { JniBitmap* jniBitmap = (JniBitmap*) env->GetDirectBufferAddress(handle); if (jniBitmap->_storedBitmapPixels == NULL) return; uint32_t* previousData = jniBitmap->_storedBitmapPixels; uint32_t oldWidth = jniBitmap->_bitmapInfo.width; uint32_t newWidth = right - left, newHeight = bottom - top; uint32_t* newBitmapPixels = new uint32_t[newWidth * newHeight]; uint32_t* whereToGet = previousData + left + top * oldWidth; uint32_t* whereToPut = newBitmapPixels; for (int y = top; y < bottom; ++y) { memcpy(whereToPut, whereToGet, sizeof(uint32_t) * newWidth); whereToGet += oldWidth; whereToPut += newWidth; } //done copying , so replace old data with new one delete[] previousData; jniBitmap->_storedBitmapPixels = newBitmapPixels; jniBitmap->_bitmapInfo.width = newWidth; jniBitmap->_bitmapInfo.height = newHeight; } /**rotates the inner bitmap data by 90 degress counter clock wise*/ // JNIEXPORT void JNICALL Java_com_jni_bitmap_1operations_JniBitmapHolder_jniRotateBitmapCcw90(JNIEnv * env, jobject obj, jobject handle) { JniBitmap* jniBitmap = (JniBitmap*) env->GetDirectBufferAddress(handle); if (jniBitmap->_storedBitmapPixels == NULL) return; uint32_t* previousData = jniBitmap->_storedBitmapPixels; AndroidBitmapInfo bitmapInfo = jniBitmap->_bitmapInfo; uint32_t* newBitmapPixels = new uint32_t[bitmapInfo.height * bitmapInfo.width]; int whereToPut = 0; // AD DC // ...>... // BC AB for (int x = bitmapInfo.width - 1; x >= 0; --x) for (int y = 0; y < bitmapInfo.height; ++y) { uint32_t pixel = previousData[bitmapInfo.width * y + x]; newBitmapPixels[whereToPut++] = pixel; } delete[] previousData; jniBitmap->_storedBitmapPixels = newBitmapPixels; uint32_t temp = bitmapInfo.width; bitmapInfo.width = bitmapInfo.height; bitmapInfo.height = temp; } /**free bitmap*/ // JNIEXPORT void JNICALL Java_com_jni_bitmap_1operations_JniBitmapHolder_jniFreeBitmapData(JNIEnv * env, jobject obj, jobject handle) { JniBitmap* jniBitmap = (JniBitmap*) env->GetDirectBufferAddress(handle); if (jniBitmap->_storedBitmapPixels == NULL) return; delete[] jniBitmap->_storedBitmapPixels; jniBitmap->_storedBitmapPixels = NULL; delete jniBitmap; } /**restore java bitmap (from JNI data)*/ // JNIEXPORT jobject JNICALL Java_com_jni_bitmap_1operations_JniBitmapHolder_jniGetBitmapFromStoredBitmapData(JNIEnv * env, jobject obj, jobject handle) { JniBitmap* jniBitmap = (JniBitmap*) env->GetDirectBufferAddress(handle); if (jniBitmap->_storedBitmapPixels == NULL) { LOGD("no bitmap data was stored. returning null..."); return NULL; } // //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..."); 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, jniBitmap->_bitmapInfo.width, jniBitmap->_bitmapInfo.height, bitmapConfig); // // putting the pixels into the new bitmap: // int ret; void* bitmapPixels; if ((ret = AndroidBitmap_lockPixels(env, newBitmap, &bitmapPixels)) < 0) { LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret); return NULL; } uint32_t* newBitmapPixels = (uint32_t*) bitmapPixels; int pixelsCount = jniBitmap->_bitmapInfo.height * jniBitmap->_bitmapInfo.width; memcpy(newBitmapPixels, jniBitmap->_storedBitmapPixels, sizeof(uint32_t) * pixelsCount); AndroidBitmap_unlockPixels(env, newBitmap); //LOGD("returning the new bitmap"); return newBitmap; } /**store java bitmap as JNI data*/ // JNIEXPORT jobject JNICALL Java_com_jni_bitmap_1operations_JniBitmapHolder_jniStoreBitmapData(JNIEnv * env, jobject obj, jobject bitmap) { AndroidBitmapInfo bitmapInfo; uint32_t* storedBitmapPixels = NULL; //LOGD("reading bitmap info..."); int ret; if ((ret = AndroidBitmap_getInfo(env, bitmap, &bitmapInfo)) < 0) { LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret); return NULL; } LOGD("width:%d height:%d stride:%d", bitmapInfo.width, bitmapInfo.height, bitmapInfo.stride); if (bitmapInfo.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; storedBitmapPixels = new uint32_t[bitmapInfo.height * bitmapInfo.width]; int pixelsCount = bitmapInfo.height * bitmapInfo.width; memcpy(storedBitmapPixels, src, sizeof(uint32_t) * pixelsCount); AndroidBitmap_unlockPixels(env, bitmap); JniBitmap *jniBitmap = new JniBitmap(); jniBitmap->_bitmapInfo = bitmapInfo; jniBitmap->_storedBitmapPixels = storedBitmapPixels; return env->NewDirectByteBuffer(jniBitmap, 0); } 

Src / com / jni / bitmap_operations / JniBitmapHolder.java

 package com.jni.bitmap_operations; import java.nio.ByteBuffer; import android.graphics.Bitmap; import android.util.Log; public class JniBitmapHolder { ByteBuffer _handler =null; static { System.loadLibrary("JniBitmapOperations"); } private native ByteBuffer jniStoreBitmapData(Bitmap bitmap); private native Bitmap jniGetBitmapFromStoredBitmapData(ByteBuffer handler); private native void jniFreeBitmapData(ByteBuffer handler); private native void jniRotateBitmapCcw90(ByteBuffer handler); private native void jniCropBitmap(ByteBuffer handler,final int left,final int top,final int right,final int bottom); public JniBitmapHolder() {} public JniBitmapHolder(final Bitmap bitmap) { storeBitmap(bitmap); } public void storeBitmap(final Bitmap bitmap) { if(_handler!=null) freeBitmap(); _handler=jniStoreBitmapData(bitmap); } public void rotateBitmapCcw90() { if(_handler==null) return; jniRotateBitmapCcw90(_handler); } public void cropBitmap(final int left,final int top,final int right,final int bottom) { if(_handler==null) return; jniCropBitmap(_handler,left,top,right,bottom); } public Bitmap getBitmap() { if(_handler==null) return null; return jniGetBitmapFromStoredBitmapData(_handler); } public Bitmap getBitmapAndFree() { final Bitmap bitmap=getBitmap(); freeBitmap(); return bitmap; } public void freeBitmap() { if(_handler==null) return; jniFreeBitmapData(_handler); _handler=null; } @Override protected void finalize() throws Throwable { super.finalize(); if(_handler==null) return; Log.w("DEBUG","JNI bitmap wasn't freed nicely.please rememeber to free the bitmap as soon as you can"); freeBitmap(); } } 
  • Convertir Bitmap a Byte matriz sin método de compresión en android
  • Rotación de un mapa de bits mediante JNI y NDK
  • Cambiar el tamaño de un mapa de bits
  • Rotar un mapa de bits guardado en android
  • Bandas de mapa de bits de Android
  • "No se pueden dibujar mapas de bits reciclados" con Picasso
  • Android: ¿Cómo mostrar un mapa de bits en un WebView?
  • Android: cómo convertir matriz de bytes a mapa de bits?
  • Tamaño de imagen de mapa de bits de Android en xml
  • La forma más rápida de rotar un mapa de bits de gran tamaño
  • ¿Es posible mover un fondo de azulejos? (Scroll infinito con TileMode.REPEAT)
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.