Android高斯模糊

传送门
github地址:http://developer.android.com/guide/topics/renderscript/compute.html
https://github.com/kikoso/android-stackblur
csdn参考知识:http://blog.csdn.net/huli870715/article/details/39378349
感谢大神们的无私奉献,让我从小彩笔慢慢成长成大彩笔的梦想更近一步。

什么是高斯模糊(根据百科描述)

技术分享

高斯模糊能够把某一点周围的像素色值按高斯曲线统计起来,采用数学上加权平均的计算方法得到这条曲线的色值,最后能够留下人物的轮廓,即曲线。
所有的颜色不过都是数字,各种模糊不过都是算法。把要模糊的像素色值统计,用数学上加权平均的计算方法(高斯函数)得到色值,对范围、半径等进行模糊,大致就是高斯模糊。

高斯函数、正太分布、权重矩阵……有兴趣的人可以了解一下,我们主要还是调用【stackblur】开源项目中名为fastBlur的方法在java层直接进行高斯模糊处理。

根据网上的一些参考资料,以及自己项目实际的要求,一开始直接调用下述方法等到以及模糊后的效果图

 private Bitmap blurImageAmeliorate(Bitmap sentBitmap,int radius, boolean canReuseInBitmap) 
    { 
        Bitmap bitmap;  

        if (canReuseInBitmap) {  
            bitmap = sentBitmap;  
        } else {  
            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];  
        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;  
            }  
        }  

        bitmap.setPixels(pix, 0, w, 0, 0, w, h);  

        return (bitmap);  
    } 

结果没有问题,效果图就想网上描述的一样。
但有一个问题就是直接使用原图进行高斯模糊处理的时间有点长,大概200毫秒左右,有明显的停顿感,即使用Handler异步处理,但图片展示的延后,用户体验明显下降。

stackOverflow对于程序员来说永远是最大的宝藏。http://stackoverflow.com/questions/2067955/fast-bitmap-blur-for-android-sdk这篇提问帖终于提供了新的解决思路:
This is a shot in the dark, but you might try shrinking the image and then enlarging it again. This can be done with Bitmap.createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter). Make sure and set the filter parameter to true. It’ll run in native code so it might be faster.
它所表述的原理为先通过缩小图片,使其丢失一些像素点,接着进行模糊化处理,然后再放大到原来尺寸。由于图片缩小后再进行模糊处理,需要处理的像素点和半径都变小,从而使得模糊处理速度加快。
于是在高斯模糊算法之外我们套接一层,进行位图的缩小。

private void blur(Bitmap bkg, View view) {  
        float radius = 2;  
        float scaleFactor = 8;  
        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);  
        view.setBackground(new BitmapDrawable(getResources(), blurImageAmeliorate(overlay, (int)radius, true)));  
    }   

Bitmap不懂,不常用有没有。。。
Canvas不懂,不常用有没有。。。
Paint不懂,不常用有没有。。。
然后直接拿来调用最终的效果不错
技术分享
那么问题来了,为什么背景图没有填充整个RelativeLayout呢,我们通过RelativeLayout.setBackground来设置背景。
各种测试后问题原因在于Canvas缩小的处理,对于Canvas实在不在行,知道问题所在,也解释不了。
现在回到正题,我们要做得是在高斯模糊处理之前得到一个缩小的位图,在此再感谢一下李刚老师的【疯狂Android讲义】,平时还是可以翻一翻,加深一些相关的知识。通过调用Bitmap.createScaledBitmap方法,我们可以等到一个缩小后的位图,而且能顺利填充到组件背景,在效率上对比第一种快上不少,问题就如此处理了。。。除了Canvas的问题!

要用到高斯模糊上网查了很久,其它的处理方式我这也从别人那copy一下,以备以后有用。(以下内容,我只是用过一下下,出现的bug暂时没有处理)

RenderScript
RenderScript是API11之后才引入的,所以对版本有限制,而且RenderScript确实挺复杂的,虽然使用他的Blur功能很简单,但是要真正搞懂,不是一天两天的事。学习文档:http://developer.android.com/guide/topics/renderscript/compute.html

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(SecondActivity.this);  

    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("cost " + (System.currentTimeMillis() - startMs) + "ms");  
}  

布局代码就不详细的copy过来了,实现这个方法一个要求最低的sdk版本是11,对于硬件要求是17,也就是说如果手机Android版本是4.2以下的话,应该是用不了的。我测试机还是小米1,让我呵呵一笑。

以上差不多是自己这次接触高斯模糊所见所学,有不足之处,请大家谅解。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。