Syncing simple book: Android Image Gaussian blur solution

In recent years, image Gaussian blur has been favored by designers. In various well-known apps, such as wechat, mobile QQ, NetEase Cloud Music and so on, there are background Gaussian blur design. In Adnroid, there are three commonly used image Gaussian blur technologies: RenderScript, fastBlur, optimizations to RenderScript and fastBlur, then analyze the pros and cons of each and which solution to use in your project. First, a rendering:

Gaussian blur rendering. PNG

1, RenderScript

RenderScript is a high-performance, intensive computing framework for Android. RenderScript is primarily used for data parallel computing, and is particularly useful for image processing, photographic analysis, and computer vision. RenderScript was introduced in Android3.0 (API 11). The Android image gaussian blur is usually done with this library. It provides apis for our Java layer calls and is actually handled at the C/C ++ layer, so it is usually the most efficient and efficient. To complete gaussian blur using RenderScript, you need to initialize a RenderScript Context: RenderScript Context is created through the create(Context) method, which guarantees the use of RenderScript and provides control over all subsequent RenderScript objects (e.g. ScriptIntrinsicBlur, Allocation, etc.) life cycle objects.

(2) Create at least one Allocation via Script: an Allocation is a RenderScript object that provides a large amount of variable data. In the kernel, Allocation serves as input and output. In the kernel, Allocation is accessed through the rsGetElementAt_type () and rsSetElementAt_type() methods when script global binding. Use createFromBitmap and createTyped to create an Allocation.

(3) Founding ScriptIntrinsic: It has built-in some general operations of RenderScript, such as Gaussian blur, distortion transform, image blending, etc. For more operations, see The subclass of ScriptIntrinsic. The Gaussian blur used in this paper is ScriptIntrinsicBlur. (4) Populate data to Allocations: All Allocations were filled with empty data at first creation, except for Allocation created using method createFromBitmap.

(5) Set the fuzzy radius: Set a fuzzy radius from 0 to 25.

(6) Start kernel, call method processing: call forEach method fuzzy processing.

Copy data from Allocation: In order to access the Allocation data in the Java layer, use one of Allocation’s copy methods to copy the data. Destroy the RenderScript object: You can use the destroy method to destroy the RenderScript object or make it garbage recyclable. After destroy, you can use the RenderScript object that it controls (for example, after the destroy, Invoking ScriptIntrinsic or Allocation is optional.

The above steps can be completed in the image of gaussian blur, take a look at the corresponding code:

private static Bitmap rsBlur(Context context,Bitmap source,int radius){

        Bitmap inputBmp = source;
        / / (1)
        RenderScript renderScript =  RenderScript.create(context);

        Log.i(TAG,"scale size:"+inputBmp.getWidth()+"*"+inputBmp.getHeight());

        // Allocate memory for Renderscript to work with
        / / (2)
        final Allocation input = Allocation.createFromBitmap(renderScript,inputBmp);
        final Allocation output = Allocation.createTyped(renderScript,input.getType());
        / / (3)
        // Load up an instance of the specific script that we want to use.
        ScriptIntrinsicBlur scriptIntrinsicBlur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript));
        / / (4)
        scriptIntrinsicBlur.setInput(input);
        / / (5)
        // Set the blur radius
        scriptIntrinsicBlur.setRadius(radius);
        / / (6)
        // Start the ScriptIntrinisicBlur
        scriptIntrinsicBlur.forEach(output);
        / / (7)
        // Copy the output to the blurred bitmap
        output.copyTo(inputBmp);
        / / (8)
        renderScript.destroy();

    return inputBmp;
    }Copy the code

The corresponding steps above have been numbered, the code is only about ten lines, very simple. These are the ten libraries Android provides that can handle gaussian blur on images. The performance is better because the processing is done at the C/C ++ layer. But it can only be used with API 17 or later, see the documentation:

RC – API. PNG

As shown above, ScriptIntrinsicBlur marked in the red box was added in API 17, so it doesn’t work on older phones, so we have to explore other options to make it compatible with older phones.

RenderScript compatibility packages: Fortunately, Google to compatible with low version can also use RenderScript, added a compatibility pack, android. Support. V8. RenderScript, Renderscript is compatible with Android 2.3 (API 9) using support-v8.renderScript, and there are probably no phones on the market that are lower than 2.3 (there aren’t many 4.X phones left). Using the compatibility package is exactly the same as using the native RenderScript; the code is the same as above. Just add the following code to your app’s build.gradle

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.3"

    defaultConfig {
        minSdkVersion 9
        targetSdkVersion 19

        / / use the support. The v8. Renderscript
        renderscriptTargetApi 18
        renderscriptSupportModeEnabled true}}Copy the code

