AndroidのImmutableなBitmapをMutableなやつに変換する方法3つ!

2012.05.29

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Immutableって‥

AndroidでBitmapにグラデーション等のフィルタを適用しようと思ったところ、ImmutableなBitmapを変換する箇所でメモリの問題にぶち当たったので調べてみました。

まずは例外を。BitmapFactory でオプションを与えずにデコードした場合、生成されるBitmapはImmutable(不変)になっています。
これを…

Canvas canvas = new Canvas(bitmap);
canvas.drawRect(...);

なんかした場合、java.lang.IllegalStateException: Immutable bitmap passed to Canvas constructor と言われChaiMaxx
ImmutableなBitmapがCanvasのコンストラクタ引数に設定されている(Canvasに設定=編集するのにImmutable)のが原因です。
これを解決するには何通りかの方法があります。

1.Bitmapの生成時にオプションを与えてmutableなオブジェクトを生成する。

		BitmapFactory.Options options = new  BitmapFactory.Options();
    options.inMutable = true;
    Bitmap mutableBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.z9, options);

 ○:簡単でメモリ的にもやさしい。
 ×:API Level 11(3.0) 以上しか使えない。

2.Bitmapをコピーしてmutableなオブジェクトを生成する。

    Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.z9);
    Bitmap mutableBitmap = bitmap.copy(Config.ARGB_8888, true);
		bitmap.recycle();

 ○:簡単。
 ×:同じBitmapを一時的に2枚持つためメモリをBitmap2枚分消費する。

3.一旦Bitmapをファイルに書き込んでからMutableなオブジェクトを生成する。

	private Bitmap getBitmap(Bitmap bitmap) {
		Bitmap mutable = null;
		try {
  		// 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.
			mutable = Bitmap.createBitmap(width, height, Config.ARGB_8888);
			map.position(0);
			// load it back from temporary
			mutable.copyPixelsFromBuffer(map);
			// close the temporary file and channel , then delete that also
			channel.close();
			randomAccessFile.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return mutable;

	}

 ○:メモリにやさしい。API Levelを問わない。
 ×:ローカルストレージの書き込み権限が必要。若干遅い。(1,2と比較して2〜3倍遅かった)

まとめ

タブレット向けアプリ以外なら現実的には今(2012年5月)でマルチ端末対応を視野に入れると2.x系も間違いなくターゲットに入ってくるので1は使えない、(3系以上がターゲットならベストチョイス)。 メモリに余裕があるなら2がいいかも。画像がメインのアプリではOutOfMemory祭りになるので現実には3がベストチョイスになることが多いのかな。
というか、3のようにBitmapを一時的にストレージにファイルとして保存する手法があったんですね。
AndroidでBitmapを扱うの場合は特に使用メモリ量、パフォーマンス、画質、のバランスを何処に置くかがキモですね。

でわでわ〜

元ネタ
Android: convert Immutable Bitmap into Mutable