Androidで効果的なぼかしを書く

画像

今日は、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に気付くことができます。これを使用して、あらゆる種類のサービス情報を表示します。



私たちの目標は、テキストの背景をぼかして強調することです。 これを行う方法の一般的な原則を次に示します。





レンダースクリプト



「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; } }
      
      







ここで何が起こるか見てみましょう:





このコードには、覚えておくべきいくつかの欠点があることにすぐに注意します。





blur()



戻るblur()









起こったことは次のとおりです。

画像



ご覧のとおり、結果は非常に見栄えが良く、 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"); }
      
      











興味深いことに、RenderscriptはFastBlurよりも遅く動作しました。 これは、ビットマップを割り当てにコピーしたり元に戻したりする時間を節約したために発生しました。



その結果、Androidで写真をぼかす非常に簡単な方法を得ました



便利なリンク:

GitHubのこの記事のソース

記事の祖先

ぼかしアルゴリズムのSOに投稿する



All Articles