2010-05-21 5 views
6

je tente de lire une image de sdcard (dans l'émulateur), puis créer une image bitmap avec la méthodeAndroid - Lire l'image PNG sans alpha et decode comme ARGB_8888

BitmapFactory.decodeByteArray

. Je mets les options:

options.inPrefferedConfig = Bitmap.Config.ARGB_8888
options.inDither = false

Puis-je extraire les pixels dans un ByteBuffer.

ByteBuffer buffer = ByteBuffer.allocateDirect(width*height*4)
bitmap.copyPixelsToBuffer(buffer)

J'utilise cette ByteBuffer puis dans la JNI pour le convertir en format RVB et que vous voulez calculer sur elle.

Mais toujours j'obtiens des données fausses - je teste sans modifier le ByteBuffer. La seule chose que je fais est de le mettre dans la méthode native dans JNI. Puis le jeter dans un unsigned char* et le reconvertir en un ByteBuffer avant de le retourner en arrière à Java.

unsigned char* buffer = (unsinged char*)(env->GetDirectBufferAddress(byteBuffer))
jobject returnByteBuffer = env->NewDirectByteBuffer(buffer, length)

Avant d'afficher l'image que je récupérer des données avec

bitmap.copyPixelsFromBuffer(buffer)

Mais il a mal les données qu'il contient.

Ma question est si c'est parce que l'image est convertie en interne en RVB 565 ou ce qui ne va pas ici?

.....

une réponse pour elle:

- >>> oui, il est converti en interne à RGB565.

Est-ce que quelqu'un sait comment créer une telle image bitmap à partir de PNG avec le format de pixel ARGB8888?

Si quelqu'un a une idée, ce serait génial!

+0

Le point est que j'ai besoin des données d'image pour le calculer dans une partie de code natif. Pour tester (émulateur), je prends l'image PNG de la carte SD, puis je vais prendre une séquence d'images de la caméra Android. Je veux que les images 24Bit ne perdent aucune information avant de les calculer ... PS: où est le nom de l'autre gars disparu? – user345982

+1

ok, je vais essayer d'une autre façon. Je veux faire le moyen le plus rapide d'extraire les données d'image d'une image. Après cela, je vais faire un traitement d'image sur les données brutes. Quel est le moyen le plus rapide pour extraire les informations de pixel d'image dans un octet []? Cordialement, F. – user345982

Répondre

11

Une ARGB_8888 Bitmap (sur les versions antérieures en nid d'abeille) est en mode natif réalisés en format RGBA. Ainsi, le canal alpha est déplacé à la fin. Vous devriez en tenir compte lors de l'accès natif aux pixels d'un bitmap.

Je suppose que vous écrivez du code pour une version d'Android inférieure à 3,2 (niveau de l'API < 12), parce que depuis lors, le comportement des méthodes

BitmapFactory.decodeFile(pathToImage); 
BitmapFactory.decodeFile(pathToImage, opt); 
bitmapObject.createScaledBitmap(bitmap, desiredWidth, desiredHeight, false /*filter?*/); 

a changé. Sur les anciennes plateformes (niveau API < 12), les méthodes BitmapFactory.decodeFile (..) tentent de renvoyer un bitmap avec la configuration RGB_565 par défaut, si elles ne trouvent pas d'alpha, ce qui réduit la qualité d'un iamge.Cela est toujours ok, parce que vous pouvez appliquer un bitmap ARGB_8888 en utilisant

options.inPrefferedConfig = Bitmap.Config.ARGB_8888 
options.inDither = false 

Le vrai problème vient quand chaque pixel de l'image a une valeur alpha de 255 (à savoir complètement opaque). Dans ce cas, l'indicateur de bitmap 'hasAlpha' est défini sur false, même si votre bitmap a une config ARGB_8888. Si votre fichier * .png avait au moins un vrai pixel transparent, ce drapeau aurait été mis à true et vous n'auriez pas à vous soucier de quoi que ce soit.

Ainsi, lorsque vous voulez créer un Bitmap mis à l'échelle en utilisant

bitmapObject.createScaledBitmap(bitmap, desiredWidth, desiredHeight, false /*filter?*/); 

la méthode vérifie si le drapeau « hasAlpha » est réglé sur vrai ou faux, et dans votre cas, il est défini sur false, ce qui entraîne l'obtention d'un bitmap mis à l'échelle, qui a été automatiquement converti au format RGB_565.

Par conséquent au niveau de l'API> = 12 il existe une méthode publique appelée

public void setHasAlpha (boolean hasAlpha); 

qui aurait résolu cette question. Jusqu'à présent, c'était juste une explication du problème. J'ai fait quelques recherches et j'ai remarqué que la méthode setHasAlpha existe depuis longtemps et qu'elle est publique, mais a été cachée (@hide annotation). Voici comment il est défini sur Android 2.3:

/** 
* Tell the bitmap if all of the pixels are known to be opaque (false) 
* or if some of the pixels may contain non-opaque alpha values (true). 
* Note, for some configs (e.g. RGB_565) this call is ignore, since it does 
* not support per-pixel alpha values. 
* 
* This is meant as a drawing hint, as in some cases a bitmap that is known 
* to be opaque can take a faster drawing case than one that may have 
* non-opaque per-pixel alpha values. 
* 
* @hide 
*/ 
public void setHasAlpha(boolean hasAlpha) { 
    nativeSetHasAlpha(mNativeBitmap, hasAlpha); 
} 

Maintenant, voici ma proposition de solution. Il ne comporte pas de copie des données bitmap:

  1. Vérifié lors de l'exécution en utilisant java.lang.reflect si la mise en œuvre Bitmap actuelle a une méthode « setHasAplha » public. (Selon mes tests cela fonctionne parfaitement depuis le niveau 3 de l'API, et je n'ai pas testé les versions inférieures, car JNI ne fonctionnerait pas). Vous pouvez avoir des problèmes si un fabricant l'a explicitement rendu privé, protégé ou supprimé.

  2. Appelez la méthode 'setHasAlpha' pour un objet Bitmap donné en utilisant JNI. Cela fonctionne parfaitement, même pour des méthodes ou des champs privés. Il est officiel que JNI ne vérifie pas si vous violez les règles de contrôle d'accès ou non. Source: http://java.sun.com/docs/books/jni/html/pitfalls.html (10.9) Cela nous donne une grande puissance, qui devrait être utilisé à bon escient. Je n'essaierais pas de modifier un dernier champ, même si cela fonctionnerait (juste pour donner un exemple). Et s'il vous plaît noter que ceci est juste une solution ...

Voici ma mise en œuvre de toutes les méthodes nécessaires:

JAVA PARTIE:

// NOTE: this cannot be used in switch statements 
    private static final boolean SETHASALPHA_EXISTS = setHasAlphaExists(); 

    private static boolean setHasAlphaExists() { 
     // get all puplic Methods of the class Bitmap 
     java.lang.reflect.Method[] methods = Bitmap.class.getMethods(); 
     // search for a method called 'setHasAlpha' 
     for(int i=0; i<methods.length; i++) { 
      if(methods[i].getName().contains("setHasAlpha")) { 
       Log.i(TAG, "method setHasAlpha was found"); 
       return true; 
      } 
     } 
     Log.i(TAG, "couldn't find method setHasAlpha"); 
     return false; 
    } 

    private static void setHasAlpha(Bitmap bitmap, boolean value) { 
     if(bitmap.hasAlpha() == value) { 
      Log.i(TAG, "bitmap.hasAlpha() == value -> do nothing"); 
      return; 
     } 

     if(!SETHASALPHA_EXISTS) { // if we can't find it then API level MUST be lower than 12 
      // couldn't find the setHasAlpha-method 
      // <-- provide alternative here... 
      return; 
     } 

     // using android.os.Build.VERSION.SDK to support API level 3 and above 
     // use android.os.Build.VERSION.SDK_INT to support API level 4 and above 
     if(Integer.valueOf(android.os.Build.VERSION.SDK) <= 11) { 
      Log.i(TAG, "BEFORE: bitmap.hasAlpha() == " + bitmap.hasAlpha()); 
      Log.i(TAG, "trying to set hasAplha to true"); 
      int result = setHasAlphaNative(bitmap, value); 
      Log.i(TAG, "AFTER: bitmap.hasAlpha() == " + bitmap.hasAlpha()); 

      if(result == -1) { 
       Log.e(TAG, "Unable to access bitmap."); // usually due to a bug in the own code 
       return; 
      } 
     } else { //API level >= 12 
      bitmap.setHasAlpha(true); 
     } 
    } 

    /** 
    * Decodes a Bitmap from the SD card 
    * and scales it if necessary 
    */ 
    public Bitmap decodeBitmapFromFile(String pathToImage, int pixels_limit) { 
     Bitmap bitmap; 

     Options opt = new Options(); 
     opt.inDither = false; //important 
     opt.inPreferredConfig = Bitmap.Config.ARGB_8888; 
     bitmap = BitmapFactory.decodeFile(pathToImage, opt); 

     if(bitmap == null) { 
      Log.e(TAG, "unable to decode bitmap"); 
      return null; 
     } 

     setHasAlpha(bitmap, true); // if necessary 

     int numOfPixels = bitmap.getWidth() * bitmap.getHeight(); 

     if(numOfPixels > pixels_limit) { //image needs to be scaled down 
      // ensures that the scaled image uses the maximum of the pixel_limit while keeping the original aspect ratio 
      // i use: private static final int pixels_limit = 1280*960; //1,3 Megapixel 
      imageScaleFactor = Math.sqrt((double) pixels_limit/(double) numOfPixels); 
      Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, 
        (int) (imageScaleFactor * bitmap.getWidth()), (int) (imageScaleFactor * bitmap.getHeight()), false); 

      bitmap.recycle(); 
      bitmap = scaledBitmap; 

      Log.i(TAG, "scaled bitmap config: " + bitmap.getConfig().toString()); 
      Log.i(TAG, "pixels_limit = " + pixels_limit); 
      Log.i(TAG, "scaled_numOfpixels = " + scaledBitmap.getWidth()*scaledBitmap.getHeight()); 

      setHasAlpha(bitmap, true); // if necessary 
     } 

     return bitmap; 
    } 

