Android之高斯模糊的记录

来源:互联网 发布:网络语言大大什么意思 编辑:程序博客网 时间:2024/05/21 22:22

最近在开发项目的时候遇到一个UI提出的效果就是PS里面的高斯模糊效果,上图

                                                          

下面其实是一段文本内容,但是当用户没有获取到某种权限的时候,是不能查看具体的文字内容的(但是又给用户一种下面有文字内容的模糊的感觉)。当用户点击偷瞄一下的时候需要获取某种权限,使这个模糊(遮罩)效果消失,显露出真正的文字内容。UI说在PS里面这叫高斯模糊。自己网上看了看,其实在Android里面也有高斯模糊效果的API,在API 11的时候RenderScript,用来进行高效的图片处理。其实所谓的模糊,就是在以我们真实的图片的基础上,对这张照片来进行处理,然后将处理后的照片,放在真实图片上面,给人一种放否能看清又不能看清的感觉,当年的微信发红包看图片应该也是这样实现的吧,只是将我这里的偷瞄一眼改成了需要发红包,其实是异曲同工之妙吧。先来看看RenderScript对高斯模糊的操作方法吧

public static Bitmap blurBitmap(Bitmap bitmap,Context context){                  //Let's create an empty bitmap with the same size of the bitmap we want to blur          Bitmap outBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888);                    //Instantiate a new Renderscript          RenderScript rs = RenderScript.create(context);                    //Create an Intrinsic Blur Script using the Renderscript          ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));                    //Create the Allocations (in/out) with the Renderscript and the in/out bitmaps          Allocation allIn = Allocation.createFromBitmap(rs, bitmap);          Allocation allOut = Allocation.createFromBitmap(rs, outBitmap);                    //Set the radius of the blur          blurScript.setRadius(1.f);   //其实这里就是设置的模糊程度 值越大模糊越厉害 最后可能一团黑 哈哈                  //Perform the Renderscript          blurScript.setInput(allIn);          blurScript.forEach(allOut);                    //Copy the final bitmap created by the out Allocation to the outBitmap          allOut.copyTo(outBitmap);                    //recycle the original bitmap          bitmap.recycle();                    //After finishing everything, we destroy the Renderscript.          rs.destroy();                    return outBitmap;        }  
这里高斯模糊的逻辑其实就是 ,根据你所传入的bitmap,在setRadius(int)设置模糊程度,用RenderScript调用之后 模糊之后 返回模糊之后的图片,再将我们模糊之后的bitmap作为一张照片"盖"在我们所需要模糊(保护)的内容这块区域之上,这样就给人一种神秘的感觉。这是google 给我们提供的api里面有的,在性能更方面应该是比较优的,但是当时没有选择用这种方法,而是选择了github上面的FastBlur方法,至于原因后续再说。但是正如前面所说,RenderScript是api11之后才能使用的,但是低版本怎么办呢,这就需要考虑到兼容性了,还好google也提供了一套方案。下面就兼容低版本的高斯模糊做一些配置问题。其实低版本可以将supportV8中的包拷贝进来。

1.首先去你的sdk的目录下build-tool目录下找到所需要的jar包,我的路径是 H:\android-sdk-windows\build-tools\23.0.3\renderscript\lib,将renderscript-v8.jar拷贝到我们的工程当中libs下面,以后所有的用到renderScript的相关操作的类,都引用这里面的,不要引用系统默认的。例如import android.renderscript.RenderScript;  改为 import android.support.v8.renderscript.RenderScript;

2.使用RenderScript库,在某些手机或Android版本奔溃的问题 ,错误信息:

H:/AndroidRuntime(4476): android.support.v8.renderscript.RSRuntimeException: Error loading RS

jni library: java.lang.UnsatisfiedLinkError: Couldn't load RSSupport: findLibrary returned null

导入官方jar renderscript-v8.jar 报这个错误 android.support.v8.renderscript.RSRuntimeException: Error loadin 或者 java.lang.UnsatisfiedLinkError: Couldn't load RSSupport from loader dalvik.system.PathClassLoader

这个错误原因是因为在4.4以上的手机上自带 librsjni.so和libRSSupport.so 而在4.0以下,或者某些奇葩手机是没有这两个jni 的.所以我将我H:\android-sdk-windows\build-tools\23.0.3\renderscript\lib\packaged 目录下的文件全部拷贝到lib下面对应的文件夹下面 没有对应的文件夹就创建,如果有对应的文件夹就将文件夹中的内容拷贝进去arm64-v8a,armeabi-v7a,mips,x86。

这2步进行了之后 ,我们就可以完全兼容低版本的问题了额。


在进行高斯模糊的时候 ,我们有时候会遇到这样一种需求,就是当我们的界面(Activity)显示完成之后,我们希望我们模糊效果就能出来,我上面的渲染bitmap的函数中因为函数中用到bitmap.getWidth(),getHeight()获得其宽高,那我们应该在什么时候去调用我们的这个方法 使其能够正常运行呢?在onStart()方法中?还是在onResume()回调方法中?呵呵,其实都不是。实际中做实验的时候,你可去尝试,这个两个函数中是获取不到其宽高的呢,那么下面有几种方法可以使用 ,都是我经过尝试使用之后,能够运行的。

1,如果你在Activity启动完成,界面显示出来的时候需要显示我们对某个控件的模糊效果,我们可以使用Activity的回调方法。

  @Override    public void onWindowFocusChanged(boolean hasFocus) {    // TODO Auto-generated method stub    super.onWindowFocusChanged(hasFocus);    if(hasFocus){  //界面完全渲染完成    //我们应该在这里面执行我们的模糊图片的效果    }else{  //界面焦点失去        }    }
为什么在这个方法之中执行呢?不在上面所说的onResume()方法中呢?其实我也说不了很清楚,说说自己的理解吧,可能在onResume()回调方法当中,所有界面并没有完全渲染完成,所有有些控件我们是不能得到他的宽高等属性的,而在onWindowFcusChanged回调方法中,我们所有的UI都渲染完成,能够获得我们的宽高了,上面的blurBitmap也不会报错了。

2,view.post(runnable),将我们的模糊执行的逻辑放在runnalbe中执行,这里我的理解是,通过post将一个runnable投递到消息队列的队尾,然后等待Looper调用此runnable的时候,view已经初始化完成了。

3。ViewTreeObserver:使用ViewTreeObserver的众多回调可以完成这个功能,比如使用addOnPreDrawListener这个接口,不知道为什么这里获取宽高也不会出错。

hover_view.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {@Overridepublic boolean onPreDraw() {hover_view.getViewTreeObserver().removeOnPreDrawListener(this);  //以免重复执行 所以先移除textview.buildDrawingCache();Bitmap bmp = textview.getDrawingCache();FastBlur.blur(bmp, textview, hover_view, context);return true;}});

 我的具体需求是在listView中去给每一个Item拥有模糊效果,由于这个模糊涉及到bitmap,所以当数据Item很多的时候,滑动会存在卡顿现象。因为在blurBitmap函数的27行有一句代码bitmap.recycle(); 当我们listView的item 复用时候 由于bitmap已经被回收了,会造成程序崩溃的bug。所以当时我就考虑用github上面的FastBlur去实现(后来才发现其实RenderScript也可以实现)这种模糊效果。


看看gitHub上面的FastBulr的模糊效果的具体实现:

public static Bitmap doBlur(Bitmap sentBitmap, int radius,              boolean canReuseInBitmap) {          Bitmap bitmap;          if (canReuseInBitmap) {              bitmap = sentBitmap;          } else {              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];          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;              }          }            bitmap.setPixels(pix, 0, w, 0, 0, w, h);            return bitmap;      }  
其实也就是对bitmap进行了一定的像素处理。参数sentBitmap其实就是我们要处理的图片,radius就是传入的要处理达到的模糊度,canReuseInBitmap标识是否能够重用这个sentBitmap. 加载bitmap和渲染的问题我是采用上面的addOnPreDrawListener来处理的。对于处理listview中加载并且模糊的卡顿效果我采用的方法是对bitmap采取压缩之后再模糊处理 将处理之后的图片在通过bitmap的缩放,放大到我需要的指定大小。
public static void blur(Bitmap bkg, TextView textview, ImageView view,Context context) {int radius = 2;float scaleFactor = 8;Bitmap overlay = Bitmap.createBitmap((int) (textview.getMeasuredWidth() / scaleFactor),(int) (textview.getMeasuredHeight() / scaleFactor),Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(overlay);Paint paint = new Paint();paint.setFlags(Paint.FILTER_BITMAP_FLAG);canvas.translate(-textview.getLeft() / scaleFactor, -textview.getTop()/ scaleFactor);canvas.scale(1 / scaleFactor, 1 / scaleFactor);canvas.drawBitmap(bkg, 0, 0, paint);//overlay = doBlur(overlay, radius, true);overlay = blurBitmap(overlay, context);view.setBackground(new BitmapDrawable(context.getResources(), zoomImg(overlay, textview.getMeasuredWidth(),textview.getMeasuredHeight())));}/** * 处理图片 *  * @param bm *            所要转换的bitmap * @param newWidth新的宽 * @param newHeight新的高 * @return 指定宽高的bitmap */public static Bitmap zoomImg(Bitmap bm, int newWidth, int newHeight) {// 获得图片的宽高int width = bm.getWidth();int height = bm.getHeight();// 计算缩放比例float scaleWidth = ((float) newWidth) / width;float scaleHeight = ((float) newHeight) / height;// 取得想要缩放的matrix参数Matrix matrix = new Matrix();matrix.postScale(scaleWidth, scaleHeight);// 得到新的图片Bitmap newbm = Bitmap.createBitmap(bm, 0, 0, width, height, matrix,true);return newbm;}

函数blur通过我们需要模糊的控件(此处为TextView)产生的bitmap结合textView的宽和高创建一个缩小了scaleFactor倍大小的已经有模糊效果的bitmap对象,最后再通过zoomImg方法放大scaleFactor倍数(这里的通用性可能不好,应该将scaleFactor写成输入参数)放到我们定义好的以及悬浮在TextView(需要模糊效果)的上面的wrapContent大小的ImageView里面,用setBackGround放大设置位背景,这样就覆盖住了TextView。由于开始ImageView没有任何内容和背景颜色 ,所有他并不现实出来,之后设置背景之后才会现实出来了。下面是效果的对比。传送门

           

0 0
原创粉丝点击