查看原文
其他

Android高斯模糊你所不知道的坑

mandypig 鸿洋 2019-04-05

本文作者


作者:mandypig

链接:

https://www.jianshu.com/p/d29841b1a4d5

本文由作者授权发布。


如果你想了解如何封装一个高斯模糊,可以参考之前的文章:

如何封装个好用的高斯模糊组件


本文作者给大家分享了一个自己在制作高斯模糊效果时,遇到的问题以及分析的思路。


高斯模糊的UI效果相信大家多多少少都有接触过,只是可能并没有真正在实际项目中去使用过,这次产品需求中正好涉及到了高斯模糊的展示效果,随便去研究了下这块东西,先上一个最终的效果图。



实际真机效果还是流畅的,录屏出来有点卡顿的感觉。


从效果还是可以看出,当点击签到按钮的时候,会伴随签到页面和分享按钮的自下向上的弹出效果,这个动画直接使用属性动画就能实现没什么好说的,最主要的就是后面的高斯模糊效果,仔细看的话可以发现是有一个从清晰逐渐模糊的渐变效果,这个不是本文重点,这里想说的是在实现自定义高斯模糊效果的时候自己所遇到的一个坑。


1基础知识


实际上高斯模糊效果本身是没什么好说的,只要你去网上搜索一下相信能找到不少相关的实现,其中对于android开发来说最主要的实现方法就是使用renderscript方法去实现,自己实现模糊效果的时候也是参考这些代码来完成。


写这篇文章主要不是来说高斯模糊是如何实现,而是想把自己在实现这个高斯模糊效果中遇到的一个大坑和大家分享下,这个坑大家不一定遇到到,尤其是从github直接找一个现成的高斯模糊控件,通过看源码几乎不可能发现这个大坑,只有真正去实现过这个效果的人才有可能发现这个问题,很明显自己就属于后者,还正巧踩到了这个坑上,这里就说下自己踩坑埋坑的心路历程。


高斯模糊实现思路


还是简单说下高斯模糊实现的简单思路,就是得到原图bitmap然后通过renderscript处理获取到模糊后的bitmap,自定义的view在ondraw中去draw该bitmap。思路就是这么简单,这里说下需要注意的几个点。


(1)如何得到模糊后的bitmap,这个就是通过renderscript来实现的,模板代码如下:


//创建一个缩小后的bitmap
Bitmap inputBitmap = Bitmap.createScaledBitmap(image, width, height, false);
//创建将在ondraw中使用到的经过模糊处理后的bitmap
Bitmap outputBitmap = Bitmap.createBitmap(inputBitmap);

//创建RenderScript,ScriptIntrinsicBlur固定写法
RenderScript rs = RenderScript.create(context);
ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));

//根据inputBitmap,outputBitmap分别分配内存
Allocation tmpIn = Allocation.createFromBitmap(rs, inputBitmap);
Allocation tmpOut = Allocation.createFromBitmap(rs, outputBitmap);

//设置模糊半径取值0-25之间,不同半径得到的模糊效果不同
blurScript.setRadius(15);
blurScript.setInput(tmpIn);
blurScript.forEach(tmpOut);

//得到最终的模糊bitmap
tmpOut.copyTo(outputBitmap);


高斯模糊的核心代码差不多都是这样大同小异,上述代码需要注意一下的有三个地方:


1. blurScript.setRadius参数取值在0-25之前,超过这个范围程序会崩溃掉

2. RenderScript,ScriptIntrinsicBlur,Allocation这些类需要使用v8中的类,所以需要在你的build.gradle加入以下配置


defaultConfig {
    minSdkVersion 15
    targetSdkVersion 27
    versionCode 1
    versionName "1.0"

    renderscriptTargetApi 19
    renderscriptSupportModeEnabled true
}


3 巧妙得到“原图”,高斯模糊就是通过对原图处理得到一张模糊图,但是这里所说的原图并不是真正意义上的原图,听着有点拗口.


