Fast Bitmap Blur For Android SDK

后端 未结 19 1474
情书的邮戳
情书的邮戳 2020-11-22 08:53

Currently in an Android application that I\'m developing I\'m looping through the pixels of an image to blur it. This takes about 30 seconds on a 640x480 image.

W

相关标签:
19条回答
  • 2020-11-22 09:28

    EDIT (April 2014): This is a question/answer page that still gets a lot of hits it seems. I know I'm always getting upvotes for this post. But if you're reading this, you need to realize the answers posted here (both mine and the accepted answer) are out of date. If you want to implement efficient blur today, you should use RenderScript instead of the NDK or Java. RenderScript runs on Android 2.2+ (using the Android Support Library), so there's no reason not to use it.

    The old answer follows, but beware as it's outdated.


    For future² Googlers, here is an algorithm that I ported from Yahel's port of Quasimondo's algorithm, but using the NDK. It's based on Yahel's answer, of course. But this is running native C code, so it's faster. Much faster. Like, 40 times faster.

    I find that using the NDK is how all image manipulation should be done on Android... it's somewhat annoying to implement at first (read a great tutorial on using JNI and the NDK here), but much better, and near real time for a lot of things.

    For reference, using Yahel's Java function, it took 10 seconds to blur my 480x532 pixels image with a blur radius of 10. But it took 250ms using the native C version. And I'm pretty sure it can still be further optimized... I just did a dumb conversion of the java code, there's probably some manipulations that can be shortened, didn't want to spend too much time refactoring the whole thing.

    #include <jni.h>
    #include <string.h>
    #include <math.h>
    #include <stdio.h>
    #include <android/log.h>
    #include <android/bitmap.h>
    
    #define LOG_TAG "libbitmaputils"
    #define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
    #define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
    
    typedef struct {
        uint8_t red;
        uint8_t green;
        uint8_t blue;
        uint8_t alpha;
    } rgba;
    
    JNIEXPORT void JNICALL Java_com_insert_your_package_ClassName_functionToBlur(JNIEnv* env, jobject obj, jobject bitmapIn, jobject bitmapOut, jint radius) {
        LOGI("Blurring bitmap...");
    
        // Properties
        AndroidBitmapInfo   infoIn;
        void*               pixelsIn;
        AndroidBitmapInfo   infoOut;
        void*               pixelsOut;
    
        int ret;
    
        // Get image info
        if ((ret = AndroidBitmap_getInfo(env, bitmapIn, &infoIn)) < 0 || (ret = AndroidBitmap_getInfo(env, bitmapOut, &infoOut)) < 0) {
            LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
            return;
        }
    
        // Check image
        if (infoIn.format != ANDROID_BITMAP_FORMAT_RGBA_8888 || infoOut.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
            LOGE("Bitmap format is not RGBA_8888!");
            LOGE("==> %d %d", infoIn.format, infoOut.format);
            return;
        }
    
        // Lock all images
        if ((ret = AndroidBitmap_lockPixels(env, bitmapIn, &pixelsIn)) < 0 || (ret = AndroidBitmap_lockPixels(env, bitmapOut, &pixelsOut)) < 0) {
            LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
        }
    
        int h = infoIn.height;
        int w = infoIn.width;
    
        LOGI("Image size is: %i %i", w, h);
    
        rgba* input = (rgba*) pixelsIn;
        rgba* output = (rgba*) pixelsOut;
    
        int wm = w - 1;
        int hm = h - 1;
        int wh = w * h;
        int whMax = max(w, h);
        int div = radius + radius + 1;
    
        int r[wh];
        int g[wh];
        int b[wh];
        int rsum, gsum, bsum, x, y, i, yp, yi, yw;
        rgba p;
        int vmin[whMax];
    
        int divsum = (div + 1) >> 1;
        divsum *= divsum;
        int dv[256 * divsum];
        for (i = 0; i < 256 * divsum; i++) {
            dv[i] = (i / divsum);
        }
    
        yw = yi = 0;
    
        int stack[div][3];
        int stackpointer;
        int stackstart;
        int rbs;
        int ir;
        int ip;
        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 = input[yi + min(wm, max(i, 0))];
    
                ir = i + radius; // same as sir
    
                stack[ir][0] = p.red;
                stack[ir][1] = p.green;
                stack[ir][2] = p.blue;
                rbs = r1 - abs(i);
                rsum += stack[ir][0] * rbs;
                gsum += stack[ir][1] * rbs;
                bsum += stack[ir][2] * rbs;
                if (i > 0) {
                    rinsum += stack[ir][0];
                    ginsum += stack[ir][1];
                    binsum += stack[ir][2];
                } else {
                    routsum += stack[ir][0];
                    goutsum += stack[ir][1];
                    boutsum += stack[ir][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;
                ir = stackstart % div; // same as sir
    
                routsum -= stack[ir][0];
                goutsum -= stack[ir][1];
                boutsum -= stack[ir][2];
    
                if (y == 0) {
                    vmin[x] = min(x + radius + 1, wm);
                }
                p = input[yw + vmin[x]];
    
                stack[ir][0] = p.red;
                stack[ir][1] = p.green;
                stack[ir][2] = p.blue;
    
                rinsum += stack[ir][0];
                ginsum += stack[ir][1];
                binsum += stack[ir][2];
    
                rsum += rinsum;
                gsum += ginsum;
                bsum += binsum;
    
                stackpointer = (stackpointer + 1) % div;
                ir = (stackpointer) % div; // same as sir
    
                routsum += stack[ir][0];
                goutsum += stack[ir][1];
                boutsum += stack[ir][2];
    
                rinsum -= stack[ir][0];
                ginsum -= stack[ir][1];
                binsum -= stack[ir][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 = max(0, yp) + x;
    
                ir = i + radius; // same as sir
    
                stack[ir][0] = r[yi];
                stack[ir][1] = g[yi];
                stack[ir][2] = b[yi];
    
                rbs = r1 - abs(i);
    
                rsum += r[yi] * rbs;
                gsum += g[yi] * rbs;
                bsum += b[yi] * rbs;
    
                if (i > 0) {
                    rinsum += stack[ir][0];
                    ginsum += stack[ir][1];
                    binsum += stack[ir][2];
                } else {
                    routsum += stack[ir][0];
                    goutsum += stack[ir][1];
                    boutsum += stack[ir][2];
                }
    
                if (i < hm) {
                    yp += w;
                }
            }
            yi = x;
            stackpointer = radius;
            for (y = 0; y < h; y++) {
                output[yi].red = dv[rsum];
                output[yi].green = dv[gsum];
                output[yi].blue = dv[bsum];
    
                rsum -= routsum;
                gsum -= goutsum;
                bsum -= boutsum;
    
                stackstart = stackpointer - radius + div;
                ir = stackstart % div; // same as sir
    
                routsum -= stack[ir][0];
                goutsum -= stack[ir][1];
                boutsum -= stack[ir][2];
    
                if (x == 0) vmin[y] = min(y + r1, hm) * w;
                ip = x + vmin[y];
    
                stack[ir][0] = r[ip];
                stack[ir][1] = g[ip];
                stack[ir][2] = b[ip];
    
                rinsum += stack[ir][0];
                ginsum += stack[ir][1];
                binsum += stack[ir][2];
    
                rsum += rinsum;
                gsum += ginsum;
                bsum += binsum;
    
                stackpointer = (stackpointer + 1) % div;
                ir = stackpointer; // same as sir
    
                routsum += stack[ir][0];
                goutsum += stack[ir][1];
                boutsum += stack[ir][2];
    
                rinsum -= stack[ir][0];
                ginsum -= stack[ir][1];
                binsum -= stack[ir][2];
    
                yi += w;
            }
        }
    
        // Unlocks everything
        AndroidBitmap_unlockPixels(env, bitmapIn);
        AndroidBitmap_unlockPixels(env, bitmapOut);
    
        LOGI ("Bitmap blurred.");
    }
    
    int min(int a, int b) {
        return a > b ? b : a;
    }
    
    int max(int a, int b) {
        return a > b ? a : b;
    }
    

    Then use it like this (considering a class called com.insert.your.package.ClassName and a native function called functionToBlur, as the code above states):

    // Create a copy
    Bitmap bitmapOut = bitmapIn.copy(Bitmap.Config.ARGB_8888, true);
    // Blur the copy
    functionToBlur(bitmapIn, bitmapOut, __radius);
    

    It expects a RGB_8888 bitmap!

    To use a RGB_565 bitmap, either create a converted copy before passing the parameter (yuck), or change the function to use a new rgb565 type instead of rgba:

    typedef struct {
        uint16_t byte0;
    } rgb565;
    

    The problem is that if you do that you can't read .red, .green and .blue of the pixel anymore, you need to read the byte properly, duh. When I needed that before, I did this:

    r = (pixels[x].byte0 & 0xF800) >> 8;
    g = (pixels[x].byte0 & 0x07E0) >> 3;
    b = (pixels[x].byte0 & 0x001F) << 3;
    

    But there's probably some less dumb way of doing it. I'm not much of a low-level C coder, I'm afraid.

    0 讨论(0)
  • 2020-11-22 09:28

    Here is a realtime blurring overlay using RenderScript, which seems to be fast enough.

    https://github.com/mmin18/RealtimeBlurView

    0 讨论(0)
  • 2020-11-22 09:29

    For those still having issues with Renderscript support library on x86 chipsets, please have a look at this post by the creator of the library. It looks like the fix he prepared didn't make it somehow to the Build Tools v20.0.0, so he provides the files to fix it manually and a brief explanation of how to do it.

    https://code.google.com/p/android/issues/detail?can=2&start=0&num=100&q=&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars&groupby=&sort=&id=71347

    0 讨论(0)
  • 2020-11-22 09:32

    This code is work perfect for me

    Bitmap tempbg = BitmapFactory.decodeResource(getResources(),R.drawable.b1); //Load a background.
    Bitmap final_Bitmap = BlurImage(tempbg);
    
    
    @SuppressLint("NewApi")
    Bitmap BlurImage (Bitmap input)
    {
        try
        {
        RenderScript  rsScript = RenderScript.create(getApplicationContext());
        Allocation alloc = Allocation.createFromBitmap(rsScript, input);
    
        ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create(rsScript,   Element.U8_4(rsScript));
        blur.setRadius(21);
        blur.setInput(alloc);
    
        Bitmap result = Bitmap.createBitmap(input.getWidth(), input.getHeight(), Bitmap.Config.ARGB_8888);
        Allocation outAlloc = Allocation.createFromBitmap(rsScript, result);
    
        blur.forEach(outAlloc);
        outAlloc.copyTo(result);
    
        rsScript.destroy();
        return result;
        }
        catch (Exception e) {
            // TODO: handle exception
            return input;
        }
    
    }
    
    0 讨论(0)
  • 2020-11-22 09:33

    You can now use ScriptIntrinsicBlur from the RenderScript library to blur quickly. Here is how to access the RenderScript API. The following is a class I made to blur Views and Bitmaps:

    public class BlurBuilder {
        private static final float BITMAP_SCALE = 0.4f;
        private static final float BLUR_RADIUS = 7.5f;
    
        public static Bitmap blur(View v) {
            return blur(v.getContext(), getScreenshot(v));
        }
    
        public static Bitmap blur(Context ctx, Bitmap image) {
            int width = Math.round(image.getWidth() * BITMAP_SCALE);
            int height = Math.round(image.getHeight() * BITMAP_SCALE);
    
            Bitmap inputBitmap = Bitmap.createScaledBitmap(image, width, height, false);
            Bitmap outputBitmap = Bitmap.createBitmap(inputBitmap);
    
            RenderScript rs = RenderScript.create(ctx);
            ScriptIntrinsicBlur theIntrinsic = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
            Allocation tmpIn = Allocation.createFromBitmap(rs, inputBitmap);
            Allocation tmpOut = Allocation.createFromBitmap(rs, outputBitmap);
            theIntrinsic.setRadius(BLUR_RADIUS);
            theIntrinsic.setInput(tmpIn);
            theIntrinsic.forEach(tmpOut);
            tmpOut.copyTo(outputBitmap);
    
            return outputBitmap;
        }
    
        private static Bitmap getScreenshot(View v) {
            Bitmap b = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888);
            Canvas c = new Canvas(b);
            v.draw(c);
            return b;
        }
    }
    
    0 讨论(0)
  • 2020-11-22 09:33

    Use Render Script as mentioned here http://blog.neteril.org/blog/2013/08/12/blurring-images-on-android/

    0 讨论(0)
提交回复
热议问题