Android で Bitmap を安全に操作する(3) ~画像の編集・コピー~

前回は、画像の縮小や回転を行ってみた。今回は、画像を安全に編集したりコピーしてみる。画像の編集は Canvas クラスを、コピーは Bitmap.copy(Bitmap.Config, boolean)を使えばできるのだが、いろいろとはまったのでメモを残しておく。

Immutable bitmap passed to Canvas constructor

画像を編集する場合、ファイルを読み込んで Bitmap を生成、Canvas のコンストラクタに Bitmap を渡して Canvas を操作するだろう。コードにすると、以下のようになる。

File file = new File(<読み込みたい画像ファイル>);
InputStream in = context.getContentResolver().openInputStream(Uri.fromFile(file));
Bitmap bitmap = BitmapFactory.decodeStream(in);
in.close();
Canvas canvas = new Canvas(bitmap);

残念ながら、このままだと IllegalStateException になる。

java.lang.IllegalStateException: Immutable bitmap passed to Canvas constructor
09-10 12:05:19.099: E/AndroidRuntime(1640): at android.graphics.Canvas.(Canvas.java:127)

immutable(不変)な Bitmap を Canvas クラスのコンストラクターの引数に渡したということなのだが、最初はさっぱり意味が分からなかった。調べてみると、BitmapFactory で読み込んで生成した Bitmap は immutable とのこと。java.lang.String と同じということだろうか。コピーすれば mutable な、つまり変更可能で Canvas の引数として渡せる Bitmap になるようだが、メモリーを圧迫してしまう。Android 3.0以降では、オプションを渡せば mutable な Bitmap を戻してくれるようだ。

解決策を探す前に、BitmapFactory が immutable な Bitmap を戻す理由を考えてみた。以前も参考にした、Managing Bitmap Memoryには、2.x系は Bitmap のピクセルデータを native heap に、3.0以降は Dalvik heap に格納すると書いてある。Android 3.0以降で mutable な Bitmap を戻してくれるオプションがあることと、関係しているのだろうか。
2.x系で immutable な Bitmap を戻すのは、普通に考えるとスレッドセーフにするためだろうか。不変オブジェクトであれば複数のスレッドから、同時にアクセスすることができるので、同じデータを利用するときは参照を戻すことができる。ソースコードを追いかけてみたが、C++ のコードと Java のコードを読まないのといけない。コードを行き来するのがあまりに面倒で挫折した。

Bitmap をコピーする

Bitmap を画像として編集するには、Canvas に mutable な Bitmap を渡せばよいので、Bitmap を安全にコピーできれば問題は解決する。実はAndroid: convert Immutable Bitmap into Mutableという記事に解答がある。簡単にまとめると、以下のようになる。

  1. Bitmap.copy(Bitmap.Config, boolean)の2番目の引数に、true を渡して Bitmap をコピーする。メモリーは圧迫するが、どんな環境でも動く
  2. Bitmap.decodeFile(String, BitmapFactory.Options)の2番目の引数の inMutable を true にして読み込む。Android 3.0以降のみ可能
  3. Bitmap をファイルに書き出して読み込み直す。どんな環境でも動くが処理速度が遅い

・・・帯に短したすきに長しだ。最近、Android 2.x 系に対応しなければ、もっと楽に作れるんじゃないだろうかと考えている。なお、3番目の処理は、少しコードを書かなければならない。Android: convert Immutable Bitmap into Mutableのコードは同じファイルに書き出していたので、一時ファイルに書き出すように変更してみたのが以下のコードだ。

public static Bitmap copy(Context context, Bitmap bitmap)
		throws IOException {
	File file = File.createTempFile("_ub", ".tmp", context.getCacheDir());
	RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");

	int width = bitmap.getWidth();
	int height = bitmap.getHeight();
	FileChannel channel = randomAccessFile.getChannel();
	MappedByteBuffer map = channel.map(MapMode.READ_WRITE, 0, width
			* height * 4);
	bitmap.copyPixelsToBuffer(map);
	bitmap.recycle();

	Bitmap mutable = Bitmap.createBitmap(width, height,
			Bitmap.Config.ARGB_8888);
	map.position(0);
	mutable.copyPixelsFromBuffer(map);
	channel.close();
	randomAccessFile.close();

	file.delete();

	return mutable;
}

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

Time limit is exhausted. Please reload CAPTCHA.