web-dev-qa-db-fra.com

BitmapFactory.decodeResource renvoie un bitmap mutable en Android 2.2 et un bitmap immuable en Android 1.6

Je développe une application et la teste sur mon appareil en exécutant Android 2.2. Dans mon code, j'utilise un bitmap que je récupère à l'aide de BitmapFactory.decodeResource, et je suis en mesure d'apporter des modifications en appeler bitmap.setPixels() dessus. Lorsque je teste cela sur le périphérique d'un ami exécutant Android 1.6, j'obtiens un IllegalStateException dans l'appel à bitmap.setPixels. La documentation en ligne indique qu'un IllegalStateException est jeté à partir de cette méthode lorsque le bitmap est immuable. La documentation ne dit rien au sujet de decodeResource renvoyant une image bitmap immuable, mais cela doit clairement être le cas.

Existe-t-il un appel différent que je peux faire pour obtenir un bitmap mutable de manière fiable à partir d'une ressource d'application sans avoir besoin d'un deuxième objet Bitmap (je pourrais créer un objet mutable de la même taille et dessiner dans un canevas l'enveloppant, mais ce serait besoin de deux bitmaps de taille égale en utilisant jusqu'à deux fois plus de mémoire que je l'avais prévu)?

33
Rich

Vous pouvez convertir votre bitmap immuable en un bitmap mutable.

J'ai trouvé une solution acceptable qui utilise uniquement la mémoire d'un bitmap.

Un bitmap source est enregistré brut (RandomAccessFile) sur le disque (pas de mémoire RAM), puis le bitmap source est publié, (maintenant, il n'y a pas de bitmap en mémoire), et après cela, les informations sur le fichier sont chargées dans un autre bitmap. De cette façon, il est possible de faire une copie bitmap ayant une seule bitmap stockée dans la mémoire RAM à la fois.

Voir la solution complète et l'implémentation ici: Android: convertir Immutable Bitmap en Mutable

J'ajoute une amélioration à cette solution, qui fonctionne maintenant avec n'importe quel type de Bitmaps (ARGB_8888, RGB_565, etc.) et supprime le fichier temporaire. Voir ma méthode:

/**
 * 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;
}
61
Derzu

Copiez le bitmap sur lui-même avec l'option mutable true. De cette façon, ni consommation de mémoire supplémentaire ni longues lignes de codes ne sont nécessaires.

Bitmap bitmap= BitmapFactory.decodeResource(....);
bitmap= bitmap.copy(Bitmap.Config.ARGB_8888, true);
47
Jesus Oliva

Nous pouvons d'abord définir des options pour BitmapFactory en instanciant une classe BitmapFactory.Options, puis définir le champ d'options nommé "inMutable" sur true, puis transmettre cette instance d'options à decodeResource.

 BitmapFactory.Options opt = new BitmapFactory.Options();
 opt.inMutable = true;
 Bitmap bp = BitmapFactory.decodeResource(getResources(), R.raw.white, opt);
26
Vaishak Nair

Voici une solution que j'ai créée qui utilise le stockage interne et ne nécessite aucune nouvelle autorisation, basée sur l'idée de "Derzu", et le fait qu'à partir du nid d'abeille, cela est intégré:

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

une autre alternative consiste à utiliser JNI afin d'y mettre les données, de recycler le bitmap d'origine et d'utiliser les données JNI pour créer un nouveau bitmap, qui sera (automatiquement) modifiable, donc avec ma solution JNI pour les bitmaps , on peut faire ce qui suit:

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

cependant, je ne sais pas quelle est l'exigence minimale du niveau API. cela fonctionne très bien sur API 8 et supérieur.

6

Je sais que je suis en retard à la fête, mais c'est ainsi que nous avons évité ce problème douloureusement ennuyeux Android, et recadré et modifié une image avec une seule copie en mémoire.

Situation
nous voulons traiter les pixels d'une version recadrée d'une image enregistrée dans un fichier. Avec des exigences de mémoire élevées, nous ne voulons jamais avoir plus d'une copie de cette image en mémoire à un moment donné.

Ce qui aurait dû fonctionner mais n'a pas fonctionné
Ouverture de la sous-section de l'image (le bit que nous voulions recadrer) avec BitmapRegionDecoder, en passant un BitmapFactory.option avec inMutable = true, traitement des pixels puis enregistrement dans un fichier.
Bien que notre application ait déclaré un minimum de 14 API et une cible de 19, BitmapRegionDecoder renvoyait une image bitmap immuable, ignorant effectivement notre BitMapFactory.options

Ce qui ne fonctionnera pas

  • ouvrir une image modifiable avec BitmapFactory (qui respecte notre option inMutable) et la recadrer: toutes les techniques de recadrage sont non impératives (nécessitent une copie de l'image entière pour exister en mémoire à la fois, même si les ordures sont collectées immédiatement après avec écrasement et recyclage)
  • ouvrir une image immuable avec BitmapRegionDecoder (effectivement rognée) et la convertir en une image mutable; toutes les techniques disponibles nécessitent à nouveau une copie en mémoire.

La grande solution de contournement de 2014

  • ouvrir l'image en taille réelle avec BitmapFactory en tant que bitmap mutable, et effectuer nos opérations de pixel
  • enregistrer le bitmap dans un fichier et le recycler de la mémoire (il n'est toujours pas recadré)
  • ouvrez le bitmap enregistré avec BitmapRegionDecoder, n'ouvrant que la région à recadrer (maintenant, peu nous importe que le bitmap soit immuable ou non)
  • enregistrer ce bitmap (qui a effectivement été recadré) dans un fichier, en écrasant le bitmap précédemment enregistré (qui n'a pas été recadré)

Avec cette méthode, nous pouvons recadrer et effectuer le traitement des pixels sur une image bitmap avec seulement 1 copie jamais en mémoire (afin que nous puissions éviter ces erreurs OOM embêtantes autant que possible), en échangeant RAM pour le temps comme nous devons effectuer des E/S de fichiers supplémentaires (lentes).

2
Anti Earth