举个简单的列子,比如你想对一个500x500大小的imageview进行高斯模糊处理,得到该bitmap后还需要对该原图进一步压缩得到一张例如200x200大小的图.


为什么要这么做呢,其实最主要的原因还是因为高斯模糊处理过程本身就比较消耗性能,所以说理论上你处理的原图分辨率越小那么消耗时间也就越少,对原图进行压缩处理,主要就是考虑到性能上的问题,尤其是当你的app需要支持动态高斯模糊效果的时候,需要不停的去得到模糊的bitmap,如果每次的处理量都很大就会造成页面卡顿的问题,所以使用压缩后的原图优势显而易见,另一个原因就是高斯模糊得到的最终bitmap就是模糊的效果,原图压缩失真对最后的结果并没有太大的影响。


明白了上面的道理那么高斯模糊的模板代码你也就掌握了。


2处理imageview模糊效果


先来看下具体效果,如图原图如下




使用上述代码处理后可以得到如下效果




看起来效果还是不错的吧,修改 blurScript.setRadius(15);的参数可以得到不同模糊效果的展示,关于原图的bitmap如何得到可以直接通过view.draw方法得到,简单粗暴,如果你的高斯模糊只是用来处理imageview就可以的话那么关于我说的那个坑你是不会遇到的。


3遇坑:处理整个屏幕的模糊效果


这才是可能会遇到坑的地方,如果你想模糊的是整个屏幕,就如我文章开头的那种效果,那么这个坑会弄得你晕头转向,至少我开始看到呈现出来的效果时真的就是一脸懵逼,先来看一下没有模糊时的布局demo



布局代码就不贴了,有点长,花点时间完全可以弄一个类似的出来,这里说一下我对该布局模糊的整体思路,首先得到布局文件中的根布局main,这里使用的是一个 Framelayout,通过调用Framelayout的draw方法得到bitmap,对该bitmap压缩后得到二次原图,最后通过renderscript处理得到模糊的bitmap并在ondraw方法中绘制出来。


根据以上思路我预期得到的效果图应该长这样才对



然而实际效果却是这样的



看起来似乎一样,但是仔细对比一下就能发现上面的那张图片似乎只是经过了半模糊的处理,边缘部分还是清晰可见,这显然不是我想要的效果。


4填坑过程


为什么会造成这种问题,当你模糊一个imageview的时候效果非常完美,而当你去模糊一个viewgroup的时候就出现上述诡异问题,起初是怀疑和图片有关,于是换了一张其他图片发现结果还是这样。


然后排查代码检查代码逻辑是否有问题,这是一个比较漫长的过程把可能引起问题的代码注释重新运行,最后折腾了半天发现并没有任何卵用。


图片没问题,代码看起来也似乎没有问题那到底是哪里出了问题,无奈只好网上搜相关问题,只能说网络这么大可惜没有我想要的答案。


不过从网上的一些文章也算间接找到可能解决的方案,去github上找一个高斯模糊的控件,将该控件应用到我的布局上去,然后奇迹发生了,居然没有发生边缘清晰的异常,这只能说明是我自己的代码有问题才会导致这个问题,剩下的问题就是排查到底是什么代码引起的这个诡异问题。


这里说一句题外话为什么不直接使用github上的开源库,而是自己重新造轮子。


第一github上的开源库不能完全满足需求即使使用也要进行二次开发,耗费的时间成本可能比自己写还要多。


第二高斯模糊本身模板代码不复杂完全可以自己自定义。


第三很多人都喜欢说不要重复造轮子,但我想说的是你也要有这个本事去造这个轮子,更多的时候给你这个时间你都不一定有本事造成这个轮子才是主要原因,很多人只不过是个代码的搬运工而已,github有个类似效果就直接拿到用也不去研究下如何实现。