Just add the two lines above. But there are two things to note:

Note: Revision 22.2 or higher of Android SDK Tools revision 22.2 or higher Android SDK Build-Tools Revision 18.1.0 or higher(Build-Tools requires 18.1.0 or higher)

With compatibility packages, is RenderScript a perfect solution? The answer is NO, and there are two drawbacks:

  • RenderScript was efficient, but it needed to be optimized to handle larger images at less than 16ms per frame
  • The APK package size has been increased. Support.v8.renderscript is 160K, and now many apps are asking APK to be thinner. This is unacceptable for APK, which is already very large.

So we’re going to have to look at alternatives, and then we’re going to look at the fastBlur algorithm.

2, fastBlur

FastBlur is an alternative to RenderScript that does image blur directly in the Java layer. Gaussian blur calculation is applied to each pixel point, and finally Bitmap is synthesized. Look at the source code:

 / * * * Stack the Blur v1.0 from * * Java Author: http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html Mario Klingemann 
      
        * http://incubator.quasimondo.com * * created Feburary 29, 2004 * Android port : Yahel Bouaziz 
       
         * http://www.kayenko.com * ported april 5th, 2012 * * This is a compromise between Gaussian Blur and Box blur * It creates much better looking blurs than Box Blur, but is * 7x faster than my Gaussian Blur implementation. * * I called it Stack Blur because this describes best how this  * filter works internally: it creates a kind of moving stack * of colors whilst scanning through the image. Thereby it * just has to add one new block of color to the right side * of the stack and remove the leftmost color. The remaining * colors on the topmost layer of the stack are either added on * or reduced by one, depending on if they are on the right or * on the left side of the stack. * * If you are using this algorithm in your code please add * the following line: * Stack Blur Algorithm by Mario Klingemann 
        
          */
        @quasimondo.com>
       
      

    private static Bitmap fastBlur(Bitmap sentBitmap, float scale, int radius) {

        int width = Math.round(sentBitmap.getWidth() * scale);
        int height = Math.round(sentBitmap.getHeight() * scale);
        sentBitmap = Bitmap.createScaledBitmap(sentBitmap, width, height, false);

        Bitmap bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);

        if (radius < 1) {
            return (null);
        }

        int w = bitmap.getWidth();
        int h = bitmap.getHeight();

        int[] pix = new int[w * h];
        Log.e("pix", w + "" + h + "" + pix.length);
        bitmap.getPixels(pix, 0, w, 0.0, w, h);

        int wm = w - 1;
        int hm = h - 1;
        int wh = w * h;
        int div = radius + radius + 1;

        int r[] = new int[wh];
        int g[] = new int[wh];
        int b[] = new int[wh];
        int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
        int vmin[] = new int[Math.max(w, h)];

        int divsum = (div + 1) > >1;
        divsum *= divsum;
        int dv[] = new int[256 * divsum];
        for (i = 0; i < 256 * divsum; i++) {
            dv[i] = (i / divsum);
        }

        yw = yi = 0;

        int[][] stack = new int[div][3];
        int stackpointer;
        int stackstart;
        int[] sir;
        int rbs;
        int r1 = radius + 1;
        int routsum, goutsum, boutsum;
        int rinsum, ginsum, binsum;

        for (y = 0; y < h; y++) {
            rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
            for (i = -radius; i <= radius; i++) {
                p = pix[yi + Math.min(wm, Math.max(i, 0))];
                sir = stack[i + radius];
                sir[0] = (p & 0xff0000) > >16;
                sir[1] = (p & 0x00ff00) > >8;
                sir[2] = (p & 0x0000ff);
                rbs = r1 - Math.abs(i);
                rsum += sir[0] * rbs;
                gsum += sir[1] * rbs;
                bsum += sir[2] * rbs;
                if (i > 0) {
                    rinsum += sir[0];
                    ginsum += sir[1];
                    binsum += sir[2];
                } else {
                    routsum += sir[0];
                    goutsum += sir[1];
                    boutsum += sir[2];
                }
            }
            stackpointer = radius;

            for (x = 0; x < w; x++) {

                r[yi] = dv[rsum];
                g[yi] = dv[gsum];
                b[yi] = dv[bsum];

                rsum -= routsum;
                gsum -= goutsum;
                bsum -= boutsum;

                stackstart = stackpointer - radius + div;
                sir = stack[stackstart % div];

                routsum -= sir[0];
                goutsum -= sir[1];
                boutsum -= sir[2];

                if (y == 0) {
                    vmin[x] = Math.min(x + radius + 1, wm);
                }
                p = pix[yw + vmin[x]];

                sir[0] = (p & 0xff0000) > >16;
                sir[1] = (p & 0x00ff00) > >8;
                sir[2] = (p & 0x0000ff);

                rinsum += sir[0];
                ginsum += sir[1];
                binsum += sir[2];

                rsum += rinsum;
                gsum += ginsum;
                bsum += binsum;

                stackpointer = (stackpointer + 1) % div;
                sir = stack[(stackpointer) % div];

                routsum += sir[0];
                goutsum += sir[1];
                boutsum += sir[2];

                rinsum -= sir[0];
                ginsum -= sir[1];
                binsum -= sir[2];

                yi++;
            }
            yw += w;
        }
        for (x = 0; x < w; x++) {
            rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
            yp = -radius * w;
            for (i = -radius; i <= radius; i++) {
                yi = Math.max(0, yp) + x;

                sir = stack[i + radius];

                sir[0] = r[yi];
                sir[1] = g[yi];
                sir[2] = b[yi];

                rbs = r1 - Math.abs(i);

                rsum += r[yi] * rbs;
                gsum += g[yi] * rbs;
                bsum += b[yi] * rbs;

                if (i > 0) {
                    rinsum += sir[0];
                    ginsum += sir[1];
                    binsum += sir[2];
                } else {
                    routsum += sir[0];
                    goutsum += sir[1];
                    boutsum += sir[2];
                }

                if (i < hm) {
                    yp += w;
                }
            }
            yi = x;
            stackpointer = radius;
            for (y = 0; y < h; y++) {
                // Preserve alpha channel: ( 0xff000000 & pix[yi] )
                pix[yi] = ( 0xff000000 & pix[yi] ) | ( dv[rsum] << 16 ) | ( dv[gsum] << 8 ) | dv[bsum];

                rsum -= routsum;
                gsum -= goutsum;
                bsum -= boutsum;

                stackstart = stackpointer - radius + div;
                sir = stack[stackstart % div];

                routsum -= sir[0];
                goutsum -= sir[1];
                boutsum -= sir[2];

                if (x == 0) {
                    vmin[y] = Math.min(y + r1, hm) * w;
                }
                p = x + vmin[y];

                sir[0] = r[p];
                sir[1] = g[p];
                sir[2] = b[p];

                rinsum += sir[0];
                ginsum += sir[1];
                binsum += sir[2];

                rsum += rinsum;
                gsum += ginsum;
                bsum += binsum;

                stackpointer = (stackpointer + 1) % div;
                sir = stack[stackpointer];

                routsum += sir[0];
                goutsum += sir[1];
                boutsum += sir[2];

                rinsum -= sir[0];
                ginsum -= sir[1];
                binsum -= sir[2];

                yi += w;
            }
        }

        Log.e("pix", w + "" + h + "" + pix.length);
        bitmap.setPixels(pix, 0, w, 0.0, w, h);

        return (bitmap);
    }Copy the code

