Wednesday, September 14, 2011

Android: convert Immutable Bitmap into Mutable

Android provide Bitmap class to manipulate images. We can load, draw, edit or save. But incase of editing bitmap should be mutable.

For example:
Canvas canvas = new Canvas(mBitmap);
Will throw IllegalArgumentException: image is immuable ...

An image file can be loaded in to bitmap using BitmapFactory as follows:

mBitmap = BitmapFactory.decodeFile(path);

But this will be an immutable bitmap. This will not be able to edited.

Anyway can load image using BitmapFactory.decodeFile(path,options);
Here BitmapFactory.Options options = new BitmapFactory.Options();

But from API Level 11 only options.inMutable available to load the file into a mutable bitmap.

So, if we are building application with API level less than 11, then we have to find some other alternatives.

One alternative is creating another bitmap by copying the source bitmap.
mBitmap = mBitmap.copy(ARGB_8888 ,true);

But the will throw OutOfMemoryException if the source file is big. Actually incase if we want to edit an original file, then we will face this issue. We should be able to load at-least image into memory, but most we can not allocate another copy into memory.

So, we have to save the decoded bytes into some where and clear existing bitmap, then create a new mutable bitmap and load back the  saved bytes into bitmap again. Even to copy bytes we cannot create another ByteBuffer inside the memory. In that case need to use MappedByteBuffer that will allocate bytes inside a disk file.

Following code would explain clearly:

//this is the file going to use temporally to save the bytes. 

File file = new File("/mnt/sdcard/sample/temp.txt");
file.getParentFile().mkdirs();

//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 = bitmap.getWidth();
int height = bitmap.getHeight();

//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, width*height*4);
bitmap.copyPixelsToBuffer(map);
//recycle the source bitmap, this will be no longer used.
bitmap.recycle();
//Create a new bitmap to load the bitmap again.
bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
map.position(0);
//load it back from temporary 
bitmap.copyPixelsFromBuffer(map);
//close the temporary file and channel , then delete that also
channel.close();
randomAccessFile.close();

Hope this wil help you.

I have uploaded the sample source code here:
http://dl.dropbox.com/u/7717254/DemoPaint.zip
In which application, user can load a image and draw and save back. In case of saving back to original image size.


16 comments:

deepakswami said...

Explain More Plz,
It's a Complex Problem
can Have Any Demo Code For this If Yes Please Give Reference....

Uruji said...

Thank you so much. But i don`t understand some things.
1)How to declare bitmap
2)file instance is a image file?
File file = new File("/mnt/sdcard/sample/temp.txt");(why temp.txt?, Is not img.jpg?)
3)before declaring bitmap, How I can get size of bitmap.
int width = bitmap.getWidth();
int height = bitmap.getHeight();

4)I want to set bitmap to imageview. But, I don't know where I set it.
after closing randomAccessFile or before?

Please give help me.

Derzu said...

Very nice solution, works to me perfectly. I just made a little change to work with bitmaps RGB_565 too.

Sudar Nimalan said...

Sorry for taking long time upload sample code. I have uploaded and added the sample code link.

Please check when you want more detail.

Thanks every for your comments.

redcurry said...

Thank you! This worked great!

Radu said...

Thank you! Best solution found so far!

Anonymous said...

I am trying to use this solution, but the bitmap says that the buffer is not large enough for all the pixels, despite making the size bitmap.height()*bitmap.width().

I tried multiplying that by 4, since I'm using ARGB_888, and it worked in writing a file, but the output I read back from the disk is garbled.

I'm on a Samsung Epic 4g.

Sudar Nimalan said...

If you give me some code sample then I can help you out of this.

Unknown said...

Excellent solution brother to a very common and complex problem.

p said...
This comment has been removed by the author.
p said...

Hi Sudar,
This example proved to be of great help in making the original bitmap mutable...
Currently I am trying to save a webpage in an image form in Android (requirement in project)..
I was able to take a snapshot of this website -> http://m.rediff.com/movies
but the contents are unreadable...
so I tried to change the bitmap & increased the height & width of bitmap before saving it to file... but now only half of the webpage contents are seen... (it seems I will need to save the bitmap in multiple file) do you have any idea how to save large chunk of bitmap data into a file/files.... ?
Any help will be appreciated. Thanx

Sudar Nimalan said...

I am not sure about constrains, you can use Bitmap.compress method or Bitmap.copyPixelsFrom/ToBuffer

Sudar Nimalan said...

I am not sure about constrains, you can use Bitmap.compress method or Bitmap.copyPixelsFrom/ToBuffer

Sudar Nimalan said...

I am not sure about constrains, you can use Bitmap.compress method or Bitmap.copyPixelsFrom/ToBuffer

OThadd said...
This comment has been removed by the author.
OThadd said...

I simply used the options alternative since I'm sure of my app's users minimum sdk level.
Thank you so much.