java - how - BitmapFactory.decodeResource devuelve un mapa de bits mutable en Android 2.2 y un mapa de bits inmutables en Android 1.6
imageview bitmap android (6)
Estoy desarrollando una aplicación y probándola en mi dispositivo con Android 2.2. En mi código, hago uso de un mapa de bits que recupero usando BitmapFactory.decodeResource, y puedo hacer cambios llamando a bitmap.setPixels()
en él. Cuando IllegalStateException
esto en el dispositivo de un amigo que ejecuta Android 1.6, obtengo una IllegalStateException
en la llamada a bitmap.setPixels
. La documentación en línea dice que se IllegalStateException
una IllegalStateException
de este método cuando el mapa de bits es inmutable. La documentación no dice nada sobre decodeResource
devolviendo un mapa de bits inmutables, pero claramente ese debe ser el caso.
¿Hay una llamada diferente que pueda hacer para obtener un mapa de bits mutable confiablemente de un recurso de aplicación sin necesidad de un segundo objeto Bitmap
(podría crear un mutable del mismo tamaño y dibujar en un lienzo envolviéndolo, pero eso requeriría dos mapas de bits de igual tamaño usando hasta el doble de memoria que había previsto)?
Aquí hay una solución que he creado que usa el almacenamiento interno y no requiere ningún permiso nuevo, basado en la idea de "Derzu", y el hecho de que, comenzando con honeycomb, está integrado en:
/**decodes a bitmap from a resource id. returns a mutable bitmap no matter what is the API level.<br/>
might use the internal storage in some cases, creating temporary file that will be deleted as soon as it isn''t finished*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static Bitmap decodeMutableBitmapFromResourceId(final Context context, final int bitmapResId) {
final Options bitmapOptions = new Options();
if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB)
bitmapOptions.inMutable = true;
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), bitmapResId, bitmapOptions);
if (!bitmap.isMutable())
bitmap = convertToMutable(context, bitmap);
return bitmap;
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public static Bitmap convertToMutable(final Context context, final Bitmap imgIn) {
final int width = imgIn.getWidth(), height = imgIn.getHeight();
final Config type = imgIn.getConfig();
File outputFile = null;
final File outputDir = context.getCacheDir();
try {
outputFile = File.createTempFile(Long.toString(System.currentTimeMillis()), null, outputDir);
outputFile.deleteOnExit();
final RandomAccessFile randomAccessFile = new RandomAccessFile(outputFile, "rw");
final FileChannel channel = randomAccessFile.getChannel();
final MappedByteBuffer map = channel.map(MapMode.READ_WRITE, 0, imgIn.getRowBytes() * height);
imgIn.copyPixelsToBuffer(map);
imgIn.recycle();
final Bitmap result = Bitmap.createBitmap(width, height, type);
map.position(0);
result.copyPixelsFromBuffer(map);
channel.close();
randomAccessFile.close();
outputFile.delete();
return result;
} catch (final Exception e) {
} finally {
if (outputFile != null)
outputFile.delete();
}
return null;
}
Otra alternativa es usar JNI para poner los datos en él, reciclar el mapa de bits original y usar los datos JNI para crear un nuevo mapa de bits, que será mutable (automáticamente), así que junto con mi solución JNI para mapas de bits , uno puede Haz lo siguiente:
Bitmap bitmap=BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher);
final JniBitmapHolder bitmapHolder=new JniBitmapHolder(bitmap);
bitmap.recycle();
bitmap=bitmapHolder.getBitmapAndFree();
Log.d("DEBUG",""+bitmap.isMutable()); //will return true
sin embargo, no estoy seguro de cuál es el requisito mínimo del nivel de API. funciona muy bien en API 8 y superior.
Copie el mapa de bits a sí mismo con la opción mutable true. De esta forma, ni el consumo de memoria adicional ni las largas líneas de códigos son necesarios.
Bitmap bitmap= BitmapFactory.decodeResource(....);
bitmap= bitmap.copy(Bitmap.Config.ARGB_8888, true);
Primero podemos establecer opciones para BitmapFactory instanciando una clase BitmapFactory.Options y luego establecer el campo de opciones llamado ''inMutable'' como verdadero y luego pasar esta instancia de opciones a decodeResource.
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inMutable = true;
Bitmap bp = BitmapFactory.decodeResource(getResources(), R.raw.white, opt);
Puede convertir su mapa de bits inmutables en un mapa de bits mutable.
Encontré una solución aceptable que usa solo la memoria de un mapa de bits.
Un mapa de bits de origen se guarda en bruto (RandomAccessFile) en el disco (sin memoria RAM), luego se libera el mapa de bits de origen (ahora no hay mapa de bits en la memoria) y luego la información del archivo se carga en otro mapa de bits. De esta forma es posible hacer una copia de mapa de bits con solo un mapa de bits almacenado en la memoria RAM por tiempo.
Vea la solución completa y la implementación aquí: Android: convertir mapa de bits inmutables en Mutable
Agrego una mejora a esta solución, que ahora funciona con cualquier tipo de mapa de bits (ARGB_8888, RGB_565, etc.) y elimina el archivo temporal. Ver mi método:
/**
* Converts a immutable bitmap to a mutable bitmap. This operation doesn''t allocates
* more memory that there is already allocated.
*
* @param imgIn - Source image. It will be released, and should not be used more
* @return a copy of imgIn, but muttable.
*/
public static Bitmap convertToMutable(Bitmap imgIn) {
try {
//this is the file going to use temporally to save the bytes.
// This file will not be a image, it will store the raw image data.
File file = new File(Environment.getExternalStorageDirectory() + File.separator + "temp.tmp");
//Open an RandomAccessFile
//Make sure you have added uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
//into AndroidManifest.xml file
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
// get the width and height of the source bitmap.
int width = imgIn.getWidth();
int height = imgIn.getHeight();
Config type = imgIn.getConfig();
//Copy the byte to the file
//Assume source bitmap loaded using options.inPreferredConfig = Config.ARGB_8888;
FileChannel channel = randomAccessFile.getChannel();
MappedByteBuffer map = channel.map(MapMode.READ_WRITE, 0, imgIn.getRowBytes()*height);
imgIn.copyPixelsToBuffer(map);
//recycle the source bitmap, this will be no longer used.
imgIn.recycle();
System.gc();// try to force the bytes from the imgIn to be released
//Create a new bitmap to load the bitmap again. Probably the memory will be available.
imgIn = Bitmap.createBitmap(width, height, type);
map.position(0);
//load it back from temporary
imgIn.copyPixelsFromBuffer(map);
//close the temporary file and channel , then delete that also
channel.close();
randomAccessFile.close();
// delete the temp file
file.delete();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return imgIn;
}
Sé que la pregunta está resuelta, pero ¿qué hay de:
BitmapFactory.decodeStream(getResources().openRawResource(getResources().getIdentifier("bitmapname", "drawable", context.getPackageName())))
Sé que llego tarde a la fiesta, pero así es como evitamos este doloroso problema de Android, recortamos y modificamos una imagen con solo una copia en la memoria.
Situación
queremos procesar los píxeles de una versión recortada de una imagen guardada en un archivo. Con altas demandas de memoria, nunca queremos tener más de una copia de esta imagen en la memoria en un momento dado.
Lo que debería haber funcionado pero no lo hizo
Apertura de la subsección de imagen (el bit que quería recortar) con BitmapRegionDecoder
, pasando en un BitmapFactory.option
con inMutable = true
, procesando los píxeles y luego guardando en el archivo.
Aunque nuestra aplicación declaró un mínimo API de 14 y un objetivo de 19, BitmapRegionDecoder
devolvió un mapa de bits inmutables, haciendo caso omiso de nuestras BitMapFactory.options
Lo que no funcionará
- abriendo una imagen mutable con
BitmapFactory
(que respeta nuestra opcióninMutable
) y croppping: todas las técnicas de recorte no son imperfectas (requieren una copia de la imagen completa para que exista en la memoria a la vez, incluso si la basura se recoge inmediatamente después de sobrescribir y reciclar) ) - abrir una imagen inmutable con
BitmapRegionDecoder
(recortada efectivamente) y convertirla en una mutable; todas las técnicas disponibles nuevamente requieren una copia en la memoria.
La gran solución de 2014
- abrir la imagen de tamaño completo con
BitmapFactory
como un mapa de bits mutable, y realizar nuestras operaciones de píxeles - guarde el mapa de bits en el archivo y recíclelo de la memoria (aún no se ha recortado)
- abra el mapa de bits guardado con
BitmapRegionDecoder
, abriendo solo la región a cortar (ahora no nos importa si el mapa de bits es inmutable o no) - guarde este mapa de bits (que se ha recortado efectivamente) en un archivo, sobrescribiendo el mapa de bits previamente guardado (que no se recortó)
Con este método, podemos recortar y realizar el procesamiento de píxeles en un mapa de bits con solo 1 copia en la memoria (para evitar esos molestos errores OOM tanto como sea posible), intercambiando RAM por tiempo ya que tenemos que realizar un archivo extra (lento) IOs.