2012年5月2日水曜日

AndroidでBitmapにグロー的な効果をつけるには?

AndroidでBitmapにこのようなグロー的な効果をつけるには?


ビットマップをBlurMaskFilterを使って描画してもボケない

ドキュメントを見るとBlurMaskFilterというのがあるので、これを使うのだろうなとは思った。

CanvasにBitmapを描画する時にBlurMaskFilterをセットしたPaintを使うようだ。素直に書いてみるなら、こんな感じになるだろう。

Paint paint = new Paint();
paint.setColor(0xFFFF0000);
paint.setMaskFilter(new BlurMaskFilter(15, Blur.NORMAL));
canvas.drawBitmap(bitmap, 0, 0, paint);

しかし、これがまったくボケない_| ̄|○

こちらのページを見ると、少なくともベクターグラフィックスを描画する場合には思ったとおりにボケるようなのだけど・・・

About Android: 2D graphics with Effects

どうやらビットマップの場合は違うらしい。


extractAlpha()がキモ

「BlurMaskFilter」という名前は「BlurFilter」ではなく「Mask」がついていることを考えると、何か特殊な使い方が必要なのかもしれないと思ってさらに調べると、こちらが参考になった。

paint - How to prevent Android's drawBitmap from only drawing black images? - Stack Overflow

オリジナルのビットマップからextractAlpha()を使ってアルファチャンネルのビットマップを作るところがポイントらしい。


アプリ動作ビデオとEclipseプロジェクトファイル



EclipseプロジェクトファイルGlowEffect.zip(157KB)


コード

res/layout/main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <jp.example.GlowEffectView
        android:id="@+id/glowEffectView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

GlowEffectView.java

package jp.example;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BlurMaskFilter;
import android.graphics.BlurMaskFilter.Blur;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;


public class GlowEffectView extends View {


  protected Bitmap bmpOnScreen;
  protected Bitmap bmpUp;
  protected Bitmap bmpDown;


  public GlowEffectView(Context context, AttributeSet attrs) {
    super(context, attrs);
    setClickable(true); // これをしないとタッチイベントがDOWNのみになる
    int radius = 15;
    int[] offsetXY = { 0, 0 };
    Paint paint = new Paint();
    paint.setColor(0xFF873955); // グローの色になる
    paint.setMaskFilter(new BlurMaskFilter(radius, Blur.NORMAL));
    bmpUp = BitmapFactory.decodeResource(getResources(), R.drawable.image);
    Bitmap bmpGlow = bmpUp.extractAlpha(paint, offsetXY); // paintを指定する
    bmpDown = Bitmap.createBitmap(bmpUp.getWidth(), bmpUp.getHeight(), Bitmap.Config.ARGB_8888);
    Canvas cv = new Canvas(bmpDown); // DOWNグラフィックスを描画するキャンバス
    cv.drawBitmap(bmpGlow, offsetXY[0], offsetXY[1], paint); // ここでもpaintは必要
    cv.drawBitmap(bmpGlow, offsetXY[0], offsetXY[1], paint); // 1回だけだと薄いので数回描画しておく
    cv.drawBitmap(bmpGlow, offsetXY[0], offsetXY[1], paint); // 1回だけだと薄いので数回描画しておく
    cv.drawBitmap(bmpGlow, offsetXY[0], offsetXY[1], paint); // 1回だけだと薄いので数回描画しておく
    cv.drawBitmap(bmpGlow, offsetXY[0], offsetXY[1], paint); // 1回だけだと薄いので数回描画しておく
    cv.drawBitmap(bmpGlow, offsetXY[0], offsetXY[1], paint); // 1回だけだと薄いので数回描画しておく
    cv.drawBitmap(bmpUp, 0, 0, null); // Glowエフェクトグラフィックスの上にUPグラフィックスを重ねる
    bmpOnScreen = bmpUp; // はじめに描画するのはUPグラフィックス
  }


  @Override
  public boolean onTouchEvent(MotionEvent event) {

    // タッチイベントによってステートを変える
    switch (event.getAction()) {
      case MotionEvent.ACTION_UP:
        bmpOnScreen = bmpUp;
        break;
      case MotionEvent.ACTION_DOWN:
        bmpOnScreen = bmpDown;
        break;
    }

    // 再描画リクエスト
    invalidate();

    return super.onTouchEvent(event);

  }


  @Override
  protected void onDraw(Canvas canvas) {
    int mode = 0;
    switch (mode) {

      // 正解
      case 0:
        canvas.drawBitmap(bmpOnScreen, 0, 0, null);
        break;

      // ダメな例1:まったくボケない
      case 1:
        Paint paint1 = new Paint();
        paint1.setColor(0xFF873955);
        paint1.setMaskFilter(new BlurMaskFilter(15, Blur.NORMAL));
        canvas.drawBitmap(bmpUp, 0, 0, paint1);
        break;

      // ダメな例2:ボケた影がつくかと思ったらつかないし、影の色も変わらない
      case 2:
        Paint paint2 = new Paint();
        paint2.setShadowLayer(15, 4, 4, 0xFFFF0000);
        canvas.drawBitmap(bmpUp, 0, 0, paint2);
        break;

    }
    super.onDraw(canvas);
  }


}


コードのポイント

ポイント1:extractAlpha()する時にBlurMaskFilterをセットしたPaintが必要なところ

ポイント2:UPグラフィックスをぼかしたグラフィックス「bmpGlow」は薄いので、数回描画して濃くするところ


失敗例

onDraw()内に、試してみたもののダメだった例も残しておいた。

ダメな例1はUPグラフィックスの状態から全く変わらず。


ダメな例2(setShadowLayer()を使う方)はこのようになってしまった。


画像の「中」をぼかしたい場合は?

今回は何とか画像の「シェイプ」をぼかすことはできた。

その試行錯誤の中でBlurMaskFilterは普通のBlurFilterではないということがわかり、また新たな疑問が出てくる。

例えば、画像の「中」をぼかしたい場合はどうするのか?どうやらこうするらしい。

untitled: androidで簡単な画像処理をする

つまり、自分でピクセル操作をする!何ていうか、Old Schoolだなぁ・・・BlurFilterくらいは標準ライブラリで欲しいよ〜

Flashならスプライト自体にフィルターをかけられるけど、それもできなさそうだし。AndroidならView.setFilter()的な。Flashはこのあたり、すごく楽だったんだなと実感。

しかし、Androidでも最近のAPIではPixelBenderみたいなことをするAPIがあるかもしれないので、さらに探してみる必要はある。