Android之高斯模糊的记录

本文详细介绍了如何在Android应用中实现高斯模糊效果,包括使用RenderScript和FastBlur方法,并探讨了不同API级别的兼容性解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在开发项目的时候遇到一个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() {
			@Override
			public 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没有任何内容和背景颜色 ,所有他并不现实出来,之后设置背景之后才会现实出来了。下面是效果的对比。传送门

           

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值