6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ゆめみAdvent Calendar 2020

Day 13

Androidでもリアルタイムにブラーをかける

Last updated at Posted at 2020-12-12

最近はWebページでもブラー(ぼかし)エフェクトを使用したサイトがみられるようになりましたね(今からだと旬は過ぎた感ありますが…)。
iOSだと6年前のiOS 8から UIVisualEffectView という便利なビューが標準で用意されたので、ミニマムだとレイアウトだけで実現できてしまいます。

ios.gif

Layout風に書くとこんな感じ…

<UIView> <!-- UIViewはViewGroupのように子ビューを複数持てる -->
  <UIScrollView>
    <UIImageView>
  </UIScrollView>
  <UIVisualEffectView /> <!-- Androidと同じく下に書くと階層的には上に来て下にあるビューにブラーがかかる -->
</UIView>

最近Webでも backdrop-filter によってIEを除いた最新のブラウザで使えるようになりました:

web.gif

雑ですが iOS のサンプルを大まかに再現するとこうなります:

ちょっと長いので省略
<html>
  <head>
    <style type="text/css">
      img {
        object-fit: cover;
        width: 100%;
        height: 200vh;
      }

      .overlay {
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        display: flex;
      }

      .box {
        margin: auto;
        width: 75vw;
        height: 75vw;
        backdrop-filter: blur(24px);
        -webkit-backdrop-filter: blur(24px); /* Safari */
       background-color: #ffffff5f;
     }
    </style>
  </head>

  <body>
    <img src="...">
    <div class="overlay">
      <div class="box"></div>
    </div>
  </body>
</html>

本題

画像やビューのキャプチャに対してブラーをかける方法についてはすでに 記事
質問 がありましたが、スクロールなどリアルタイムにかける方法についてはまだ情報がまとまっていなさそうだったので、実現するライブラリについていろいろ書いていきたいと思います。

ちなみに

  • 今回のような例であれば上記ライブラリであらかじめビットマップを作っておいてスクロールに合わせて動かせば擬似的に見せることは可能でした(発想の転換)
  • 静止画に対するブラーの掛け方は RenderScript から CPU で Bitmap を直接触る方法などいろいろありますが、興味深いことに StackOverflow でパフォーマンスを測定した方がおられたので共有しておきます

RealtimeBlurView

ずばりな名前のライブラリでレイアウトに乗っけるだけで背景のビューにブラーがかかってくれるすごいライブラリです。説明に書いてある通りにするだけで確かに実現ができました:

android.gif

サンプルコードはこちらです: https://github.com/cubenoy22/android-reatime-blur

どうやって実現しているのか

ただライブラリを紹介するだけではつまらないのでちょっと中身をみていきたいと思います。

RealtimeBlurView

XMLで配置するビューの実装から見ていきます。対象ファイルは こちら

ブラー用のビットマップ作成

prepare() メソッドでメンバーの mBitmapToBlur をビューの大きさ分で作成していて、描画用の mBlurringCanvas も同時に作成しています。このメソッドは後ほど紹介する onPreDraw 内で呼ばれる ようになっていて、画面の更新がされる直前かつまだ準備できていなければ実行されるようになっています。ビューのサイズ変更もここで対応できるようになっていました。

protected boolean prepare() {
    // 省略
    int scaledWidth = Math.max(1, (int) (width / downsampleFactor));
    int scaledHeight = Math.max(1, (int) (height / downsampleFactor));

    if (mBlurringCanvas == null || mBlurredBitmap == null
        || mBlurredBitmap.getWidth() != scaledWidth
        || mBlurredBitmap.getHeight() != scaledHeight) {
        mBitmapToBlur = Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888);
    }
    mBlurringCanvas = new Canvas(mBitmapToBlur);
    mBlurredBitmap = Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888);
}

ブラーが必要なエリアをビットマップに転写

Activity の Window の decorView に対して

private final ViewTreeObserver.OnPreDrawListener preDrawListener = new ViewTreeObserver.OnPreDrawListener() {
    @Override
    public boolean onPreDraw() {
        // 省略
        if (decor != null && isShown() && prepare()) {
            if (decor.getBackground() != null) {
                decor.getBackground().draw(mBlurringCanvas);
            }
            decor.draw(mBlurringCanvas);
            blur(mBitmapToBlur, mBlurredBitmap);
        }
    }
}

これであらかじめ作っておいた Bitmap (mBitmapToBlur) に対してビューの内容が転写されました。面白い。

転写した Bitmap に対してブラーをかける

直前の preDrawListener での一連の流れでブラーもかけています。 mBitmapToBlur の他にもう一つ mBlurredBitmap という Bitmap が用意されています。このビットマップは実は mBitmapToBlur同時に同じサイズで用意 されていました。上のコードにも書いておきましたが blur() 関数ですね。

protected void blur(Bitmap bitmapToBlur, Bitmap blurredBitmap) {
    mBlurImpl.blur(bitmapToBlur, blurredBitmap);
}

mBlurImpl ( BlurImpl ) は getBlurImpl メソッドで状況に応じた実装が使用されるようになっており、違いは RenderScript のパッケージが各種用意されているだけのようでした:

ScriptIntrinsicBlur を使って mBitmapToBlur の内容にブラーを mBlurrerdBitmap に出力させていることがわかります。

@Override
public void blur(Bitmap input, Bitmap output) {
    mBlurInput.copyFrom(input);
    mBlurScript.setInput(mBlurInput);
    mBlurScript.forEach(mBlurOutput);
    mBlurOutput.copyTo(output);
}

これで表示するための準備ができあがりました。

onDraw で mBlurredBitmap を描画する

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    drawBlurredBitmap(canvas, mBlurredBitmap, mOverlayColor);
}

ブラーをかけた Bitmap を表示すれば完了となるわけですね!

BlurKit

こちらの方がスター数が多いですが

という理由から個人的に RealtimeBlurView をオススメしたいです。ちなみにブラーをかけるところは ほぼ同じ でビットマップをひとつだけしか使用しないか二つ使用するかが唯一異なる点でした(RealtimeBlurViewのビットマップが二つ必要な理由についてはまだ調査していません)。

(ちょこっと追記) ほぼ同じって書きましたが RenderScript のコードも改めてみてみると毎回初期化していてここも効率化できそう。。

BlurView

こちらはレイアウト以外にコードを書く必要があるみたいです。コードは読みやすい気がします。

全体的に RealtimeBlurView と同じような実装で設計と RenderScript のビットマップを2つ使用するかどうかの違いしかなさそうでした。気軽に使用できる点からやはり RealtimeBlurView をおすすめしたいです。RealtimeBlurViewのビットマップが二重になっているところが改善されたら今のところ最強でしょうかね(プルリク出してみようかしら…)。

終わりに

Androidのライブラリ3つを紹介しましたがいずれもビットマップを用意したりしています。おそらくiOSやWebではGPUのレンダリングパイプラインあたりだけで実現している気がしていて、Androidでもそういった高効率のアプローチができないか調査する余地がありそう、という感想を持ちました。できればGoogle純正のイケてるライブラリが出てくれると嬉しかったり。

6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?