
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:

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:
- From the picture we cut out the area that is directly behind the TextView.
- Blur
- The result is set 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. 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:
- When creating a fragment, a layout is created, a TextView is added to my “service panel” (I will use it to display the speed of the algorithm) and a blur is started.
- Inside
applyBlur()
I register onPreDrawListener
. I do this because at the time of calling this function, my UI elements are not ready yet, so there is really nothing to blur. Therefore, I need to wait for the moment when my layout is measured and ready for rendering. This callback will be called immediately before the first frame is drawn. - Inside
onPreDraw()
first thing I usually do is change the return value to true. The fact is that the IDE generates false
by default, which means that the drawing of the first frame will be skipped. In this case, I am interested in the first frame, so we set true. - Then we remove our callback - we are no longer interested in onPreDraw events
- Now I need to get the Bitmap out of my ImageView. Make her create a drawing cache and pick it up.
- Well, actually, blur. Consider this process in more detail.
At once I want to note that this code has a number of shortcomings, which you should definitely remember:
- This code does not "remap" when changing the layout. It is good to register onGlobalLayoutListener and restart the algorithm when receiving this event.
- Blur is done in the main thread. Do not try to do it in your applications - such operations should be “unloaded” into a separate thread in order not to block the UI. AsyncTask or something similar will cope with this task.
Let's return to
blur()
:
- An empty Bitmap is created, corresponding in size to our TextView - here we will copy a piece of our background
- Create a Canvas so that you can draw in this Bitmap
- Shift the coordinate system to the position at which the TextView is located
- Draw a piece of background
- At this stage, we have a Bitmap, which contains a piece of background that is directly behind the TextView.
- Create a Renderscript object
- Copy our Bitmap into the structure with which the Renderscript works
- Create a script for the blur (ScriptIntrinsicBlur)
- Set the blur parameters (in my case, the radius is 20) and run the script
- Copy the result back to our bitmap
- Great, we have a blurry bitmap - set it as a background for our TextView
Here's what happened:

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:

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!

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"); }
scaleFactor
determines how much we will reduce the image. In my case, we will reduce by 8 times. Moreover, given that we will lose the lion's share of pixels when the image is reduced / enlarged, we can safely reduce the blur radius of the main algorithm. By scientific poking, I reduced to 2x- Create a bitmap. This time it will be less - in this case 8 times.
- Note that when drawing, I set the flag
FILTER_BITMAP_FLAG
, which will allow smoothing to be applied when the image is resized - As before, we apply blur, but now to a much smaller picture and with a smaller radius, which allows us to speed up the algorithm
- We put this small picture as a background for TextView - it will automatically be enlarged.
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 GitHubThe progenitor of the articlePost on SO about blur algorithms