"Sin embargo, no tengo idea de cómo asegurarme de que Bitmap se mantenga como 32 bits durante todo el proceso."
Quería publicar una solución alternativa, que se ocupa de mantener la configuración ARGB_8888 intacta. NOTA: Este código solo decodifica bitmaps y necesita ser extendido, por lo que podría almacenar un mapa de bits.
Asumo que está escribiendo código para una versión de Android inferior a 3,2 (nivel API < 12), ya que desde entonces el comportamiento de los métodos
BitmapFactory.decodeFile(pathToImage);
BitmapFactory.decodeFile(pathToImage, opt);
bitmapObject.createScaledBitmap(bitmap, desiredWidth, desiredHeight, false /*filter?*/);
ha cambiado.
En las plataformas anteriores (nivel API < 12) los métodos BitmapFactory.decodeFile (..) intentan devolver un mapa de bits con configuración RGB_565 por defecto, si no pueden encontrar ningún alfa, lo que reduce la calidad de una iamge. Esto es todavía bien, porque se puede hacer cumplir un mapa de bits utilizando ARGB_8888
options.inPrefferedConfig = Bitmap.Config.ARGB_8888
options.inDither = false
El verdadero problema viene cuando cada píxel de la imagen tiene un valor alfa de 255 (es decir, totalmente opaco). En ese caso, el indicador de mapa de bits 'hasAlpha' se establece en falso, aunque su Bitmap tenga la configuración ARGB_8888. Si su * .png-file tenía al menos un píxel real transparente, esta bandera se habría establecido en verdadero y no tendría que preocuparse por nada.
Por eso, cuando se desea crear un mapa de bits a escala utilizando
bitmapObject.createScaledBitmap(bitmap, desiredWidth, desiredHeight, false /*filter?*/);
el método comprueba si la bandera 'hasAlpha' se establece en verdadero o falso, y en su caso se establece en false, lo que resulta en obtener un mapa de bits escalado, que se convirtió automáticamente al formato RGB_565.
tanto en el nivel API> = 12 hay un método público denominado
public void setHasAlpha (boolean hasAlpha);
que habría resuelto este problema. Hasta ahora, esto fue solo una explicación del problema. Hice algunas investigaciones y noté que el método setHasAlpha existe desde hace mucho tiempo y es público, pero ha estado oculto (@hide annotation). Así es como se define en 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);
}
Ahora aquí es mi propuesta de solución. No se trata de cualquier copia de los datos de mapa de bits:
controlados en tiempo de ejecución usando java.lang.reflect si la corriente aplicación de mapa de bits tiene un método público 'setHasAplha'. (Según mis pruebas, funciona perfectamente desde API nivel 3, y no he probado versiones inferiores, porque JNI no funcionaría). Puede tener problemas si un fabricante explícitamente lo ha hecho privado, protegido o eliminado.
Llamar al método 'setHasAlpha' para un objeto de mapa de bits dado utilizando JNI. Esto funciona perfectamente, incluso para métodos o campos privados. Es oficial que JNI no verifica si está violando las reglas de control de acceso o no. Fuente: http://java.sun.com/docs/books/jni/html/pitfalls.html (10.9) Esto nos da una gran potencia, que debe usarse con prudencia. No trataría de modificar un campo final, incluso si funcionaría (solo para dar un ejemplo). Y por favor, tenga en cuenta que esto es sólo una solución ...
Aquí es mi aplicación de todos los métodos necesarios:
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;
}
Carga tu lib y declarar el método nativo:
static {
System.loadLibrary("bitmaputils");
}
private static native int setHasAlphaNative(Bitmap bitmap, boolean value);
sección nativo (carpeta '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
}
Eso es todo. Hemos terminado. Publiqué todo el código para copiar y pegar. El código real no es tan grande, pero hacer todos estos controles de error paranoides lo hace mucho más grande. Espero que esto pueda ser útil para cualquiera.
¿Está cambiando el tamaño de las imágenes para resolver problemas con varias densidades de pantalla? Si es así, supongo que ejecutará este código en el arranque inicial y no en el inicio posterior ... ¿O lo está ejecutando localmente para obtener nuevos activos (es decir, imágenes) para pantallas pequeñas? Solo tengo curiosidad porque me estoy encontrando con errores en pantallas de densidad pequeña. – gary
En mi caso, no se trata realmente de la densidad de la pantalla. Solo estoy intentando colocar un conjunto de imágenes en un contenedor determinado. – cottonBallPaws