Home Development for Android Writing effective blur on Android

Writing effective blur on Android

by admin

Writing effective blur on Android
Today we will try to understand the blur methods available to Android developers.After reading a number of articles and posts on StackOverflow we can say that there are plenty of opinions and ways to accomplish this task. I’ll try to put it all together.

And so, why?

More and more often you can see the blurring effect in the applications that appear on the Google Play Store. Take for example a wonderful application Muzei from +RomanNurik or the same Yahoo Weather. Looking at these applications, you can see that you can achieve very impressive results with a skilful handling of blurring.
I was prompted to write this article by a series of articles Blurring Images , so the first part of this article will be very similar. In fact, I’m going to try to dig a little deeper.
This is roughly what we will be trying to achieve :
Writing effective blur on Android

Here we go

First I want to show you what we are working with. I created 1 activity, inside of which is located ViewPagerViewPager flips through the slices. Each fragment is a separate implementation of the blur.
This is what mine looks like. main_layout.xml :

<android.support.v4.view.ViewPagerxmlns: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 fragment layout (fragment_layout.xml) looks like:

<?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"><ImageViewandroid:id="@+id/picture"android:layout_width="match_parent"android:layout_height="match_parent"android:src="@drawable/picture"android:scaleType="centerCrop" /><TextViewandroid: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" /><LinearLayoutandroid: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 there’s nothing military about it – just a full screen image with text in the middle. You can also notice the additional LinearLayout – I’ll use it to display all sorts of service information.
Our goal is to blur the background of the text, thereby emphasizing it. Here’s the general principle of how we’ll do it :

  • Cut out the part of the picture which is directly behind TextView
  • Blur
  • Set the result as a background for TextView

Renderscript

Probably the most popular answer today to the question "how to quickly blur a picture in Android" is Renderscript. It is a very powerful image manipulation tool. Despite its apparent complexity, many parts of it 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;@Overridepublic 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() {@Overridepublic 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);ScriptIntrinsicBlurblur = 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");}@Overridepublic 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 figure out what’s going on here :

  • When I create a fragment – I create a layout, add a TextView to my "service panel" (I will use it to display the speed of the algorithm) and run the blur
  • Inside applyBlur() I register onPreDrawListener I do this because when I call this function, my UI elements are not ready yet, so there’s not much to blur. So I have to wait until my layout is measured and ready to be drawn. This colloback will be called just before the first frame is rendered
  • Inside onPreDraw() the first thing I usually do is change the return value to true. The thing is, the IDE generates false by default, which means that rendering of the first frame will be skipped. In this case I am interested in the first frame, so put true.
  • Then we remove our colbeck – we are not interested in onPreDraw events anymore
  • Now I need to pull the Bitmap out of my ImageView. I get her to create a drawing cache and take it out
  • And then there’s the blurring. Let’s take a closer look at this process

I want to point out right away that this code has a number of disadvantages that you should definitely keep in mind :

  • This code doesn’t "reblur" when the layout changes. It’s a good idea to register onGlobalLayoutListener and restart the algorithm when this event is received
  • Blurring is done in the main thread. Don’t try to do this in your applications – this kind of operation should be "offloaded" to a separate thread so as not to block the UI. AsyncTask or something similar will do the job

Back to blur() :

  • We create an empty Bitmap of the same size as our TextView – here we copy a piece of our background
  • Create a Canvas so that we can draw in this Bitmap
  • Move the coordinate system to the position where TextView is
  • Drawing a piece of background
  • At this step we have a Bitmap that contains the background piece, which is right behind the TextView
  • Create Renderscript object
  • Copy our Bitmap into the structure that the Renderscript works with
  • Create a script for blurring (ScriptIntrinsicBlur)
  • Set the blur parameters (radius 20 in my case) and run the script
  • Copy it back into our Bitmap
  • Alright, we have a blurry Bitmap – set it as a background for our TextView

This is what I got :
Writing effective blur on Android
As you can see, the result looks pretty good and took us 57ms Considering that it shouldn’t take more than 16ms (~60fps) to render one frame, we can calculate that frame rate in our case will drop to 17fps while the blur is going on. I would say unacceptable. That’s why we need to load the blur into a separate stream.
I would also like to point out that ScriptIntrinsicBlur is available in API > 16. Of course, you can use renderscript support library, which will reduce the required API level.
But as you know, we can’t rely on renderscript, so let’s use one of the alternatives.

FastBlur

In fact blurring is nothing but manipulation with pixels, so what prevents us from manipulating these very pixels ourselves? Fortunately, there are lots of various blurring algorithms available on the Internet, so our task is just to choose the best one.
On the all-knowing StackOverflow ( more precisely here ), I came across a pretty good implementation of the blurring algorithm.
Let’s see what we have here. I won’t give you the whole code, because only blur() 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 :
Writing effective blur on Android
As you can see, it’s hard to see a difference in quality with renderscript. But the performance leaves a lot to be desired. 147ms ! And this is far from the slowest algorithm! I’m afraid to even try a Gaussian blur.

Optimize

Let’s think for a second about what blur is. In its essence blur is very closely related to the "loss" of pixels (I would like to ask you not to swear too much from math and graphics experts, because I describe it more based on my understanding of the problem than on concrete facts 🙂 ). What else can help us easily "lose" pixels? Shrinking the picture!
What if we shrink the picture first, blur it, and then enlarge it back?
Let’s try it!
Writing effective blur on Android
And so we have 13ms renderscript and 2ms FastBlur. Pretty good, considering that the quality of the blur remained comparable in quality to the previous results.
Let’s take a look at the code. I will only describe the FastBlur variant, because 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");}

  • scaleFactor determines how much we will shrink the picture. In my case we will reduce it by 8 times. And given that the lion’s share of pixels we will lose when reducing/increasing the image, we can safely reduce the blurring radius of the main algorithm. So I have scientifically reduced it to 2x
  • Create a Bitmap. This time it will be smaller – in this case 8 times smaller.
  • Note that when rendering, I set the flag FILTER_BITMAP_FLAG which allows to apply smoothing when changing the size of the image
  • As before, apply blur, but now with a much smaller image and a smaller radius, which speeds up the algorithm
  • Put this small picture as a background for TextView – it will be automatically enlarged.

Quite interestingly, the Renderscript worked slower than FastBlur. This is because we saved time copying Bitmap to Allocation and back.
As a result, we got a pretty fast method of blurring pictures on Android
Useful Links :
The sources for this stat on GitHub
Article progenitor
A post on SO about blur algorithms

You may also like