毛玻璃效果从ios流行,逐渐已经成为很多Android应用的设计选项


那么到底毛玻璃效果是什么呢?

毛玻璃效果其实是常说的高斯模糊。高斯模糊的原理中,它是根据高斯曲线调节像素色值,它是有选择地模糊图像。说得直白一点,就是高斯模糊能够把某一点周围的像素色值按高斯曲线统计起来,采用数学上加权平均的计算方法得到这条曲线的色值,最后能够留下人物的轮廓,即曲线。

Java 实现(来自复制粘贴)

public static Bitmap blur(Bitmap bitmap, int iterations, int radius) {
        try {
            int width = bitmap.getWidth();
            int height = bitmap.getHeight();
            int[] inPixels = new int[width * height];
            int[] outPixels = new int[width * height];
            Bitmap blured = null;
            blured = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            bitmap.getPixels(inPixels, 0, width, 0, 0, width, height);
            for (int i = 0; i < iterations; i++) {
                blur(inPixels, outPixels, width, height, radius);
                blur(outPixels, inPixels, height, width, radius);
            }
            blured.setPixels(inPixels, 0, width, 0, 0, width, height);
            return blured;
        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        }
    }



private static void blur(int[] in, int[] out, int width, int height,
                             int radius) {
        int widthMinus1 = width - 1;
        int tableSize = 2 * radius + 1;
        int divide[] = new int[256 * tableSize];
        for (int index = 0; index < 256 * tableSize; index++) {
            divide[index] = index / tableSize;
        }
        int inIndex = 0;
        for (int y = 0; y < height; y++) {
            int outIndex = y;
            int ta = 0, tr = 0, tg = 0, tb = 0;
            for (int i = -radius; i <= radius; i++) {
                int rgb = in[inIndex + clamp(i, 0, width - 1)];
                ta += (rgb >> 24) & 0xff;
                tr += (rgb >> 16) & 0xff;
                tg += (rgb >> 8) & 0xff;
                tb += rgb & 0xff;
            }
            for (int x = 0; x < width; x++) {
                out[outIndex] = (divide[ta] << 24) | (divide[tr] << 16)
                        | (divide[tg] << 8) | divide[tb];
                int i1 = x + radius + 1;
                if (i1 > widthMinus1)
                    i1 = widthMinus1;
                int i2 = x - radius;
                if (i2 < 0)
                    i2 = 0;
                int rgb1 = in[inIndex + i1];
                int rgb2 = in[inIndex + i2];
                ta += ((rgb1 >> 24) & 0xff) - ((rgb2 >> 24) & 0xff);
                tr += ((rgb1 & 0xff0000) - (rgb2 & 0xff0000)) >> 16;
                tg += ((rgb1 & 0xff00) - (rgb2 & 0xff00)) >> 8;
                tb += (rgb1 & 0xff) - (rgb2 & 0xff);
                outIndex += height;
            }
            inIndex += width;
        }
    }复制代码

实际效果,在对华为揽月M2测试机上,blur一张1024x768的bmp需要耗时3900ms。这个速度还是很难令人接受的。

可是事实情况是这么多点,每个点都要进行运算,怎么优化呢?


优化分析:

其实想要优化,可以从两个方面考虑。一个是blur算法本身,一个是算法之外。

算法本身:

Android是Java开发的底层native使用C++做的。所以算法如果能在native层实现运算,然后把结果传给java层肯定能加快速度。

很幸运,在google提供这么一个库RenderScript,可以帮助我们通过调用cpu多内核直接进行计算。而且刚好封装了高斯模糊的算法。

RenderScript的使用可以google或百度。

算法如下:

public Bitmap blur(Bitmap src, int radius) {
        final Allocation input = Allocation.createFromBitmap(rs, src);
        final Allocation output = Allocation.createTyped(rs, input.getType());
        final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
        script.setRadius(radius);
        script.setInput(input);
        script.forEach(output);
        output.copyTo(src);
        return src;
    }复制代码

renderScript需要引入一个依赖

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.3"
    defaultConfig {
        minSdkVersion 9
        targetSdkVersion 19
        renderscriptTargetApi 18
        renderscriptSupportModeEnabled true
    }
}复制代码

另外官方建议renderScript一个application中最好只有一个实例。

算法提升非常大,原来需要3900ms的blur只需要70ms上下


算法之外

我们前面分析过高斯模糊的原理,其实是对每个像素点进行运算处理。1024x768的bmp就有768k个像素点,像素越多肯定越慢。所以有没有办法减少运算的像素数呢?

对了,相信你已经想到了:先把bmp缩小,进行blur,再把它放大。因为是高斯模糊化,缩小损失的精度不会对效果造成很大影响。只要掌握好缩放比例,就能轻松实现高斯模糊。

算法如下:

public static Bitmap superBlur(Context context, Bitmap bitmap, int radius) {
        int factor = 3;
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        int scaledWidth = width / factor;
        int scaledHeight = height / factor;
        Bitmap scaledBmp = Bitmap.createScaledBitmap(bitmap, scaledWidth, scaledHeight, true);
        RenderScriptUtils.getInstance(context).blur(scaledBmp, radius);
        Bitmap resBmp = Bitmap.createScaledBitmap(scaledBmp, width, height, true);
        scaledBmp.recycle();
        return resBmp;
    }复制代码

速度已经可以到65ms!

what?只提升 5ms?有必要优化吗?lol...

其实可以不用这一步优化了,因为整体时间很短,缩放优化出来的时间很有限。

优化的时间 - bmp缩放时间 = 5s复制代码

效果已经不太明显了。这里我只是要安利一个发散的思路而已。

当然如果不想引用第三方库,只是用之前的java算法做高斯模糊,强烈推荐我的第二种优化思路。