18 May

Renderscript – parallel operations on bitmaps without using NDK

You have to perform operations on images and Java is too slow for that? You want to avoid playing with compiling code using dk? There is a solution! Renderscirpt will help you with this task. With it you can use parallel operations, executing them both on the CPU and GPU. Everything is available in a very accessible form, so you don't even have to wonder how to divide the work.

What exactly is renderscript?

Renderscript is a framework adapted to performing parallel operations. It is designed to be easy and almost effortless to use. It deals with dividing work and we are left only with designing the algorithm.

Basic properties

Script files are usually kept in scr/rs/file and their file format is .rs. We are writing a script in a language inheriting from C-99. Each script must contain a declaration of the version and the name of the package the classes of which will be using it. At the beginning of each file we give this information like this:

#pragma version(1)
#pragma rs java_package_name(com.example.app)

At this moment there is only one version of the script, so we always give 1. Another element of the script is the function init(), which is called always only once, at initialization of a script. It is not required so you can skip it.

Implementation

The most important part of any script is the function of calling. It looks like this:

uchar4 __attribute__((kernel)) greyscale(uchar4 in, uint32_t x, uint32_t y) {
  uchar4 out = in;
  int average = (in.r + in.g + in.b)/3;

  out.r = average;
  out.g = average;
  out.b = average;
  return out;
}

The first thing that draws our attention is __attribute __((kernel)) in function declaration. It tells us that the function is to be treated as a function of the kernel of renderscript. In this case, it takes three arguments. The first is of the uchar4 type and stores information about the colour of given pixel. The next two are for its position. Because renderscript performs tasks in a parallel way, our function is also called so, individually for each pixel. So created renderscript is ready to be used within Java classes.

Usage

The first thing we need to do is to declare using it in the build.gradle file

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        applicationId "com.exmaple.app"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"

        renderscriptTargetApi 18
        renderscriptSupportModeEnabled true
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

Renderscript classes are located in:

import android.support.v8.renderscript.*;

If you use Android 4.2+ you don't need to use the support library, however this is the recommended option as it always contains the newest version of renderscript. To have access to our script we first need to import it. We do this by adding a prefix ScriptC to the name of the script.

import com.example.app.ScriptC_invert;

Performing operations on bitmaps using renderscript looks like this:

public static Bitmap invertBitmap(Bitmap bitmap, Context context) {
        RenderScript rs = RenderScript.create(context);

        Allocation allocation = Allocation.createFromBitmap(rs, bitmap);

        Type t = allocation.getType();

        Allocation invertedAllocation = Allocation.createTyped(rs, t);

        ScriptC_invert invertScript = new ScriptC_invert(rs);

        invertScript.forEach_invert(allocation, invertedAllocation);

        invertedAllocation.copyTo(bitmap);

        allocation.destroy();
        invertedAllocation.destroy();
        invertScript.destroy();
        t.destroy();
        rs.destroy();
        return bitmap;
    }

At the very beginning we create an object of renderscript, and then the object of allocation of our bitmap. Another required element is the object of result allocation. Its type must match what we declared in the function, so we simply get it from the input allocation. The allocation itself is an object that stores elements which can be of different types. Thanks to that, it is possible to perform operations not only on images but also on many other things, such as matrices and vectors. When our allocations are ready, we need only to create an object of our script and call prepared function. Our function was equipped with a prefix forEach, which tells us that it will be performed on each item of allocation. After operation is completed successfully, we should move the data from the allocation to our bitmap (or a newly created one) and then destroy all created objects to release memory.

Conclusions

Renderscript is a very useful tool which can save us playing with NDK, but will also facilitate achieving high performance. Thanks to its versatility, it can be used in many parts of our applications. I encourage you to dig deeper into this subject.