Chargez votre lib et déclarer la méthode native:

static { 
    System.loadLibrary("bitmaputils"); 
} 

private static native int setHasAlphaNative(Bitmap bitmap, boolean value); 

section native (dossier 'JNI')

Android.mk:

LOCAL_PATH := $(call my-dir) 

include $(CLEAR_VARS) 
LOCAL_MODULE := bitmaputils 
LOCAL_SRC_FILES := bitmap_utils.c 
LOCAL_LDLIBS := -llog -ljnigraphics -lz -ldl -lgcc 
include $(BUILD_SHARED_LIBRARY) 

bitmapUtils.c:

#include <jni.h> 
#include <android/bitmap.h> 
#include <android/log.h> 

#define LOG_TAG "BitmapTest" 
#define Log_i(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) 
#define Log_e(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) 


// caching class and method IDs for a faster subsequent access 
static jclass bitmap_class = 0; 
static jmethodID setHasAlphaMethodID = 0; 

jint Java_com_example_bitmaptest_MainActivity_setHasAlphaNative(JNIEnv * env, jclass clazz, jobject bitmap, jboolean value) { 
    AndroidBitmapInfo info; 
    void* pixels; 


    if (AndroidBitmap_getInfo(env, bitmap, &info) < 0) { 
     Log_e("Failed to get Bitmap info"); 
     return -1; 
    } 

    if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) { 
     Log_e("Incompatible Bitmap format"); 
     return -1; 
    } 

    if (AndroidBitmap_lockPixels(env, bitmap, &pixels) < 0) { 
     Log_e("Failed to lock the pixels of the Bitmap"); 
     return -1; 
    } 


    // get class 
    if(bitmap_class == NULL) { //initializing jclass 
     // NOTE: The class Bitmap exists since API level 1, so it just must be found. 
     bitmap_class = (*env)->GetObjectClass(env, bitmap); 
     if(bitmap_class == NULL) { 
      Log_e("bitmap_class == NULL"); 
      return -2; 
     } 
    } 

    // get methodID 
    if(setHasAlphaMethodID == NULL) { //initializing jmethodID 
     // NOTE: If this fails, because the method could not be found the App will crash. 
     // But we only call this part of the code if the method was found using java.lang.Reflect 
     setHasAlphaMethodID = (*env)->GetMethodID(env, bitmap_class, "setHasAlpha", "(Z)V"); 
     if(setHasAlphaMethodID == NULL) { 
      Log_e("methodID == NULL"); 
      return -2; 
     } 
    } 

    // call java instance method 
    (*env)->CallVoidMethod(env, bitmap, setHasAlphaMethodID, value); 

    // if an exception was thrown we could handle it here 
    if ((*env)->ExceptionOccurred(env)) { 
     (*env)->ExceptionDescribe(env); 
     (*env)->ExceptionClear(env); 
     Log_e("calling setHasAlpha threw an exception"); 
     return -2; 
    } 

    if(AndroidBitmap_unlockPixels(env, bitmap) < 0) { 
     Log_e("Failed to unlock the pixels of the Bitmap"); 
     return -1; 
    } 

    return 0; // success 
} 

C'est tout. Nous avons fini. J'ai posté l'intégralité du code à des fins de copier-coller. Le code réel n'est pas si grand, mais en faisant tous ces contrôles d'erreur paranoïaque, il est beaucoup plus gros. J'espère que cela pourrait être utile à tout le monde.

+0

n'y a-t-il pas d'alternative pour la méthode setHasAlpha pour les niveaux api inférieurs à 12? J'ai un périphérique qui a le même problème, et malheureusement 2.3.6 –

+0

désolé j'ai lu la partie de jni, son étant fait dans le jni :) –

+0

mais où la méthode est-elle appelée inorder pour mettre l'alpha à vrai pour 2.3 versions –