今日は、Android開発者が利用できるぼかしメソッドを理解しようとします。 StackOverflowで一定数の記事と投稿を読んだ後、このタスクを達成するための多くの意見と方法があると言えます。 私はそれをすべて積み重ねようとします。
それで、なぜですか?
Google Playストアに表示されるアプリケーションのぼかし効果にますます気付くことがあります。 + RomanNurikまたは同じYahoo Weatherから少なくとも素晴らしいMuzeiアプリケーションを入手してください 。 これらのアプリケーションを見ると、ぼかしを巧みに処理することで、非常に印象的な結果を達成できることがわかります。
Blurring Imagesシリーズの記事はこの記事を書くように促したので、記事の最初の部分は非常によく似ています。 実際、もう少し掘り下げてみます。
達成しようとするものは次のとおりです。
さあ始めましょう
まず、私たちが取り組んでいるものを示したいと思います。
ViewPager
が配置されているアクティビティを1つ作成しました。
ViewPager
はフラグメントを
ViewPager
ます。 各フラグメントは、ぼかしの個別の実装です。
これは私の
main_layout.xml
ようです:
<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/pager" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.paveldudka.MainActivity" />
そして、フラグメントレイアウト(fragment_layout.xml)は次のようになります。
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/picture" android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/picture" android:scaleType="centerCrop" /> <TextView android:id="@+id/text" android:gravity="center_horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="My super text" android:textColor="@android:color/white" android:layout_gravity="center_vertical" android:textStyle="bold" android:textSize="48sp" /> <LinearLayout android:id="@+id/controls" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#7f000000" android:orientation="vertical" android:layout_gravity="bottom"/> </FrameLayout>
ご覧のとおり、軍事的なものは何もありません。テキストが中央にある通常のフルスクリーン画像です。 また、追加のLinearLayoutに気付くことができます。これを使用して、あらゆる種類のサービス情報を表示します。
私たちの目標は、テキストの背景をぼかして強調することです。 これを行う方法の一般的な原則を次に示します。
- 画像から、TextViewの真後ろにあるセクションを切り取ります
- 洗い流す
- 結果はTextViewの背景として設定されます
レンダースクリプト
「Androidで画像をすばやくぼかす方法」という質問に対する今日の最も人気のある答えは、おそらくRenderscriptです。 これは非常に強力な画像ツールです。 明らかな複雑さにもかかわらず、その部品の多くは非常に使いやすいです。 幸いなことに、ぼかしはそれらの部分の1つです。
public class RSBlurFragment extends Fragment { private ImageView image; private TextView text; private TextView statusText; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_layout, container, false); image = (ImageView) view.findViewById(R.id.picture); text = (TextView) view.findViewById(R.id.text); statusText = addStatusText((ViewGroup) view.findViewById(R.id.controls)); applyBlur(); return view; } private void applyBlur() { image.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { image.getViewTreeObserver().removeOnPreDrawListener(this); image.buildDrawingCache(); Bitmap bmp = image.getDrawingCache(); blur(bmp, text); return true; } }); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) private void blur(Bitmap bkg, View view) { long startMs = System.currentTimeMillis(); float radius = 20; Bitmap overlay = Bitmap.createBitmap((int) (view.getMeasuredWidth()), (int) (view.getMeasuredHeight()), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(overlay); canvas.translate(-view.getLeft(), -view.getTop()); canvas.drawBitmap(bkg, 0, 0, null); RenderScript rs = RenderScript.create(getActivity()); Allocation overlayAlloc = Allocation.createFromBitmap( rs, overlay); ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create( rs, overlayAlloc.getElement()); blur.setInput(overlayAlloc); blur.setRadius(radius); blur.forEach(overlayAlloc); overlayAlloc.copyTo(overlay); view.setBackground(new BitmapDrawable( getResources(), overlay)); rs.destroy(); statusText.setText(System.currentTimeMillis() - startMs + "ms"); } @Override public String toString() { return "RenderScript"; } private TextView addStatusText(ViewGroup container) { TextView result = new TextView(getActivity()); result.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); result.setTextColor(0xFFFFFFFF); container.addView(result); return result; } }
ここで何が起こるか見てみましょう:
- フラグメントを作成すると、レイアウトが作成され、TextViewが「サービスパネル」に追加され(アルゴリズムの速度を表示するために使用されます)、ぼかしが開始されます
-
applyBlur()
内で、applyBlur()
を登録しonPreDrawListener
。 これを行うのは、この関数を呼び出す時点でUI要素の準備がまだ整っていないため、ぼかしがあまりないためです。 したがって、レイアウトが測定され、描画の準備が整うまで待つ必要があります。 このコールバックは、最初のフレームが描画される直前に呼び出されます。 -
onPreDraw()
内onPreDraw()
私が通常行う最初のことは、戻り値をtrueに変更することです。 実際には、IDEはデフォルトでfalse
を生成します。つまり、最初のフレームのレンダリングはスキップされます。 この場合、最初のフレームに興味があるため、trueに設定します。 - 次に、コールバックを削除します-onPreDrawイベントに関心がなくなりました
- 次に、ImageViewからビットマップを取得する必要があります。 彼女に描画キャッシュを作成させ、それを奪います
- まあ、実際には、ぼかし。 このプロセスをより詳細に検討してください。
このコードには、覚えておくべきいくつかの欠点があることにすぐに注意します。
- このコードは、レイアウトの変更で「上書き」されません。 良い方法では、onGlobalLayoutListenerを登録し、このイベントを受信したときにアルゴリズムを再起動する必要があります
- ぼかしはメインスレッドで行われます。 アプリケーションでこれを行わないでください。UIをブロックしないように、この種の操作は別のスレッドで「アンロード」する必要があります。 AsyncTaskまたは類似の何かが仕事をします。
blur()
戻る
blur()
:
- TextViewに対応するサイズの空のビットマップが作成されます-ここで背景の一部をコピーします
- このビットマップで描画できるようにキャンバスを作成します
- TextViewが配置されている位置に座標系をシフトします
- 背景を描く
- この時点で、TextViewの真後ろにある背景を含むビットマップがあります。
- Renderscriptオブジェクトを作成する
- Renderscriptが動作する構造にビットマップをコピーします。
- ぼかしスクリプトを作成する(ScriptIntrinsicBlur)
- ぼかしパラメーター(私の場合は半径20)を設定し、スクリプトを実行します
- 結果をコピーしてビットマップに戻します。
- すばらしい、ぼやけたビットマップがあります-TextViewの背景として設定してください
起こったことは次のとおりです。
ご覧のとおり、結果は非常に見栄えが良く、 57msかかりました。 1つのフレームのレンダリングに16ms(〜60fps)を超えてはならないことを考慮すると、この場合のフレームレートは、ぼかしが実行されている間、17fpsに低下すると想定できます。 私は受け入れられないと言うでしょう。 したがって、ぼかしを別のストリームにロードする必要があります。
ScriptIntrinsicBlur
はAPI> 16で使用できることにも注意してください。もちろん、必要なレベルのAPIを削減するrenderscriptサポートライブラリを使用できます。
しかし、ご想像のとおり、レンダースクリプトは収束しなかったため、代替案の1つを見てみましょう。
Fastblur
実際、ぼかしはピクセルの操作に過ぎないので、これら同じピクセルを自分で操作できないのはなぜですか? 幸いなことに、インターネットでは、あらゆる種類のぼかしアルゴリズムのかなり多数の実装が利用可能であるため、私たちのタスクは最適なものを選択することです。
すべてを知っているStackOverflow( または、こちら )で、ぼかしアルゴリズムの適切な実装に出会いました。
その結果を見てみましょう。 blur()関数のみが異なるため、すべてのコードを提供するわけではありません。
private void blur(Bitmap bkg, View view) { long startMs = System.currentTimeMillis(); float radius = 20; Bitmap overlay = Bitmap.createBitmap((int) (view.getMeasuredWidth()), (int) (view.getMeasuredHeight()), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(overlay); canvas.translate(-view.getLeft(), -view.getTop()); canvas.drawBitmap(bkg, 0, 0, null); overlay = FastBlur.doBlur(overlay, (int)radius, true); view.setBackground(new BitmapDrawable(getResources(), overlay)); statusText.setText(System.currentTimeMillis() - startMs + "ms"); }
結果は次のとおりです。
ご覧のとおり、renderscriptとの違いに気付くのは品質が困難です。 しかし、パフォーマンスは良くありません-147ms ! そして、これは最も遅いアルゴリズムとはほど遠いです! ガウスをぼかすことさえ怖いです。
最適化
ブラーとは何かを少し考えてみましょう。 本質的に、ぼかしはピクセルの「損失」と非常に密接に関係しています(数学とグラフィックの専門家にあまり誓わないでください。特定の事実よりも問題の理解に基づいているためです)。 ピクセルを「失う」のに簡単に役立つものは他にありますか? 画像を縮小します!
最初に画像を縮小し、ぼかしてから拡大して戻すとどうなりますか?
やってみましょう!
そして、13 ミリ秒のレンダースクリプトと2ミリ秒の FastBlurがあります。 ぼかしの品質は以前の結果と同等の品質であることを考えると、かなり良いです。
コードを見てみましょう。 FastBlurオプションについてのみ説明します。 Renderscriptのコードも同様です(コードの完全版へのリンクは記事の最後にあります):
private void blur(Bitmap bkg, View view) { long startMs = System.currentTimeMillis(); float scaleFactor = 1; float radius = 20; if (downScale.isChecked()) { scaleFactor = 8; radius = 2; } Bitmap overlay = Bitmap.createBitmap((int) (view.getMeasuredWidth()/scaleFactor), (int) (view.getMeasuredHeight()/scaleFactor), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(overlay); canvas.translate(-view.getLeft()/scaleFactor, -view.getTop()/scaleFactor); canvas.scale(1 / scaleFactor, 1 / scaleFactor); Paint paint = new Paint(); paint.setFlags(Paint.FILTER_BITMAP_FLAG); canvas.drawBitmap(bkg, 0, 0, paint); overlay = FastBlur.doBlur(overlay, (int)radius, true); view.setBackground(new BitmapDrawable(getResources(), overlay)); statusText.setText(System.currentTimeMillis() - startMs + "ms"); }
-
scaleFactor
は、画像をどれだけ縮小するかを決定します。 私の場合、8倍に減らします。 さらに、画像を縮小/拡大するときにピクセルの大部分を失うことを考えると、メインアルゴリズムのぼかし半径を安全に減らすことができます。 科学的な突きによって、私は2倍に減少しました - ビットマップを作成します。 今回は少なくなります-この場合、8倍です。
- レンダリング時にフラグ
FILTER_BITMAP_FLAG
を設定しFILTER_BITMAP_FLAG
。これにより、画像のサイズを変更するときにスムージングが可能になります。 - 前と同じように、ぼかしを適用しますが、今でははるかに小さな画像に小さな半径を使用して、アルゴリズムを高速化できます
- この小さな画像をTextViewの背景として配置します-自動的に拡大されます。
興味深いことに、RenderscriptはFastBlurよりも遅く動作しました。 これは、ビットマップを割り当てにコピーしたり元に戻したりする時間を節約したために発生しました。
その結果、Androidで写真をぼかす非常に簡単な方法を得ました
便利なリンク:
GitHubのこの記事のソース
記事の祖先
ぼかしアルゴリズムのSOに投稿する