Developer

How to use Android's RenderScript engine for hardware optimized blur effects

William J. Francis explains how to use Android's RenderScript functions to hardware accelerate a Guassian blur.

android-040714.jpg

One of the most underused capabilities on the Android platform is probably RenderScript. I believe there are a number of reasons that contribute to developers choosing to overlook this powerful feature.

First of all, it's not Java. Secondly, for those of you familiar with C (the basis for RenderScript syntax), the process of compiling and marshaling kernels to Java can quickly become convoluted. Finally, in my opinion RenderScript is one of the most poorly documented tools in Android's toolbox.

With so many reasons adding to RenderScript's lack of popularity, why would a developer bother? One word: speed. RenderScript is blazingly efficient at execution time. The following description comes straight from Google.

The RenderScript runtime will parallelize work across all processors available on a device, such as multi-core CPUs, GPUs, or DSPs, allowing you to focus on expressing algorithms rather than scheduling work or load balancing. RenderScript is especially useful for applications performing image processing, computational photography, or computer vision.

Recently, I had several factors all align that caused me to give RenderScript another look. First, I had a customer requirement to perform a Gaussian blur on an image. My initial trials at manipulating the Android canvas myself proved both memory and processor intensive. I tried numerous approaches and still found myself needing to allocate nearly twice the memory of the original image, and the total calculations often chugged for over 10 seconds before completing.

Then I remembered a colleague who was very fond of RenderScript. I started googling and found that beginning with API 17, the Android SDK began including several pre-compiled RenderScripts covering an array of commonly used image processing algorithms.

The final piece of my solution fell into place when I discovered that Google was now providing a RenderScript V8 Support jar, allowing applications as far back as Android 2.2 to take advantage of the new feature. With no reason besides my own lack of experience holding me back, I forged ahead with my RenderScript solution. The result was everything I hoped for and more. Take a look at the tutorial below and see for yourself! Feel free to follow along, or download and import the entire project directly into Eclipse.

1. Create a new Android project in Eclipse. Target Android 2.2 (Froyo) or better.

2. There is no good reason not to use the compatibility library. However, unlike the standard V4 support library, there isn't a way to automagically install the RenderScript support library. You need to browse to the latest version of your SDK /build-tools/android-x/renderscript/lib directory and copy the renderscript-v8.jar to the /libs folder of your project. Additionally, you will want to add the folders /armeabi-v7a, /mips, and /x86 from the /packaged directory to your libs folder. These contain the .so drivers necessary to interface directly with your device hardware.

3. With the renderscript support libraries now in place, we can proceed with the /res/layout/activity_main.xml file. In this case I have an image view and a button centered inside a relative layout.

activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <ImageView
        android:id="@+id/img"
        android:layout_margin="20dp"
        android:layout_centerInParent="true"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:src="@drawable/kirk_gorn" />
    
    <Button 
        android:id="@+id/button"
        android:layout_below="@id/img"
        android:layout_centerHorizontal="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Blur Image"/>

</RelativeLayout>

4. The image view in our layout is pointing to a PNG I've included in the /res/drawable-xhdpi folder.

startrekkirkgorn.png

5. We can implement our MainActivity.java class in the /src folder. Because we are using the intrinsic (or precompiled) renderscripts, we don't need to write a single line of C code ourselves.

MainActivity.java
package com.authorwjf.fastimageblur;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.support.v8.renderscript.Allocation;
import android.support.v8.renderscript.Element;
import android.support.v8.renderscript.RenderScript;
import android.support.v8.renderscript.ScriptIntrinsicBlur;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;

public class MainActivity extends Activity implements OnClickListener {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		findViewById(R.id.button).setOnClickListener(this);
	}

	@Override
	public void onClick(View v) {
		ImageView iv = (ImageView) findViewById(R.id.img);
		iv.setImageBitmap(blurImage(((BitmapDrawable)iv.getDrawable()).getBitmap()));
	}
	
	private Bitmap blurImage(Bitmap bmIn) {
		RenderScript rs = RenderScript.create(this);
		Bitmap bmOut = Bitmap.createBitmap(bmIn.getWidth(), bmIn.getHeight(), Bitmap.Config.ARGB_8888);
		ScriptIntrinsicBlur theIntrinsic = ScriptIntrinsicBlur.create(rs, Element.RGBA_8888(rs));;
		Allocation tmpIn = Allocation.createFromBitmap(rs, bmIn);
		Allocation tmpOut = Allocation.createFromBitmap(rs, bmOut);
		theIntrinsic.setRadius(25.f);
		theIntrinsic.setInput(tmpIn);
		theIntrinsic.forEach(tmpOut);
		tmpOut.copyTo(bmOut);
		return bmOut;
	}
	
}

If you want to see the blur in action, you can run the generated APK on an emulator or a device. If you are running it on an emulator, make sure you check the "use host GPU" option during configuration.

Keep in mind that not all Android devices are going to support hardware acceleration. In these cases, the RenderScript support library falls back to a software solution that will run on the main CPU, so you will definitely notice a difference. Still, in my own testing with a Nexus 7, Nexus 4, and an older Samsung Galaxy S3, I was able to achieve the desired effect in under a second.

About William J. Francis

William J Francis began programming computers at age eleven. Specializing in embedded and mobile platforms, he has more than 20 years of professional software engineering under his belt, including a four year stint in the US Army's Military Intellige...

Editor's Picks

Free Newsletters, In your Inbox