回到问题本身,又是半天的摸索,经过大量对比终于将可疑点定位到了一个关键的地方,我自己代码中是通过得到你要模糊的view,然后调用该view的draw方法得到bitmap,而github上的高斯模糊控件是通过decorview的draw方法得到bitmap,当我将自己的高斯模糊控件对应逻辑改成decorview之后,整个世界都清净了,我得到了和github一样的高斯模糊效果,那一刻可以说困扰了我两天的问题终于得到了解决,这种成就感不是单单使用一个开源库所能得到的。


为什么??


开心没有多久,一个巨大的疑问又让我感到郁闷,为什么使用decorview就没问题,而使用其他viewgroup就会出问题,可能猜测到的原因就是decorview内部做了什么操作,但是做了什么操作,面对这么多的代码可以说真的就是无从下手去找,猜测的第一个原因是不是和硬件加速有关,测试后发现并没有用处,继续懵逼中......,换几张图片看看能不能发现一点蛛丝马迹,以下效果引起了我注意



上述效果是我调用布局中的根view的draw方法得到的模糊效果,可以发现一个奇怪的地方,似乎箭头模糊后原图悬浮到了模糊效果之上,为了验证猜测我在自定义高斯模糊的ondraw方法中绘制了一个白色的背景,如果原图悬浮在高斯模糊自定义控件之上的话那么将会出现白色背景不能覆盖原图的现象,反之白色背景将会遮盖掉原图。


运行看了下效果,真可谓无心插柳柳成荫,运行后的效果居然变成了:



这不正是我想要的最终效果吗!!


在给自定义高斯模糊控件设置一个白色背景居然就解决了困扰我两天的难题,接下来就是思考为什么这种方法是可行的,一个非常合理的解释就是如图所示:



原来的布局中红框圈起来的部分是没有背景色的,高斯模糊在处理模糊效果的时候将图中圆圈内部边缘的颜色值和圆圈外部的透明色进行处理最终得到的颜色值不变,这样就产生了一种边缘清晰可见的视觉效果,如图所示:




到此才可以说算是找到了导致我自定义高斯模糊效果异常的真正原因。


decorview什么时候设置背景色


最后一个困扰我的问题,为什么调用decorview的draw方法就没问题,经过上述我总结的原因,唯一一个解释就是decorview默认自带一个背景色!!


那么这个背景色是在什么时候设置的呢,只能在源码里面去找答案了,这次就不再是大海捞针的看源码了,在phonewindow--》installDecor--》generateLayout方法中最终找到了我想要的答案:


TypedArray a = getWindowStyle();
...
if (mBackgroundResource == 0) {
        mBackgroundResource = a.getResourceId(
                R.styleable.Window_windowBackground, 0);
    }
...
final Drawable background;
if (mBackgroundResource != 0) {
    background = getContext().getDrawable(mBackgroundResource);
else {
    background = mBackgroundDrawable;
}
mDecor.setWindowBackground(background);


关键代码都已经给出,可以看到最终decorview调用了setWindowBackground设置了一个背景色!!,到此所有的疑问都得到了非常完美的解答。


总结


总结下自定义高斯模糊所遇到的问题,由于viewgroup可能存在没有默认背景色的缘故,导致会发现模糊后的效果比较诡异的情况,而decorview默认在生成的时候就自带背景色所以使用decorview来实现高斯模糊不会出现问题,文章重点不是在说如何实现高斯模糊效果,而是解决问题的一个思路。




PS:有不少同学,通过这篇拥抱Android开发的变化,才能拥抱未来文章购买了绍文的课程,已经更新2讲了,记得学习和实践 github 上的课后作业,我会不定期提醒的。


推荐阅读

安卓6.0到9.0适配

非 UI 线程能调用 View.invalidate()?

Android关于Color你所知道的和不知道的一切


扫一扫 关注我的公众号

如果你想要跟大家分享你的文章,欢迎投稿~


┏(^0^)┛明天见!


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存