2010-05-21 18 views
6

provo a leggere un'immagine da sdcard (in emulatore) e poi creare un'immagine bitmap con il metodoAndroid - immagine Leggi PNG senza alfa e decodifica come ARGB_8888

BitmapFactory.decodeByteArray

. Ho impostato le opzioni:

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

Poi estrarre i pixel in un ByteBuffer.

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

Io uso questo ByteBuffer poi nella JNI per convertirlo in formato RGB e desidera calcolare su di esso.

Ma ottengo sempre dati falsi - Eseguo il test senza modificare ByteBuffer. L'unica cosa che faccio è metterlo nel metodo nativo in JNI. Quindi gettalo in un unsigned char* e convertilo nuovamente in un ByteBuffer prima di restituirlo a Java.

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

Prima di visualizzare l'immagine che ho ottenere i dati indietro con

bitmap.copyPixelsFromBuffer(buffer)

Ma poi ha dati errati in esso.

mia domanda è se questo è perché l'immagine viene convertita internamente in RGB 565 o che cosa è sbagliato?

.....

avere una risposta per esso:

- >>> sì, è convertito internamente in RGB565.

Qualcuno sa come creare una tale immagine bitmap da PNG con il formato ARGB8888 pixel?

Se qualcuno ha un'idea, sarebbe fantastico!

+0

Il punto è che ho bisogno dei dati di immagine per il calcolo su di essa in una parte di codice nativo. Per il test (emulatore) prendo l'immagine PNG dalla scheda SD e quindi eseguirò la sequenza di immagini dalla videocamera Android. Desidero che le immagini a 24 bit non perda alcuna informazione prima di calcolarle ... PS: dov'è finito il comandante dell'altro? – user345982

+1

ok, proverò in un altro modo. Voglio fare il modo più veloce per ottenere i dati dell'immagine da un'immagine. Successivamente, eseguirò l'elaborazione delle immagini sui dati grezzi. Qual è il modo più veloce per estrarre le informazioni dei pixel dell'immagine in un byte []? saluti, F. – user345982

risposta

11

Un ARGB_8888 Bitmap (sulle versioni pre Honeycomb) è nativamente memorizzati nel formato RGBA. Quindi il canale alfa viene spostato alla fine. Si dovrebbe tenerne conto quando si accede ai pixel di una Bitmap in modo nativo.

Presumo che si sta scrivendo codice per una versione di Android inferiore a 3,2 (livello di API < 12), perché da allora il comportamento dei metodi

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

è cambiato.

Su piattaforme precedenti (livello API < 12) i metodi BitmapFactory.decodeFile (..) tentano di restituire una bitmap con la configurazione RGB_565 per impostazione predefinita, se non riescono a trovare alcun alfa, che riduce la qualità di un iamge.Questo è ancora ok, perché si può applicare una bitmap ARGB_8888 utilizzando

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

Il vero problema arriva quando ciascun pixel dell'immagine ha un valore alfa di 255 (cioè completamente opaco). In tal caso il flag della bitmap 'hasAlpha' è impostato su false, anche se la tua bitmap ha la configurazione ARGB_8888. Se il tuo file * .png avesse almeno un pixel trasparente reale, questo flag sarebbe stato impostato su true e non avresti dovuto preoccuparti di nulla.

Quindi, quando si desidera creare una bitmap in scala utilizzando

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

il metodo verifica se il flag 'hasAlpha' è impostata su vero o falso, e nel tuo caso è impostato su false, che si traduce in ottenendo una bitmap in scala, che è stata automaticamente convertita nel formato RGB_565.

Pertanto il livello di API> = 12 v'è un metodo pubblico chiamato

public void setHasAlpha (boolean hasAlpha); 

che avrebbe risolto questo problema. Finora questa era solo una spiegazione del problema. Ho fatto qualche ricerca e ho notato che il metodo setHasAlpha esiste da molto tempo ed è pubblico, ma è stato nascosto (annotazione @hide). Ecco come viene definito su 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); 
} 

Ora ecco la mia proposta di soluzione. Non comporta alcuna copia dei dati bitmap:

  1. Controllato in fase di esecuzione utilizzando java.lang.Reflect se la corrente implementazione Bitmap ha un metodo pubblico 'setHasAplha'. (Secondo i miei test funziona perfettamente dal livello API 3, e non ho testato versioni inferiori, perché JNI non avrebbe funzionato). Potresti avere problemi se un produttore lo ha esplicitamente reso privato, protetto o cancellato.

  2. Chiamare il metodo "setHasAlpha" per un determinato oggetto Bitmap utilizzando JNI. Funziona perfettamente, anche per metodi o campi privati. È ufficiale che JNI non controlli se stai violando le regole di controllo degli accessi o meno. Fonte: http://java.sun.com/docs/books/jni/html/pitfalls.html (10,9) Questo ci dà un grande potere, che dovrebbe essere usato con saggezza. Non proverei a modificare un campo finale, anche se funzionasse (solo per fare un esempio). E vi prego di notare che questo è solo una soluzione ...

Ecco il mio attuazione di tutti i metodi necessari:

JAVA PARTE:

// 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; 
    } 

Caricare il lib e dichiarare il metodo nativo:

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

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

sezione Native (cartella '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 
} 

Questo è tutto. Abbiamo chiuso. Ho pubblicato l'intero codice per scopi di copia e incolla. Il codice attuale non è così grande, ma fare tutti questi controlli di errore paranoico lo rende molto più grande. Spero che questo possa essere utile a chiunque.

+0

non c'è alternativa per il metodo setHasAlpha per i livelli di API meno di 12? Ho un dispositivo che ha lo stesso problema, e purtroppo 2.3.6 –

+0

dispiace ho letto la parte JNI, il suo essere fatto in JNI :) –

+0

Ma da dove viene il metodo get chiamato inorder per impostare alpha su true per 2,3 versioni –

Problemi correlati