Android 图片高斯模糊解决方案

近年来,图片高斯模糊备受设计师的青睐,在各大知名APP中,如微信、手机QQ、网易云音乐等等都有对背景高斯图模糊的设计,在Adnroid 中,现在常用的图片高斯模糊技术有三种:RenderScript 、fastBlur对RenderScript和fastBlur的优化,接下来分别分析各自的优缺点和在项目中该使用哪个解决方案。先上一张效果图:

高斯模糊效果图.png

1,RenderScript

RenderScript是在Android上的高性能运行密集型运算的框架,RenderScript主要用于数据并行计算,尤其对图像处理、摄影分析和计算机视觉特别有用。RenderScript是在Android3.0(API 11)引入的。而Android图片高斯模糊处理,通常也是用这个库来完成。它提供了我们Java层调用的API,实际上是在c/c++ 层来处理的,所以它的效率和性能通常是最高的。要使用RenderScript完成图片高斯模糊只需要以下几步:
(1) 初始化一个RenderScript Context:RenderScript 上下文环境通过create(Context)方法来创建,它保证RenderScript的使用并且提供一个控制后续所有RenderScript对象(如:ScriptIntrinsicBlur、Allocation等)生命周期的对象。

(2)通过Script至少创建一个Allocation:一个Allocation是提供存储大量可变数据的RenderScript 对象。在内核中,Allocation作为输入和输出,在内核中通过rsGetElementAt_type ()rsSetElementAt_type()方法来访问Allocation当script全局绑定的时候。使用createFromBitmap 和createTyped来创建Allocation。

(3)创建ScriptIntrinsic:它内置了RenderScript 的一些通用操作,如高斯模糊、扭曲变换、图像混合等等,更多的操作请看ScriptIntrinsic的子类,本文要用的高斯模糊处理就是用的它的子类ScriptIntrinsicBlur
(4)填充数据到Allocations:除了使用方法createFromBitmap创建的Allocation外,其它的第一次创建时都是填充的空数据。

(5)** 设置模糊半径**:设置一个模糊的半径,其值为 0-25。

(6) 启动内核,调用方法处理:调用forEach 方法模糊处理。

(7) ** 从Allocation 中拷贝数据**:为了能在Java层访问Allocation的数据,用Allocation其中一个copy方法来拷贝数据。
(8) 销毁RenderScript对象:可以用destroy方法来销毁RenderScript对象或者让它可以被垃圾回收,destroy 之后,就能在用它控制的RenderScript对象了(比如在销毁了之后,再调用ScriptIntrinsic或者Allocation的方法是要抛异常的)。

以上几个步骤就可以完成的图片的高斯模糊,看一下对应的代码:

private static Bitmap rsBlur(Context context,Bitmap source,int radius){Bitmap inputBmp = source;//(1)RenderScript renderScript =  RenderScript.create(context);Log.i(TAG,"scale size:"+inputBmp.getWidth()+"*"+inputBmp.getHeight());// Allocate memory for Renderscript to work with//(2)final Allocation input = Allocation.createFromBitmap(renderScript,inputBmp);final Allocation output = Allocation.createTyped(renderScript,input.getType());//(3)// Load up an instance of the specific script that we want to use.ScriptIntrinsicBlur scriptIntrinsicBlur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript));//(4)scriptIntrinsicBlur.setInput(input);//(5)// Set the blur radiusscriptIntrinsicBlur.setRadius(radius);//(6)// Start the ScriptIntrinisicBlurscriptIntrinsicBlur.forEach(output);//(7)// Copy the output to the blurred bitmapoutput.copyTo(inputBmp);//(8)renderScript.destroy();return inputBmp;}

上面对应的步骤已经用序号标出,代码就十行左右,很简单。这就十Android提供给我们的可以处理图片高斯模糊的库。性能比较好,因为是在c/c++层做的处理。但是它只能在API 17或者更高的版本使用, 看一下文档的说明:

RC——API.png

如上图,红框中标记的ScriptIntrinsicBlur 是在API 17加入的,因此低版本的手机是用不了,为了能兼容低版本的手机,我们还得探索其他方案。

RenderScript 兼容包:
所幸的是,Google 为了兼容低版本也可以用RenderScript,加了一个兼容包,android.support.v8.renderscript ,使用support.v8.renderscript就能兼容到Android 2.3版本(API 9),现在市面上估计没有比2.3版本还低的手机了(4.x 的手机都不多了)。使用兼容包和使用原生的RenderScript完全一样,代码还是上面的代码。只是需要在app 的build.gradle添加如下的代码

android {compileSdkVersion 23buildToolsVersion "23.0.3"defaultConfig {minSdkVersion 9targetSdkVersion 19// 使用support.v8.renderscriptrenderscriptTargetApi 18renderscriptSupportModeEnabled true}
}

只要添加上面的2行代码就行了。但是有2点需要注意:

注意:
1,Android SDK Tools revision 22.2 or higher(Tools 需要22.2或者更高的版本)
2,Android SDK Build-tools revision 18.1.0 or higher( Build-tools 需要18.1.0或者更高的版本)
如果没有达到的话,通过Anroid SDK Manager 更新安装。

有了兼容包,那么RenderScript就是一个完美的解决方案了吗?答案是NO,还有2个缺点:

  • 虽然RenderScript效率不错,但是处理尺寸大一点的图片还是达不到16ms每一帧,需要优化
  • 虽然兼容包能解决API17以下不能使用的问题,但是引入兼容包又带来了新的问题,APK 的包大小增大了,support.v8.renderscript有160k,现在各家的APP都在要求APK瘦身,对于那种本来就很大的APK来说还是不能接受的。

因此我们还要找一下其他方案,接下来看一下fastBlur算法

2,fastBlur

fastBlur 是除了RenderScript 之外的另一种方法,它直接在Java层做图片的模糊处理。对每个像素点应用高斯模糊计算、最后在合成Bitmap。请看源码:

 /*** Stack Blur v1.0 from* http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html* Java Author: Mario Klingemann <mario at quasimondo.com>* http://incubator.quasimondo.com** created Feburary 29, 2004* Android port : Yahel Bouaziz <yahel at kayenko.com>* http://www.kayenko.com* ported april 5th, 2012** This is a compromise between Gaussian Blur and Box blur* It creates much better looking blurs than Box Blur, but is* 7x faster than my Gaussian Blur implementation.** I called it Stack Blur because this describes best how this* filter works internally: it creates a kind of moving stack* of colors whilst scanning through the image. Thereby it* just has to add one new block of color to the right side* of the stack and remove the leftmost color. The remaining* colors on the topmost layer of the stack are either added on* or reduced by one, depending on if they are on the right or* on the left side of the stack.** If you are using this algorithm in your code please add* the following line:* Stack Blur Algorithm by Mario Klingemann <mario@quasimondo.com>*/private static Bitmap fastBlur(Bitmap sentBitmap, float scale, int radius) {int width = Math.round(sentBitmap.getWidth() * scale);int height = Math.round(sentBitmap.getHeight() * scale);sentBitmap = Bitmap.createScaledBitmap(sentBitmap, width, height, false);Bitmap bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);if (radius < 1) {return (null);}int w = bitmap.getWidth();int h = bitmap.getHeight();int[] pix = new int[w * h];Log.e("pix", w + " " + h + " " + pix.length);bitmap.getPixels(pix, 0, w, 0, 0, w, h);int wm = w - 1;int hm = h - 1;int wh = w * h;int div = radius + radius + 1;int r[] = new int[wh];int g[] = new int[wh];int b[] = new int[wh];int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;int vmin[] = new int[Math.max(w, h)];int divsum = (div + 1) >> 1;divsum *= divsum;int dv[] = new int[256 * divsum];for (i = 0; i < 256 * divsum; i++) {dv[i] = (i / divsum);}yw = yi = 0;int[][] stack = new int[div][3];int stackpointer;int stackstart;int[] sir;int rbs;int r1 = radius + 1;int routsum, goutsum, boutsum;int rinsum, ginsum, binsum;for (y = 0; y < h; y++) {rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;for (i = -radius; i <= radius; i++) {p = pix[yi + Math.min(wm, Math.max(i, 0))];sir = stack[i + radius];sir[0] = (p & 0xff0000) >> 16;sir[1] = (p & 0x00ff00) >> 8;sir[2] = (p & 0x0000ff);rbs = r1 - Math.abs(i);rsum += sir[0] * rbs;gsum += sir[1] * rbs;bsum += sir[2] * rbs;if (i > 0) {rinsum += sir[0];ginsum += sir[1];binsum += sir[2];} else {routsum += sir[0];goutsum += sir[1];boutsum += sir[2];}}stackpointer = radius;for (x = 0; x < w; x++) {r[yi] = dv[rsum];g[yi] = dv[gsum];b[yi] = dv[bsum];rsum -= routsum;gsum -= goutsum;bsum -= boutsum;stackstart = stackpointer - radius + div;sir = stack[stackstart % div];routsum -= sir[0];goutsum -= sir[1];boutsum -= sir[2];if (y == 0) {vmin[x] = Math.min(x + radius + 1, wm);}p = pix[yw + vmin[x]];sir[0] = (p & 0xff0000) >> 16;sir[1] = (p & 0x00ff00) >> 8;sir[2] = (p & 0x0000ff);rinsum += sir[0];ginsum += sir[1];binsum += sir[2];rsum += rinsum;gsum += ginsum;bsum += binsum;stackpointer = (stackpointer + 1) % div;sir = stack[(stackpointer) % div];routsum += sir[0];goutsum += sir[1];boutsum += sir[2];rinsum -= sir[0];ginsum -= sir[1];binsum -= sir[2];yi++;}yw += w;}for (x = 0; x < w; x++) {rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;yp = -radius * w;for (i = -radius; i <= radius; i++) {yi = Math.max(0, yp) + x;sir = stack[i + radius];sir[0] = r[yi];sir[1] = g[yi];sir[2] = b[yi];rbs = r1 - Math.abs(i);rsum += r[yi] * rbs;gsum += g[yi] * rbs;bsum += b[yi] * rbs;if (i > 0) {rinsum += sir[0];ginsum += sir[1];binsum += sir[2];} else {routsum += sir[0];goutsum += sir[1];boutsum += sir[2];}if (i < hm) {yp += w;}}yi = x;stackpointer = radius;for (y = 0; y < h; y++) {// Preserve alpha channel: ( 0xff000000 & pix[yi] )pix[yi] = ( 0xff000000 & pix[yi] ) | ( dv[rsum] << 16 ) | ( dv[gsum] << 8 ) | dv[bsum];rsum -= routsum;gsum -= goutsum;bsum -= boutsum;stackstart = stackpointer - radius + div;sir = stack[stackstart % div];routsum -= sir[0];goutsum -= sir[1];boutsum -= sir[2];if (x == 0) {vmin[y] = Math.min(y + r1, hm) * w;}p = x + vmin[y];sir[0] = r[p];sir[1] = g[p];sir[2] = b[p];rinsum += sir[0];ginsum += sir[1];binsum += sir[2];rsum += rinsum;gsum += ginsum;bsum += binsum;stackpointer = (stackpointer + 1) % div;sir = stack[stackpointer];routsum += sir[0];goutsum += sir[1];boutsum += sir[2];rinsum -= sir[0];ginsum -= sir[1];binsum -= sir[2];yi += w;}}Log.e("pix", w + " " + h + " " + pix.length);bitmap.setPixels(pix, 0, w, 0, 0, w, h);return (bitmap);}

如上所示,就一个方法,使用这种方式不会有兼容性问题,也不会引入jar包导致APK变大。但是这种方法的效率是非常低的,想想也知道,因为是在Java 层处理,速度当然慢。测试了一张800 x 450 的图片,RenderScript平均25 ms 左右,fastBlur平均310ms 左右,相当于差了10倍。还有就是使用这种方式是把图片全部加载到内存,如果图片较大,容易导致OOM。

3,对RenderScript 和fastBlur 的优化

上面对RenderScript 和fastBlur做了分析,虽然RenderScript的效率要比fastBlur 好很多,但是还是有可能达不到16ms每一帧的要求而导致卡顿。所以需要进行优化。

思路: 在stackOverFlow上有提供优化思路(地址:http://stackoverflow.com/questions/2067955/fast-bitmap-blur-for-android-sdk)
,原理是这样的:通过缩小图片,使其丢失一些像素点,接着进行模糊化处理,然后再放大到原来尺寸。由于图片缩小后再进行模糊处理,需要处理的像素点和半径都变小,从而使得模糊处理速度加快。

因此我们只需要将原来的图片缩小,然后在用RenderScript 或者fastBlur 处理,就可以加快速度了,添加如下代码:

        int width = Math.round(source.getWidth() * scale);int height = Math.round(source.getHeight() * scale);Bitmap inputBmp = Bitmap.createScaledBitmap(source,width,height,false);

renderScript 高斯模糊的完整方法如下:

private static Bitmap rsBlur(Context context,Bitmap source,int radius,float scale){Log.i(TAG,"origin size:"+source.getWidth()+"*"+source.getHeight());int width = Math.round(source.getWidth() * scale);int height = Math.round(source.getHeight() * scale);Bitmap inputBmp = Bitmap.createScaledBitmap(source,width,height,false);RenderScript renderScript =  RenderScript.create(context);Log.i(TAG,"scale size:"+inputBmp.getWidth()+"*"+inputBmp.getHeight());// Allocate memory for Renderscript to work withfinal Allocation input = Allocation.createFromBitmap(renderScript,inputBmp);final Allocation output = Allocation.createTyped(renderScript,input.getType());// Load up an instance of the specific script that we want to use.ScriptIntrinsicBlur scriptIntrinsicBlur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript));scriptIntrinsicBlur.setInput(input);// Set the blur radiusscriptIntrinsicBlur.setRadius(radius);// Start the ScriptIntrinisicBlurscriptIntrinsicBlur.forEach(output);// Copy the output to the blurred bitmapoutput.copyTo(inputBmp);renderScript.destroy();return inputBmp;}

先对Bitmap 缩小,然后再模糊处理。

Note:缩小的系数应该为2的整数次幂 ,即上面代码中的scale应该为1/2、1/4、1/8 ... 参考BitmapFactory.Options 对图片缩放 的inSample系数。据前辈们经验,一般scale = 1/8 为佳。

看一下使用RenderScript和fastBlur 以及优化后,高斯模糊一张图片所花时间的对比表,测试机型为魅族metal,系统为Android 5.1,如下:

模糊时间对比表.png

如上图:以1080 x 1349 的图片为例(每一个半径取5次的均值),使用原尺寸用两种方法进行高斯模糊,RenderScript的效率比fastBlur高,大约快10倍,但是都超过了16ms,而使用优化方法后,使其先缩小8倍,再模糊,2种方法效率都有质的提高,RenderScript模糊时间不足5ms,fastBlur 也接近16ms,半径为15以下小与16ms。

因此不管使用哪种方法模糊图片,都应该先优化,再模糊。

4,优缺点比较及图片高斯模糊方案

RenderScript 优点:

  • 使用简单,原生的API,十行左右的代码就能完成高斯模糊
  • 效率较高,是在c/c++层做处理

RenderScript 缺点:

  • API 17以上才能使用
  • 用兼容包的话,会导致APK 体积增大,support包约160k

fastBlur的优点:

  • 没有兼容版本问题
  • 不用引入三方包,不会增加APK大小

fastBlur的缺点:

  • 效率很低,在Java层做处理
  • 将Bitmap全部加载到内存,较大图片容易OOM

以上对比了2种方法的优缺点,各有优劣,那么我们到底选择哪一种呢?这个需要看情况而定,给出下面2种方案:
高斯模糊方案一: 如果APK本身较小,可以接受增大的160k体积,那么直接使用兼容包的RenderScript (注意需要先优化,用上面的先缩小再模糊)。
高斯模糊方案二:如果不想APK体积增大,那么在模糊的时候做判断, API版本大于17 ,直接使用原生的RenderScript模糊,API版本小于17,则用fastBlur方法。(同样需要先优化,后模糊)

6,轮子

由于高斯模糊在项目中用得比较多,而每一个项目都去拷贝代码,这样很麻烦,并且不优雅,因此,对这两种方法优化后,封装成了一个Lib,要使用时直接添加依赖就行。

添加依赖:
1, 最外层build.gradle 添加一下代码:

allprojects {repositories {jcenter()maven {url "https://jitpack.io"}}
}

2,app 的build.gradle添加:

  dependencies {compile 'com.github.pinguo-zhouwei:EasyBlur:v1.0.0'
}

3,app 的build.gradle添加:

 defaultConfig {applicationId "com.zhouwei.easyblur"minSdkVersion 16targetSdkVersion 25versionCode 1versionName "1.0"testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"//使用renderscript 兼容包 renderscriptTargetApi 25renderscriptSupportModeEnabled true}

使用方法:
1,简单使用,指定Bitmap和半径

Bitmap finalBitmap = EasyBlur.with(MainActivity.this).bitmap(overlay) //要模糊的图片.radius(10)//模糊半径.blur();

2,可以指定缩小的倍数,默认缩小倍数为8

Bitmap finalBitmap = EasyBlur.with(MainActivity.this).bitmap(overlay) //要模糊的图片.radius(10)//模糊半径.scale(4)//指定模糊前缩小的倍数.blur();

3, 指定使用哪一种方法,默认是使用兼容的RenderScript 高斯模糊

Bitmap finalBitmap = EasyBlur.with(MainActivity.this).bitmap(overlay) //要模糊的图片.radius(10)//模糊半径.scale(4)//指定模糊前缩小的倍数.policy(EasyBlur.BlurPolicy.FAST_BLUR)//使用fastBlur.blur();

代码已经上传Github:EasyBlur

参考资料

RenderScript API 指南
android图片处理之图像模糊
高斯模糊实现方案探究
Fast Bitmap Blur For Android SDK

原文地址:https://www.jianshu.com/p/02da487a2f43

Android 图片高斯模糊解决方案相关推荐

  1. android图片系统解决方案-从采集到显示

    概述 Android上图片涉及到的要点: 基础 自定义相机拍照 调用系统相机拍照 图片选择 裁剪 压缩 上传 服务端处理与下载 显示与内存-普通图与超大图 文件夹管理 内置图片管理与包大小 基础 颜色 ...

  2. Android 图片高斯模糊

    1.RenderScript /*** 将传入的图片进行高斯模糊** @param mContext 上下文* @param bitmap 图片对象* @param radius 高斯模糊半径* @r ...

  3. Android 图片相关整理

    目录介绍 0.思考问题及解决方案 1.加载图片的压缩处理技术 2.网络url图片转换Bitmap保存到本地 2.1 直接通过http请求网络图片通过流转化成Bitmap 2.2 使用第三方库glide ...

  4. Android使用高斯模糊实现模糊背景

    Android使用高斯模糊实现模糊背景 引言 最近的开发中实现了一个模糊背景的效果.大概效果是这样的: 实现思路 将View转成Bitmap,并且记录列表的滑动距离,生成bitmap时上移画布(这样是 ...

  5. android 图片裁剪库,(译)uCrop介绍 —— 我们自己的Android图片裁剪库

    我们在 Yalantis 开发了许多不同的 Android 应用,经验告诉我们,几乎在所有的应用中,都需要图片裁剪的功能.图片裁剪的用途很广,从简单的用户头像调整到图片的比例裁剪.灵活变换等各种复杂的 ...

  6. Android 图片压缩、照片选择、裁剪,上传、一整套图片解决方案

    1.Android一整套图片解决方案 http://mp.weixin.qq.com/s?__biz=MzAxMTI4MTkwNQ==&mid=2650820998&idx=1& ...

  7. android怎么处理模糊图片,Android 图片的高斯模糊处理

    一.前言: 我们在开发过程中,经常会遇到高斯模糊的图片,或者高斯模糊背景的情况,所以今天记录一下,下面是效果图: 普通图片.png 高斯模糊图片.png 高斯模糊背景图片.png 二.使用: 1. 依 ...

  8. android图片模糊效果,Android下实现高效的模糊效果

    其实有关 android 下实现图片模糊的文章有很多,大多都是使用 renderscript 内置的 ScriptIntrinsicBlur 来实现的,这篇文章中的例子也不例外,但如果仅仅是调用一下 ...

  9. android 版本图片,Android - 图片处理之Glide4.0版本

    老婆保佑,代码无BUG 前言 一般项目我都会使用Glide作为我的图片加载框架,他和Picasso ,真的很像,郭大神早就分析过了,很详细,这里也就简单做个记录.小白白一枚,学习路上 目录 一:Git ...

最新文章

  1. JavaScript如何获得项目根路径
  2. postgresql创建只读账号
  3. android 相册选择视频和图片格式,Android获取文件类型是图片还是视频
  4. 【ASP.NET Core】处理异常
  5. react native loading动画_React高级进阶指南
  6. 统计学基础学习笔记:正态分布
  7. PHP三种字符串界定符的区别
  8. 新冠肺炎病毒(Covid-19)检测系统
  9. 系统评测指标:准确率(Accuracy)、精确率(Precision)、召回率(Recall)、F-Score
  10. ERP初学者入门自学笔记
  11. SpringBoot的配置文件properties和yml格式
  12. HDS USPV-VSP HP XP24000 Raid崩溃阵列损坏多盘损坏数据恢复方法思路分享
  13. 初秋进补 粥汤大对决
  14. [C++]牛客 WY11 星际穿越
  15. CODEVS 1258 关路灯
  16. php音频对比技术,HIFIDIY论坛-三种音频功放的音质特点比较 供参考 - Powered by Discuz!...
  17. 小岳岳逗乐,林志玲亲临,看科技如何助力十一出游
  18. Java——entity(实体类)的写法规范
  19. Oracle中to_char函数和MySQL中的str_to_date函数区别
  20. java中接口可不可以继承一般类,为什么?

热门文章

  1. Android 来去电自动录音(一)
  2. USB3.0移动硬盘启动Win7的方法(AHCI/AMD USB3.0/Win7)
  3. srm系统的供应管理策略
  4. 第93天学习打卡(Vue 初识Vue)
  5. 数说故事数据赋能车企数字化转型——智能决策支持系统搭建
  6. 相机旋转标题文字出现PR模板MOGRT|摄影师必备
  7. android 仿微信demo————微信启动界面实现
  8. 肿瘤侵袭性和浸润性的区别
  9. “ZAO”在中国会遇到GDPR监管吗?
  10. 零售行业数据分析指标体系及方法论