在 Android 上也能实现毛玻璃效果?性能如何?本文介绍Android上常用的一个图片处理的技巧点。
在 iOS 设备上我们随处可见毛玻璃效果,而且最近越来越多的场合应用到了这种美观的虚化效果,包括本人的一个开源项目 BlureImageView 也是受此启发。所以,恰到好处的虚化效果能很好的改善用户体验,而且也能让你的 app 显得更加优雅。
不过,我们目前在 android 上很少见到毛玻璃效果,我认为很重要的原因是性能问题,虚化一张图片所需要的时间会因设备而异,如果为了虚化使得用户需要刻意等待,那么就是弊大于利。另外,Google 官方提供的 renderScript 一般只是做一些小幅度的虚化,很难达到毛玻璃这类深度虚化效果。
所以本文的角度是能够在 android 设备上快速实现毛玻璃效果。
首先,为了实现毛玻璃效果,本文采用的是 StackBlur
模糊算法,这种算法应用非常广泛,能得到非常良好的毛玻璃效果。在这里,我们使用的是它的 Java 实现代码FastBlur.java。
public static Bitmap doBlur(Bitmap sentBitmap, int radius, boolean canReuseInBitmap)
可以看出,使用方法非常简单,传入待虚化的 bitmap、虚化程序(一般为 8)、和是否重用 flag。
然后,如果要对上面这张图片进行虚化,我们可以通过把它转化成 bitmap 传入虚化,看起来很简单就解决了,但事实并非如此。
如果直接把一张大图传入,很容易就会发生 OOM 内存溢出
03-11 21:02:02.014 16727-16742/com.wingjay.jayandroid I/art: Clamp target GC heap from 109MB to 96MB
03-11 21:02:02.026 16727-16727/com.wingjay.jayandroid I/art: Clamp target GC heap from 109MB to 96MB
03-11 21:02:02.030 16727-16727/com.wingjay.jayandroid I/art: Clamp target GC heap from 109MB to 96MB
03-11 21:02:02.031 16727-16727/com.wingjay.jayandroid I/art: Forcing collection of SoftReferences for 30MB allocation
03-11 21:02:02.035 16727-16727/com.wingjay.jayandroid I/art: Clamp target GC heap from 109MB to 96MB
03-11 21:02:02.036 16727-16727/com.wingjay.jayandroid E/art: Throwing OutOfMemoryError "Failed to allocate a 32175012 byte allocation with 2648672 free bytes and 2MB until OOM"
03-11 21:02:02.036 16727-16727/com.wingjay.jayandroid D/AndroidRuntime: Shutting down VM
这是我直接对原图进行虚化得到的 log 信息。可以看出当虚化开始时,虚拟机开始不断进行内存回收,包括把所有软引用的内存回收。然而,仍然导致了内存溢出。
那就意味着我只能虚化小图,这样才能防止内存溢出。但是我并不想换其他图,那么,我们就应该把这张图 缩放
。
public static Bitmap createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter) {}
我们可以利用这个 function 来进行 bitmap 的缩放。其中前三个参数很明显,其中宽高我们可以选择为原图尺寸的 1/10;第四个 filter 是指缩放的效果,filter 为 true 则会得到一个边缘平滑的 bitmap,反之,则会得到边缘锯齿、pixelrelated 的 bitmap。这里我们要对缩放的图片进行虚化,所以无所谓边缘效果,filter=false
。
所以,我们要使用
int scaleRatio = 10;
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 那样的虚化效果,我们有两种方法:
这里本人通过 增大缩放比
来实验。
通过上面对比图我们可以找出最适合自己的虚化效果。
那么,要实现这样的效果,是否具有损害用户体验的风险呢?下面,我们从 消耗时间
和占据内存
的角度来进行分析。
为了分析虚化一张图片所消耗的时间,本文通过同时虚化 100
来获取平均消耗时间。以期对虚化耗时和不同缩放比对耗时的影响得到一定的认识。
long start = System.currentTimeMillis();
Bitmap scaledBitmap, blurBitmap;
int scaleRatio = 10;
int loopCount = 100
for (int i=0; i<loopCount; i++) {
scaledBitmap = Bitmap.createScaledBitmap(originBitmap,
originBitmap.getWidth() / scaleRatio,
originBitmap.getHeight() / scaleRatio,
false);
blurBitmap = FastBlur.doBlur(scaledBitmap, 8, true);
}
Log.i("blurtime", String.valueOf(System.currentTimeMillis() - start));
887ms
,平均耗时8.87ms
;224ms
,平均耗时2.24ms
;99ms
,平均耗时0.99ms
;55ms
,平均耗时0.55ms
;29ms
,平均耗时0.29ms
;为了方便读者了解效果,我通过多组数据拟合了下面的曲线:
从该模拟图可以看出时间随着缩放比的增大而不断减小,当缩放比达到 30 以上时所消耗的时间不到 1ms,因此,我认为应该是完全不会产生时延破坏用户体验的。
既然时间没问题,那么,主要问题:内存占用就来了,所以我们需要考察生成一张虚化图片所占用的内存。
为了测试对一张图片进行虚化所占用内存的变化,我们改变虚化次数,即修改上面的 loopCount
并观察对内存的变化。其中 scaleRatio = 10,以获得相对较大的内存消耗。
从上面的内存消耗图,可以看出虚化的确会占用一定内存,如果大量的虚化同时发生,则会由于 UI 线程突然加载很多 bitmap 而导致内存抖动。
希望大家如果有其他测试方法或者意见多多留言,从而继续改进性能。
附上本文所采用的
Github: https://github.com/wingjay
个人博客 : http://wingjay.com
原创推荐
技术 - 思维 - 感悟
END