As shown above, there are no compatibility issues with this approach, nor will the introduction of JAR packages cause the APK to grow. But this method is very inefficient, think about it, because it is in the Java layer processing, of course, slow. For an 800 x 450 image,RenderScript averaged around 25 ms and fastBlur averaged around 310ms, a 10-fold difference. There is also the use of this way is to load all the pictures into the memory, if the picture is large, easy to cause OOM.

3. RenderScript and fastBlur optimizations

RenderScript and fastBlur have been analyzed above. Although RenderScript is much more efficient than fastBlur, it is still possible to fail to meet the requirement of 16ms per frame, resulting in lag. So you need to optimize.

Idea: provide optimization idea on stackOverFlow (address: stackoverflow.com/questions/2… Here’s how it works: You shrink an image so it loses a few pixels, blur it, and then zoom it back up. As the image is reduced before fuzzy processing, the pixels and radius to be processed become smaller, which speeds up the fuzzy processing.

So we just need to make the original image smaller and use RenderScript or fastBlur to make it faster. Add the following code:

        int width = Math.round(source.getWidth() * scale);
        int height = Math.round(source.getHeight() * scale);

        Bitmap inputBmp = Bitmap.createScaledBitmap(source,width,height,false);Copy the code

The complete method for renderScript Gaussian blur is as follows:

private static Bitmap rsBlur(Context context,Bitmap source,int radius,float scale){

        Log.i(TAG,"origin size:"+source.getWidth()+"*"+source.getHeight());
        int width = Math.round(source.getWidth() * scale);
        int height = Math.round(source.getHeight() * scale);

        Bitmap inputBmp = Bitmap.createScaledBitmap(source,width,height,false);

        RenderScript renderScript =  RenderScript.create(context);

        Log.i(TAG,"scale size:"+inputBmp.getWidth()+"*"+inputBmp.getHeight());

        // Allocate memory for Renderscript to work with

        final Allocation input = Allocation.createFromBitmap(renderScript,inputBmp);
        final Allocation output = Allocation.createTyped(renderScript,input.getType());

        // Load up an instance of the specific script that we want to use.
        ScriptIntrinsicBlur scriptIntrinsicBlur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript));
        scriptIntrinsicBlur.setInput(input);

        // Set the blur radius
        scriptIntrinsicBlur.setRadius(radius);

        // Start the ScriptIntrinisicBlur
        scriptIntrinsicBlur.forEach(output);

        // Copy the output to the blurred bitmap
        output.copyTo(inputBmp);


        renderScript.destroy();
    return inputBmp;
    }Copy the code

