Android 上的 高斯模糊 依我之见

前言

iOS 7 开始 Apple 从 拟物化 过渡到了 扁平化 的设计风格,同时也搭配使用了 毛玻璃风格 当做背景效果,不得不说十分惊艳,颇有当时pc上 Windows VistaOS 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文件的文件夹。

so文件

直接复制这三个文件加到项目工程的 jniLibs 包下,没有的话去建一个。

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的限制, 原因应该有两个 :

  1. 半径大于25的话耗时就成为了一个瓶颈;
  2. 如果想实现大于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。

参考文献

  1. [译] RenderScript:简单而快速的图像处理
  2. 图片高斯模糊效果简单优化
  3. 高斯模糊效果实现方案及性能对比
  4. Android:简单靠谱的动态高斯模糊效果
  5. 教你一分钟实现动态模糊效果
  6. 什么?IOS的专利?Android也能流畅实现毛玻璃效果(高斯模糊)效果
  7. Android 高斯模糊总结
  8. Android RenderScript 简单高效实现图片的高斯模糊效果
  9. [干货]一种快速毛玻璃虚化效果实现--Android
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容