BitmapFactory.decodeResource returns a mutable Bitmap in Android 2.2 and an immutable Bitmap in Android 1.6
Asked Answered
S

7

43

I am developing an application and testing it on my device running Android 2.2. In my code, I make use of a Bitmap that I retrieve using BitmapFactory.decodeResource, and I am able to make changes by calling bitmap.setPixels() on it. When I test this on a friend's device running Android 1.6, I get an IllegalStateException in the call to bitmap.setPixels. Documentation online says an IllegalStateException is thrown from this method when the bitmap is immutable. The documentation doesn't say anything about decodeResource returning an immutable bitmap, but clearly that must be the case.

Is there a different call I can make to get a mutable bitmap reliably from an application resource without needing a second Bitmap object (I could create a mutable one the same size and draw into a Canvas wrapping it, but that would require two bitmaps of equal size using up twice as much memory as I had intended)?

Settee answered 3/12, 2010 at 19:19 Comment(0)
B
71

You can convert your immutable bitmap to a mutable bitmap.

I found an acceptable solution that uses only the memory of one bitmap.

A source bitmap is raw saved (RandomAccessFile) on disk (no ram memory), then source bitmap is released, (now, there's no bitmap at memory), and after that, the file info is loaded to another bitmap. This way is possible to make a bitmap copy having just one bitmap stored in ram memory per time.

See the full solution and implementation here: Android: convert Immutable Bitmap into Mutable

I add a improvement to this solution, that now works with any type of Bitmaps (ARGB_8888, RGB_565, etc), and deletes the temp file. See my method:

/**
 * 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;
}
Burden answered 8/2, 2012 at 13:29 Comment(8)
Awesome. Thanks for sharing the full implementation for future googlers. I've just been doing this in memory since I asked this, but yeah...always a potential OutOfMemoryExceptionSettee
why use the external storage and require the developer to use a new permission? you should use the internal storage instead .Fray
@androiddeveloper You are right, it probably will also work if you save at the internal storage.Burden
@Burden i've created a new post here that shows how to do it : https://mcmap.net/q/176626/-bitmapfactory-decoderesource-returns-a-mutable-bitmap-in-android-2-2-and-an-immutable-bitmap-in-android-1-6 , plus it supports some newer features of android.Fray
@Burden i've now updated my answer with one that doesn't use internal storage and is faster. it uses JNI instead and works for API 8 and above .Fray
In android 4.4+ the call to imgIN.recycle produces a java.lang.RuntimeException: Canvas: trying to use a recycled bitmapLayby
FYI writing to external storage on Android 6.0+ will require an explicit permission request, even if you declare WRITE_EXTERNAL_STORAGE in the manifest. To get around this, I create a file like so instead: File file = File.createTempFile("temp.tmp", null, mContext.getCacheDir()); This works the same, and doesn't require any permissions.Ember
got java.lang.IllegalStateException: unable to copyPixelsToBuffer, pixel access is not supported on Config#HARDWARE bitmapsOutrun
A
58

Copy the bitmap to itself with mutable option true. This way neither extra memory consumption nor long lines of code are needed.

Bitmap bitmap= BitmapFactory.decodeResource(....);
bitmap= bitmap.copy(Bitmap.Config.ARGB_8888, true);
Arruda answered 3/12, 2010 at 20:38 Comment(4)
yeah, but way too much memory consumption on large images. I think I'm going to have to go multi-step with this - get just the dimensions with BitmapFactory.Options.inJustDecodeBounds, create a mutable empty bitmap, and fill it from an inputstream of raw data. The thing that gets me is that the documentation specifices those methods that return an immutable bitmap, and this method doesn't specify (implies default to mutable). And it works that way on my 2.2 phone, but not on my buddy's 1.6 phoneSettee
do u have some example of it. i knw its very old post. but still hoping for response.Carport
I had problems with copying bitmaps this way - new Bitmap's width and height are -1, and thus could not be rendered.Melchor
Bitmap is not rendering.Ciri
P
37

We can first set options for BitmapFactory by instantiating an BitmapFactory.Options class and then set the options field named 'inMutable' as true and and then pass this options instance to decodeResource.

 BitmapFactory.Options opt = new BitmapFactory.Options();
 opt.inMutable = true;
 Bitmap bp = BitmapFactory.decodeResource(getResources(), R.raw.white, opt);
Picro answered 8/10, 2011 at 16:23 Comment(3)
+1 for that, BUT that's only available in api level 11 and up. My question was how to be backwards compatible to 1.6 (api 4).Settee
Didn't know that it was not available for api 4. I hate to say this, but, you will have to adjust with the 'copy' method.Picro
Yep. I have if(!bitmap.isMutable()) all over my code now. lolSettee
F
7

Here's a solution i've created that uses the internal storage and doesn't require any new permission, based on "Derzu"'s idea, and the fact that starting with honeycomb, this is built in :

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

another alternative is to use JNI in order to put the data into it, recycle the original bitmap, and use the JNI data to create a new bitmap, which will be (automatically) mutable, so together with my JNI solution for bitmaps, one can do the following:

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

however, i'm not sure what is the minimal requirement of API level. it works very well on API 8 and above.

Fray answered 1/5, 2013 at 8:23 Comment(0)
E
2

I know I'm late to the party, but this is how we avoided this painfully annoying Android problem, and cropped and modified an image with only one copy ever in memory.

Situation
we want to process the pixels of a cropped version of an image saved to file. With high memory demands, we never want to have more than one copy of this image in memory at any given time.

What should have worked but didn't
Opening the image subsection (the bit we wanted to crop to) with BitmapRegionDecoder, passing in a BitmapFactory.option with inMutable = true, processing the pixels then saving to file.
Though our app declared an API minimum of 14 and a target of 19, BitmapRegionDecoder was returning an immutable bitmap, effectively ignoring our BitMapFactory.options

What won't work

  • opening an mutable image with BitmapFactory (which respects our inMutable option) and croppping it: all cropping techniques are non-imperitive (require a copy of the entire image to exist in memory at a time, even if garbage collected immediately after with overwrites and recycling)
  • opening an immutable image with BitmapRegionDecoder (effectively cropped) and converting it to a mutable one; all available techniques again require a copy in memory.

The great work-around of 2014

  • open the full size image with BitmapFactory as a mutable bitmap, and perform our pixel operations
  • save the bitmap to file and recycle it from memory (it's still un-cropped)
  • open the saved bitmap with BitmapRegionDecoder, opening only the region to be cropped to (now we don't care if the bitmap is immutable or not)
  • save this bitmap (which has effectively been cropped) to file, overwriting the previously saved bitmap (which was un-cropped)

With this method, we can crop and perform pixel processing on a bitmap with only 1 copy ever in memory (so we can avoid those pesky OOM errors as much as possible), trading RAM for time as we have to perform extra (slow) file IOs.

Especial answered 10/5, 2014 at 15:29 Comment(0)
L
1

It is happening because you want to resize the bitmap by calling setHeight() or setWidth()

To resize any bitmap or drawable (Png, Svg, vector, etc)

public Bitmap editMyBitmap(int drawableId, int newHeight, int newWidth) {
        Bitmap myBitmap = BitmapFactory.decodeResource(getResources(), drawableId);
        myBitmap = Bitmap.createScaledBitmap(myBitmap, newWidth, newHeight, false);
        return myBitmap;
    }

Usage Example:

Bitmap facebookIcon = editMyBitmap(R.drawable.facebookImage);
// now use it anywhere
imageView.setBitmapImage(facebookIcon); 
canvas.drawBitmap(facebookIcon, 0, 0, null); 
Leventis answered 21/5, 2021 at 10:39 Comment(0)
R
-1

I know the question is solved but what about:

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

Rosalie answered 27/8, 2013 at 9:43 Comment(1)
why would you need to get the identifier using a string if you already know its name? also, i've tested this code now (on an emulator with android 1.6) and it didn't work, so i have no idea how it worked for you.Fray

© 2022 - 2024 — McMap. All rights reserved.