2010年12月24日金曜日

java.lang.IllegalStateException: Immutable bitmap passed to Canvas constructor

png などの画像上に drawText() で文字を書こうとして、
以下のような処理を記述すると、4行目の drawText() で、タイトルの例外メッセージが発生します。

Resources r = getResources();
Bitmap bitmap = BitmapFactory.decodeResource(r, R.drawable.icon);
Canvas canvas = new Canvas(bitmap);
canvas.drawText(...);

bitmap が immutable (変えられない) ことが原因です。
mutable か immutable かは、bitmap.isMutable() で true か false かで確認できます。

じゃあ、どうやって mutable にするかというと、
bitmap.copy(Bitmap.Config config, boolean isMutable) を使って、
mutable な Bitmap を新規に作成します。

青字部分が修正内容)
Resources r = getResources();
Bitmap bitmap = BitmapFactory.decodeResource(r, R.drawable.icon);
Bitmap copy = bitmap.copy(Bitmap.Config.ARGB_8888, true);
Canvas canvas = new Canvas(copy);
canvas.drawText(...);

こうすると、うまく処理できます。


ちなみに、、

bitmap.copy(Bitmap.Config config, boolean isMutable) の第1引数ですが、
ALPHA_8、ARGB_4444、ARGB_8888、RGB_565 のいずれかを選択できます。

ARGB_4444 や RGB_565 を使えば、画像のバイト数を節約できそうです。
画質優先の場合には、ARGB_8888 を使う、というところでしょうか。
ALPHA_8 は、よく分かりません。。

項目 1 ピクセルあたりのバイト数 コピー内容(多分。確証はありません。)
ALPHA_8 1 バイト アルファ値のみ 8 ビット。
ARGB_4444  2 バイト  ARGB それぞれ 4 ビットずつ。
ARGB_8888 4 バイト ARGB それぞれ 8 ビットずつ。
RGB_565 2 バイト RGB が 5/6/5 ビット。


以下は、比較用に作成したサンプルです。


上から、以下の画像を表示させています。
  1. オリジナル画像
  2. ALPHA_8 (表示されていませんが。。)
  3. ARGB_4444
  4. ARGB_8888
  5. RGB_565

以下は、ソースです。

main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  >
<ImageView
  android:id="@+id/imageview1"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  />
<ImageView
  android:id="@+id/imageview2"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  />
<ImageView
  android:id="@+id/imageview3"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  />
<ImageView
  android:id="@+id/imageview4"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  />
<ImageView
  android:id="@+id/imageview5"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  />
</LinearLayout>

BitmapCopyActivity.java
package jp.sample.rtaki;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.widget.ImageView;

public class BitmapCopyActivity extends Activity {
  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    Paint paint = new Paint();
    paint.setColor(Color.BLUE);
    //paint.setAlpha(200);
    paint.setTextSize(12);

    ImageView v1= (ImageView) findViewById(R.id.imageview1);
    Bitmap image = BitmapFactory.decodeResource(getResources(), R.drawable.flag);
    v1.setImageBitmap(image);

    ImageView v2 = (ImageView) findViewById(R.id.imageview2);
    Bitmap copy1 = image.copy(Bitmap.Config.ALPHA_8, true);
    Canvas c2 = new Canvas(copy1);
    c2.drawText("ALPHA_8 - " + copy1.getRowBytes()/copy1.getWidth()*1.0, 25, 33, paint);
    v2.setImageBitmap(copy1);

    ImageView v3 = (ImageView) findViewById(R.id.imageview3);
    Bitmap copy2 = image.copy(Bitmap.Config.ARGB_4444, true);
    Canvas c3 = new Canvas(copy2);
    c3.drawText("ARGB_4444 - " + copy2.getRowBytes()/copy2.getWidth()*1.0, 25, 33, paint);
    v3.setImageBitmap(copy2);

    ImageView v4 = (ImageView) findViewById(R.id.imageview4);
    Bitmap copy3 = image.copy(Bitmap.Config.ARGB_8888, true);
    Canvas c4 = new Canvas(copy3);
    c4.drawText("ARGB_8888 - " + copy3.getRowBytes()/copy3.getWidth()*1.0, 25, 33, paint);
    v4.setImageBitmap(copy3);

    ImageView v5 = (ImageView) findViewById(R.id.imageview5);
    Bitmap copy4 = image.copy(Bitmap.Config.RGB_565, true);
    Canvas c5 = new Canvas(copy4);
    c5.drawText("RGB_565 - " + copy4.getRowBytes()/copy4.getWidth()*1.0, 25, 33, paint);
    v5.setImageBitmap(copy4);

  }

}