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:
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.
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.
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
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