Androidのダイナミックブラー

Androidで画像をすばやくぼかす方法については、多くの情報があります。

しかし、iOSに実装されているように、コンテンツを変更することで、ラグなしでぼやけたビットマップを再描画するほど効果的にこれを行うことは可能ですか?



だから私がやりたいこと:

  1. ViewGroup。その下にあるコンテンツをぼかし、背景として表示できます。 BlurViewと呼びましょう
  2. 子を追加する機能(任意のビュー)
  3. 親ViewGroupの特定の実装に依存しない
  4. ビューのコンテンツが変更された場合、ぼやけた背景を再描画します
  5. 16ms未満で完全なぼかしとレンダリングサイクルを実現
  6. ブラー戦略を変更する機能を持っている


この記事では、重要な点にのみ注意を払おうとしていますが、 ここで詳細を尋ねます



例付きのgif(8MB)




BlurViewのコンテンツでビットマップを取得する方法は?



キャンバスとそのビットマップを作成し、ビュー階層全体にこのキャンバスに描画するよう指示します。



internalBitmap = Bitmap.createBitmap(viewWidth, viewHeight, Bitmap.Config.ARGB_8888); internalCanvas = new Canvas(internalBitmap); ... rootView.draw(internalCanvas);
      
      





これで、internalBitmapには、画面に表示されるすべてのビューが描画されます。



問題は次のとおりです。

rootViewに特定の背景がない場合は、最初にキャンバスにアクティビティの背景を描画する必要があります。それ以外の場合は、ビットマップ上で透明になります。

 final View decorView = getWindow().getDecorView(); //Activity's root View. Can also be root View of your layout final View rootView = decorView.findViewById(android.R.id.content); final Drawable windowBackground = decorView.getBackground(); ... windowBackground.draw(internalCanvas); rootView.draw(internalCanvas);
      
      





必要な階層の一部のみを描きたいと思います。 つまり、適切なサイズのBlurViewの下です。

さらに、レンダリングが行われるビットマップを減らすと便利です。 これにより、ビュー階層のレンダリングが高速化され、ブラー自体がぼやけ、メモリ消費も削減されます。

scaleFactorフィールドを追加します。この係数は2のべき乗であることが望ましいです。



 float scaleFactor = 8;
      
      





ビットマップを8倍に縮小し、キャンバスマトリックスを調整して、位置、サイズ、スケールファクターを考慮して正しく描画されるようにします。



 private void setupInternalCanvasMatrix() { float scaledLeftPosition = -blurView.getLeft() / scaleFactor; float scaledTopPosition = -blurView.getTop() / scaleFactor; float scaledTranslationX = blurView.getTranslationX() / scaleFactor; float scaledTranslationY = blurView.getTranslationY() / scaleFactor; internalCanvas.translate(scaledLeftPosition - scaledTranslationX, scaledTopPosition - scaledTranslationY); float scaleX = blurView.getScaleX() / scaleFactor; float scaleY = blurView.getScaleY() / scaleFactor; internalCanvas.scale(scaleX, scaleY); }
      
      





これで、rootView.draw(internalCanvas)を呼び出すと、internalBitmapのビュー階層全体のコピーが縮小されます。 怖そうに見えますが、本当に気にしません。 さらに、私はまだそれを侵食します。



問題は次のとおりです。

rootViewは、すべての子に、BlurViewを含むキャンバスに描画するように指示します。 これを避けたいのですが、BlurViewはそれ自体を侵食するべきではありません。 これを行うには、BlurViewで描画(キャンバスキャンバス)メソッドをオーバーライドし、どのキャンバスに描画を要求するかを確認できます。

システムキャンバスの場合-レンダリングを有効にし、内部キャンバスの場合-無効にします。



実際に、ぼかし



BlurAlgorithmインターフェイスと、アルゴリズムをカスタマイズする機能を作成しました。

StackBlur(マリオクリンゲマンによる)とRenderScriptBlurの2つの実装を追加しました。 RenderScriptバリアントはGPUで実行されます。

メモリを節約するために、入力に来たのと同じビットマップに結果を書き込みます。 これはオプションです。

入力画像のサイズが変更されていない場合、outAllocationをキャッシュして再利用することもできます。



RenderScriptBlur
 @Override public final Bitmap blur(Bitmap bitmap, float blurRadius) { Allocation inAllocation = Allocation.createFromBitmap(renderScript, bitmap); Bitmap outputBitmap; if (canModifyBitmap) { outputBitmap = bitmap; } else { outputBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig()); } //do not use inAllocation in forEach. it will cause visual artifacts on blurred Bitmap Allocation outAllocation = Allocation.createTyped(renderScript, inAllocation.getType()); blurScript.setRadius(blurRadius); blurScript.setInput(inAllocation); blurScript.forEach(outAllocation); outAllocation.copyTo(outputBitmap); inAllocation.destroy(); outAllocation.destroy(); return outputBitmap; }
      
      







ブラーを再描画するタイミング



いくつかのオプションがあります:

  1. コンテンツが変更されたことを確認したときに手動で更新する
  2. BlurViewがスクロール可能なコンテナの上にある場合、スクロールイベントに従事します
  3. ViewTreeObserverを介してdraw()呼び出しを聞く


OnPreDrawListenerを使用してオプション3を選択しました。 彼のonPreDrawメソッドは、階層内の任意のビューがそれ自体を描画するたびにひっくり返ることに注目する価値があります。 もちろん、これは理想的ではありません。なぜなら、 くしゃみごとにBlurViewを再描画する必要がありますが、プログラマー側での制御は必要ありません。



問題は次のとおりです。

onPreDrawは、階層全体をinternalCanvasにレンダリングするとき、およびBlurViewをレンダリングするときにも呼び出されます。

この問題を回避するために、isMeDrawingNowフラグを設定します。



一瞬を除いて、すべてが明らかなようです。 ハンドラーを介してfalseに設定する必要があります。 レンダリングディスパッチは、ストリームUIイベントキューを通じて非同期的に発生します。 したがって、レンダリングの最後に正確に実行されるように、このタスクをイベントキューに追加する必要もあります。



 drawListener = new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { if (!isMeDrawingNow) { updateBlur(); } return true; } }; rootView.getViewTreeObserver().addOnPreDrawListener(drawListener);
      
      





性能



あまり多くの統計情報を収集しませんでしたが、Nexus 4、Nexus 5では、デモプロジェクトの画面でレンダリングサイクル全体に1〜4ミリ秒かかります。

Galaxy Nexus-約10ミリ秒。

ソニーXperia Z3コンパクトは、何らかの理由で悪化します-8-16ms。

それにもかかわらず、FPSは良好なレベルに保たれ、パフォーマンスに満足しています。



おわりに



すべてのコードはgithub(ライブラリおよびデモプロジェクト) -BlurViewにあります。

アイデア、コメント、バグレポート、プルリクエストを歓迎します。



All Articles