毛玻璃效果从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算法做高斯模糊,强烈推荐我的第二种优化思路。