📜 ⬆️ ⬇️

We write effective blur on Android

image
Today we will try to deal with the methods of blur (blur) available for Android developers. After reading a certain number of articles and posts on StackOverflow, we can say that there are quite a lot of opinions and ways to accomplish this task. I will try to put it all together.

And so, why?


Increasingly, you can notice the effect of blur in applications appearing in the open spaces of the Google Play Store. Get at least the wonderful Muzei app from + RomanNurik or the same Yahoo Weather. Looking at these applications, you can see that with good blurring you can achieve very impressive results.


')
A series of articles called Blurring Images pushed me to write this article, so the first part of the article will be very similar. In fact, I will try to dig a little deeper.

Here is approximately what we will try to achieve:

image

Let's get started



First I want to show what we work with. I created 1 activity, inside which the ViewPager is located. ViewPager through fragments. Each fragment is a separate implementation of the blur.
Here is what my main_layout.xml looks like:

 <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" /> 


And this is what the layout of the fragment looks like (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> 


As you can see, nothing military - the usual picture in full screen with the text in the middle. You may also notice an additional LinearLayout - I will use it to display any service information.

Our goal is to blur the background of the text, thereby underlining it. Here is the general principle of how we will do it:


Renderscript


Probably the most popular answer today to the question “how to quickly blur a picture in Android” is Renderscript. This is a very powerful tool for working with images. Despite its apparent complexity, many of its parts are very easy to use. Fortunately, blur is one of those parts.

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


Let's see what's going on here:


At once I want to note that this code has a number of shortcomings, which you should definitely remember:


Let's return to blur() :


Here's what happened:
image

As you can see, the result is pretty good looking and took us 57ms . Given that one frame should not take more than 16ms (~ 60fps), we can calculate that the frame rate in our case will fall to 17fps for the period while the blur is being performed. I would say unacceptable. It is therefore necessary to download the blur to a separate stream.

I also want to note that the ScriptIntrinsicBlur is available at API> 16. Of course, you can use the renderscript support library, which will reduce the required API level.
But, as it is not difficult to guess, the renderscript didn’t converge on the light, therefore let's consider one of the alternatives.

Fastblur



In fact, blurring is nothing more than the manipulation of pixels, so what prevents us from manipulating with these very pixels themselves? Fortunately, a fairly large number of implementations of various blurring algorithms are available on the Internet, so our task is to choose the most optimal one.
On the all-knowing StackOverflow ( or rather here ), I came across a nice implementation of the blur algorithm.

Let's see what came of it. I will not give all the code, because only the blur () function will be different:

 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"); } 


And here is the result:
image

As you can see, the quality is difficult to notice the difference with renderscript. But the performance leaves much to be desired - 147ms ! And this is not the slowest algorithm! I'm afraid to even try to blur Gauss.

We optimize



Let's think for a second what the blur is like. At its core, blurring is very closely related to the “loss” of pixels (here I would like to ask experts in mathematics and graphics not to swear because I describe more based on my understanding of the problem than on specific facts :)). What else can easily help us “lose” pixels? Reducing the picture!

What if we reduce the image first, blur it, and then zoom it back?

Let's try!

image

And so, we have 13ms renderscript and 2ms FastBlur. Pretty good, considering that the quality of the blur remains comparable in quality with previous results.

Let's take a look at the code. I will only describe the FastBlur version, since the code for Renderscript will be similar (a link to the full version of the code is available at the end of the article):

 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"); } 




Interestingly enough, the Renderscript worked slower than the FastBlur. This happened due to the fact that we saved time on copying Bitmap to Allocation and back.

As a result, we got a pretty smart method of blurring images on Android.

Useful links:
Sources to this article on GitHub
The progenitor of the article
Post on SO about blur algorithms

Source: https://habr.com/ru/post/215077/


All Articles