BitmapFactory.decodeResource devuelve un bitmap mutable en Android 2.2 y un bitmap inmutables en Android 1.6

Estoy desarrollando una aplicación y probándola en mi dispositivo con Android 2.2. En mi código, hago uso de un bitmap 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 bitmap es inmutable. La documentación no dice nada sobre decodeResource devolviendo un bitmap inmutables, pero claramente ese debe ser el caso.

¿Hay una llamada diferente que pueda hacer para obtener un bitmap 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 canvas envolviéndolo, pero eso requeriría dos mapas de bits de igual tamaño usando hasta el doble de memoria que había previsto)?

Puede convertir su bitmap inmutables en un bitmap mutable.

Encontré una solución aceptable que usa solo la memoria de un bitmap.

Un bitmap de origen se guarda en bruto (RandomAccessFile) en el disco (sin memoria RAM), luego se libera el bitmap de origen (ahora no hay bitmap en la memoria) y luego la información del archivo se carga en otro bitmap. De esta forma es posible hacer una copia de bitmap con solo un bitmap almacenado en la memoria RAM por tiempo.

Vea la solución completa y la implementación aquí: Android: convertir bitmap inmutables en Mutable

Agrego una mejora a esta solución, que ahora funciona con cualquier tipo de bitmap (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; } 

Copie el bitmap 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); 

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.
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 bitmap original y usar los datos JNI para crear un nuevo bitmap, 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.

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 objective de 19, BitmapRegionDecoder devolvió un bitmap inmutables, haciendo caso omiso de nuestras BitMapFactory.options

Lo que no funcionará

  • abriendo una imagen mutable con BitmapFactory (que respeta nuestra opción inMutable ) 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 bitmap mutable, y realizar nuestras operaciones de píxeles
  • guarde el bitmap en el archivo y recíclelo de la memoria (aún no se ha recortado)
  • abra el bitmap guardado con BitmapRegionDecoder , abriendo solo la región a cortar (ahora no nos importa si el bitmap es inmutable o no)
  • guarde este bitmap (que se ha recortado efectivamente) en un archivo, sobrescribiendo el bitmap previamente guardado (que no se recortó)

Con este método, podemos recortar y realizar el procesamiento de píxeles en un bitmap 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.

Sé que la pregunta está resuelta, pero ¿qué hay de:

BitmapFactory.decodeStream(getResources().openRawResource(getResources().getIdentifier("bitmapname", "drawable", context.getPackageName())))