Shrink the Bitmap first, then blur it.

Note: The reduced coefficient should be an integer power of 2, i.e. the scale in the above code should be 1/2, 1/4, 1/8… See bitmapFactory. Options for the inSample coefficient for image scaling. According to the experience of predecessors, the general scale = 1/8 is preferred.

Take a look at the time it took to blur an image using RenderScript and fastBlur and optimize it for Gaussian blur. The test model was Meizu Metal, running Android 5.1, as follows:

Fuzzy time comparison table.png

As shown above: Take an image of 1080 x 1349 as an example (5 mean values are taken for each half). Gaussian blur is performed with both methods using the original size. RenderScript is about 10 times faster than fastBlur, but both are over 16ms. The efficiency of both methods is improved qualitatively. RenderScript blur time is less than 5ms, fastBlur is close to 16ms, and the radius is less than 15 and 16ms.

Therefore, no matter which method is used to blur the image, it should be optimized first and then blurred.

4. Comparison of advantages and disadvantages and gaussian blur scheme

RenderScript advantages:

  • Gaussian blur can be done in about ten lines of code using a simple, native API
  • High efficiency, is in C/C ++ layer to do processing

RenderScript faults:

  • Only API 17 or above can be used
  • Use compatible package, will lead to APK volume increase, support package about 160K

FastBlur benefits:

  • There is no compatible version problem
  • Without introducing tripartite packets, APK size will not be increased

Disadvantages of fastBlur:

  • It’s inefficient to do processing in the Java layer
  • Load all bitmaps into memory, larger images are easy to OOM

The above comparison of the advantages and disadvantages of the two methods, each has its own advantages and disadvantages, so which one do we choose? If the APK is small enough to accept an increase of 160K, then use RenderScript, which is compatible with the package. Gaussian blur solution 2: If you don’t want to increase the size of APK, make a judgment during blur. If THE API version is larger than 17, use the native RenderScript blur method. If the API version is smaller than 17, use fastBlur method. (Again, you need to optimize first, then blur).

5, the wheel

Since Gaussian blur is often used in projects where every project has to copy code, which is cumbersome and inelegant, these two methods are optimized to encapsulate a Lib that adds dependencies when needed.

Build. Gradle = build. Gradle = build.

allprojects {
    repositories {
        jcenter()

        maven {url "https://jitpack.io"}}}Copy the code

Build. Gradle for app add:

  dependencies {
  compile 'com. Making. Pinguo - zhouwei: EasyBlur: v1.0.0'
}Copy the code

Build. Gradle for app add:

 defaultConfig {
        applicationId "com.zhouwei.easyblur"
        minSdkVersion 16
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

        // Use renderScript compatibility package
        renderscriptTargetApi 25
        renderscriptSupportModeEnabled true
    }Copy the code

Use method: 1, simple use, specify the Bitmap and radius

Bitmap finalBitmap = EasyBlur.with(MainActivity.this)
                        .bitmap(overlay) // Blur the image
                        .radius(10)// Blur the radius
                        .blur();Copy the code

2. You can specify the zoom factor. The default zoom factor is 8

Bitmap finalBitmap = EasyBlur.with(MainActivity.this)
                        .bitmap(overlay) // Blur the image
                        .radius(10)// Blur the radius
                        .scale(4)// Specify the zoom multiple before blurring
                        .blur();Copy the code

3. Specify which method to use. The default is compatible RenderScript Gaussian blur

Bitmap finalBitmap = EasyBlur.with(MainActivity.this)
                        .bitmap(overlay) // Blur the image
                        .radius(10)// Blur the radius
                        .scale(4)// Specify the zoom multiple before blurring
                        .policy(EasyBlur.BlurPolicy.FAST_BLUR)/ / use fastBlur
                        .blur();Copy the code

The code has been uploaded to Github:EasyBlur

The resources

Fast Bitmap Blur For Android SDK RenderScript API Guide