前言
从 iOS 7 开始 Apple 从 拟物化 过渡到了 扁平化 的设计风格,同时也搭配使用了 毛玻璃风格 当做背景效果,不得不说十分惊艳,颇有当时pc上 Windows Vista 和 OS X Yosemite 的味道,在那之后,Google 也从 Android L(5.0)开始使用了 原质化设计(Material Design) 设计语言,与 Microsoft 的 Metro 那种纯扁平化风格看似很相像,但实则因为引用了 Z轴 的概念,使其有了阴影和立体感,传达了 响应式交互 的设计理念。说到这里有一些跑题,因为笔者对设计美学很感兴趣,所以对这些平台都稍微了解一些皮毛。今天就来研究一下如何在 Android 上实现高斯模糊效果。
目前 Android 上实现高斯模糊效果的方式有:
Java : FastBlur.java ,应用非常广泛的 StackBlur 模糊算法实现代码,效率最低
C++ :两种实现,1:标准高斯模糊算法 2:均值模糊,效率中等
Android : RenderScript ,用来在 Android 上编写高性能代码的一种语言(使用C99标准,运行时机器再次优化编译, 可以均衡的运行在多个CPU 和 GPU上,有一个半径限制小于25的限制),效率最高
简单聊聊 FastBlur
因为效果的实现是基于 Java 的,所以有必要先来了解一下方法如何使用。
public static Bitmap doBlur(Bitmap sentBitmap, int radius, boolean canReuseInBitmap)
可以看出,使用方法非常简单,传入待虚化的bitmap、虚化程度(一般为8)、是否重用flag,最后返回模糊后的bitmap。
但如果直接把一张大图传入进行虚化,很容易就会产生OOM内存溢出,那就意味着我只能虚化小图,这样才能防止内存溢出。但是我并不想换其他图,那么,我们就应该把这张图缩小。
平时我们对图片缩小,必然会带来很明显的清晰度的损失,但高斯模糊本身的目的就是要实现模糊的效果,因此实际上的效果差别不大,几乎可以忽略。
同时由于图片缩小后再进行模糊处理,需要处理的像素点和半径都变小,从而使得模糊处理速度加快。
ReScale
public static Bitmap createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter) {}
我们可以利用Bitmap的 createScaledBitmap()
方法来进行bitmap的缩放。其中前三个参数很明显,其中宽高我们可以选择为原图尺寸的1/5;第四个filter是指缩放的效果,filter为true则会得到一个边缘平滑的bitmap,反之,则会得到边缘锯齿、pixelrelated的bitmap。这里我们要对缩放的图片进行虚化,所以无所谓边缘效果,filter=false。
所以,我们要使用
int scaleRatio = 5;// 缩放比例 此处代表1/5
int blurRadius = 8;// 虚化程度
Bitmap scaledBitmap = Bitmap.createScaledBitmap(originBitmap,
originBitmap.getWidth() / scaleRatio,
originBitmap.getHeight() / scaleRatio,
false);
Bitmap blurBitmap = FastBlur.doBlur(scaledBitmap, blurRadius, true);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setImageBitmap(blurBitmap);
可以得到如下效果:
从图中可以看出,首先可以确定思路是对的;然后,可以看出毛玻璃效果还不是特别的明显。为了得到如iOS那样的虚化效果,我们有两种方法:
- 增大scaleRatio缩放比,使用更小的bitmap去虚化可以得到更好的模糊效果,而且有利于占用内存的减小;
- 增大blurRadius,可以得到更高程度的虚化,不过会导致CPU更加intensive
这里笔者通过增大缩放比来实验。
- scaleRatio = 10
- scaleRatio = 20
通过上面对比图我们可以找出最适合自己的虚化效果。
再来聊聊 RenderScript
RenderScript 主要 在Android中的对图形进行处理,RenderScript 采用C99语法进行编写,主要优势在于性能较高。在 API 11 的时候被加入到 Android 中。同时,Google提供了
android.support.v8.renderscript
兼容包,能够实现更低版本的兼容。
RenderScript 提供了一个用于实现高斯模糊的封装类 ScriptIntrinsicBlur ,因为在 API 17 后才正式适配到 Android ,所以在不使用兼容包的情况下只能兼容到4.2的设备。但是,我们有兼容包啊向下兼容不是梦。
准备阶段
引入兼容包
方法很简单,只需在build.gradle中加入:
defaultConfig {
....
// 就是这么简单
renderscriptTargetApi 19
renderscriptSupportModeEnabled true
}
另外由于一些厂商会深度定制Android系统,所以一些必要的依赖文件会被他们直接去掉,这导致一些型号的设备上调用RenderScriptd
的部分方法时会报错。遇到这种兼容问题的话,需要加上这些可能丢失的文件。
其实也简单,打开android_sdk/build-tools/选择19以上版本/renderscript/lib/packaged
我们可以看见3个包含.so文件的文件夹。
直接复制这三个文件加到项目工程的 jniLibs 包下,没有的话去建一个。
如果首次创建 jniLibs 文件夹,还需要在 build.gradle 的 android{}
中加入:
sourceSets {
main {
jniLibs.srcDirs = ['jniLibs']
}
}
针对使用的混淆的同学,需要在混淆中加入:
-keep class android.support.v8.renderscript.** { *; }
实现高斯模糊
- 将核心实现方法 ScriptIntrinsicBlur 封装成工具类。
import android.support.v8.renderscript.*; // 需要导入v8包,否则无法向下兼容
public class BlurBitmapUtil {
/***
* 图片缩放比例 (例如 1/10)
*/
private static int scaleRatio = 10;
/**
* 对图片进行高斯模糊
*
* @param context 上下文对象
* @param image 需要模糊的图片
* @param blurRadius 模糊半径,由于性能限制,这个值的取值区间为(0至25f)
* @return 模糊处理后的图片
*/
public static Bitmap blurBitmap(Context context, Bitmap image, @FloatRange(from = 1, to = 25)
float blurRadius) {
// 计算图片缩小后的长宽
int width = Math.round(image.getWidth() / scaleRatio);
int height = Math.round(image.getHeight() / scaleRatio);
// 创建一张缩小后的图片做为渲染的图片
Bitmap bitmap = Bitmap.createScaledBitmap(image, width, height, false);
// 创建RenderScript内核对象
RenderScript rs = RenderScript.create(context);
// 创建一个模糊效果的RenderScript的工具对象,第二个参数Element相当于一种像素处理的算法,高斯模糊的话用这个就好
ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
// 由于RenderScript并没有使用VM来分配内存,所以需要使用Allocation类来创建和分配内存空间
// 创建Allocation对象的时候其实内存是空的,需要使用copyTo()将数据填充进去
Allocation input = Allocation.createFromBitmap(rs, bitmap);
// 创建相同类型的Allocation对象用来输出
Type type = input.getType();
Allocation output = Allocation.createTyped(rs, type);
// 设置渲染的模糊程度, 25f是最大模糊度
blurScript.setRadius(blurRadius);
// 设置blurScript对象的输入内存
blurScript.setInput(input);
// 将输出数据保存到输出内存中
blurScript.forEach(output);
// 将数据填充到bitmap中
output.copyTo(bitmap);
// 销毁它们释放内存
input.destroy();
output.destroy();
blurScript.destroy();
rs.destroy();
type.destroy();
return bitmap;
}
使用 RenderScript 增加虚化程度的方法和 FastBlur 一样,有两种方法:
增大scaleRatio缩放比,使用更小的bitmap去虚化可以得到更好的模糊效果,而且有利于占用内存的减小;
增大blurRadius,可以得到更高程度的虚化,不过会导致虚化时间变长
但因为 RenderScript 的天然优势(低级语言, 运行时机器再次优化编译, 可以均衡的运行在多个CPU 和 GPU上),所以这里笔者通过增大虚化程度来实验,缩放比例为 1/10,实际运用时可以根据需求在对虚化程度和缩放比例上采取一个合适的数值。
- blurRadius = 5
- blurRadius = 15
- blurRadius = 25
通过上面对比图我们可以找出最适合自己的虚化效果。
目前来看,为何 Google 设置这个25的限制, 原因应该有两个 :
- 半径大于25的话耗时就成为了一个瓶颈;
- 如果想实现大于25的模糊效果,可以通过缩小原图,模糊,再放大来达到同样的效果
总结
以上就是如何用 FastBlur 和 RenderScript 在 Android 上实现和 iOS 一样的高斯模糊效果的简单介绍,虽然在性能上毋庸置疑是 RenderScript 上最好,但是在一些使用场景上 FastBlur 耗时会更短,所以我们各取所需,根据实际需求去选择使用。
另一种可能性
上面说的2种解决方案都是从性能和效率出发的产物,但如果我的需求就是从网络上加载一张图片(比如头像),然后再高斯模糊化当背景,走一遍转换成bitma再将其转换成高斯模糊的流程或许会有一点点麻烦,这里我再提供一种简单快捷的解决方案 —— 基于Glid加载框架去实现一键 加载网络图片→高斯模糊化→展示。
引入兼容包
首先在build.gradle中加入图片框架需要的库和图片工具库:
defaultConfig {
....
compile 'com.yutianran.maven:super-adapter:1.0.0'
compile 'jp.wasabeef:glide-transformations:2.0.2'
}
然后就开写,一行代码即可
Glide.with(this).load(url).bitmapTransform(new BlurTransformation(this,25)).into(imageView);
需要的参数很分别是
- 上下文对象
- 图片url
- 上下文对象,虚化数值
- imageView控件
效果如上,可以看出 glide-transformations 库的虚化效果也是十分不错的,但对图片本身做的缩放应该不是很多,所以在加载速度上会弱于 FastBlur 和 RenderScript ,但作为轻量级图片而言足够了。
Code
相关代码已上传至Github:BlurView,欢迎Star,Fork。