百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

推荐一个强大的毛玻璃特效开源库

toyiye 2024-05-19 19:35 20 浏览 0 评论

本文作者:大逗大人作者博客:https://juejin.im/user/56a4ee7b2e958a0059676953

毛玻璃效果实际上是对原图片的严重劣化,突出朦胧感,一般都是通过图片的缩放+模糊算法来实现,从性能角度考虑,模糊半径不能大于25,所以要更高的模糊效果则需要进行缩放。具体实现方案有以下几种:

  • Java实现,一般都是采用Stack模糊算法
  • RenderScript实现
  • Native实现
  • OpenCV或者OpenGL实现,由于其复杂度,本文暂不讨论该方案

关于模糊算法及上面各种方案的性能分析可以参考Android动态模糊实现的研究这篇文章。

1. Java实现

Java代码实现毛玻璃效果基本上都是采用的Stack模糊算法,该算法比高斯模糊及均值模糊算法更高效,效果更好。实现代码如下

 public static Bitmap doBlur(Bitmap sentBitmap, int radius, boolean canReuseInBitmap) {
 // 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>
 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);
 }

由于是Java实现的,所以该方案不存在兼容性问题,也正因为是Java实现的,所以性能不会很好。因此该方案一般作为降级方案使用

2. RenderScript实现

RenderScript是一个在Android上以高性能运行计算密集型任务的框架。它对执行图像处理,计算摄影或计算机视觉的应用程序尤其有用。RenderScript提供了一个实现高斯模糊的类ScriptIntrinsicBlur,代码如下

 public Bitmap blurBitmap(Bitmap bitmap, int radius) {
 //创建一个空bitmap,其大小与我们想要模糊的bitmap大小相同
 Bitmap outBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
 //实例化一个新的Renderscript
 RenderScript rs = RenderScript.create(getApplicationContext());
 //创建Allocation对象
 Allocation allIn = Allocation.createFromBitmap(rs, bitmap);
 Allocation allOut = Allocation.createFromBitmap(rs, outBitmap);
 //创建ScriptIntrinsicBlur对象,该对象实现了高斯模糊算法
 ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
 //设置模糊半径,0 <radius <= 25
 blurScript.setRadius(radius);
 //执行Renderscript
 blurScript.setInput(allIn);
 blurScript.forEach(allOut);
 //将allOut创建的Bitmap复制到outBitmap
 allOut.copyTo(outBitmap);
 //释放内存占用
 bitmap.recycle();
 //销毁Renderscript。
 rs.destroy();
 return outBitmap;
 }

由于RenderScript的最低支持版本是11,但很多方法都是在17及以后添加的,所以使用RenderScript的最低版本应该为17。但如果要向下兼容则需要使用谷歌提供的向下兼容库android.support.v8.renderscript。由于该库会明显增加APK大小,所以慎重使用。

关于更多RenderScript内容可以去官网查看。

3. 开源项目

3.1 Blurry的使用

Blurry(https://github.com/wasabeef/Blurry)是GitHub一个比较热门的毛玻璃效果实现库。首先导入该库:

 dependencies {
 compile 'jp.wasabeef:blurry:3.x.x'
 }

由于该库并没有使用RenderScript的向下兼容库,所以不会导入一些so文件,也就不会增加APK大小

Blurry在使用上是非常简单的,只要使用过Glide,基本上就能快速上手。使用方式如下

 //for ViewGroup
 Blurry.with(context)
 .radius(10)//模糊半径
 .sampling(8)//缩放大小,先缩小再放大
 .color(Color.argb(66, 255, 255, 0))//颜色
 .async()//是否异步
 .animate(500)//显示动画,目前仅支持淡入淡出,默认时间是300毫秒,仅支持传入控件为ViewGroup
 .onto(viewGroup);
 //for view
 Blurry.with(context)
 .radius(10)//模糊半径
 .sampling(8)//缩放大小,先缩小再放大
 .color(Color.argb(66, 255, 255, 0))//颜色
 .async()//是否异步
 .capture(view)//传入View
 .into(view);//显示View
 //for bitmap
 Blurry.with(context)
 .radius(10)//模糊半径
 .sampling(8)//缩放大小,先缩小再放大
 .color(Color.argb(66, 255, 255, 0))//颜色
 .async()//是否异步
 .from(bitmap)//传入bitmap
 .into(view);//显示View

想必到这里就能很熟练的使用Blurry了吧。前面介绍过毛玻璃的实现原理,那么Blurry是怎么来实现毛玻璃效果的尼?其实它就是通过RenderScript+Java来实现的。来看它的Blur类,在该类的of方法中实现了毛玻璃效果。

 public static Bitmap of(Context context, Bitmap source, BlurFactor factor) {
 int width = factor.width / factor.sampling;
 int height = factor.height / factor.sampling;
 if (Helper.hasZero(width, height)) {
 return null;
 }
 Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
 Canvas canvas = new Canvas(bitmap);
 //进行缩放
 canvas.scale(1 / (float) factor.sampling, 1 / (float) factor.sampling);
 Paint paint = new Paint();
 paint.setFlags(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
 PorterDuffColorFilter filter =
 new PorterDuffColorFilter(factor.color, PorterDuff.Mode.SRC_ATOP);
 //设置颜色
 paint.setColorFilter(filter);
 canvas.drawBitmap(source, 0, 0, paint);
 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
 //如果当前sdk版本大于17则采用RenderScript实现毛玻璃效果
 try {
 bitmap = Blur.rs(context, bitmap, factor.radius);
 } catch (RSRuntimeException e) {
 //当RenderScript出现意外时,采用Java代码来实现毛玻璃效果
 bitmap = Blur.stack(bitmap, factor.radius, true);
 }
 } else {//如果当前sdk版本小于等于17则采用Java来实现毛玻璃效果
 bitmap = Blur.stack(bitmap, factor.radius, true);
 }
 ...
 }

可以发现上面代码是在对Bitmap缩放后进行处理的,由于RenderScript的兼容性限制,所以采用了Java实现作为降级方案,因此该库不会存在兼容性问题。实现效果如下:

可以发现Blurry仅支持在本地图片上实现毛玻璃效果,那么如何对网络图片实现毛玻璃效果尼?可以参考glide-transformations、picasso-transformations、fresco-processors这三个库的实现,由于它们与Blurry的作者是同一人,所以它们的实现原理与Blurry一样。但有一点需要注意,glide-transformations有使用RenderScript的向下兼容库,所以会明显增加APK大小。

3.2 blurkit-android的使用

blurkit-android(https://github.com/CameraKit/blurkit-android)也是GitHub上比较热门的毛玻璃效果实现库。首先导入该库

 dependencies {
 implementation 'io.alterac.blurkit:blurkit:1.1.1'
 }

blurkit-android有两种使用方式,使用BlurLayout控件或者直接对View及Bitmap进行高斯模糊。

先来看BlurLayout的使用,非常简单

 <io.alterac.blurkit.BlurLayout xmlns:blurkit="http://schemas.android.com/apk/res-auto"
 android:id="@+id/blurLayout"
 android:layout_width="match_parent"
 android:layout_height="150dp"
 android:layout_centerInParent="true"
 blurkit:blk_fps="60"//每过1000/fps的时间重新绘制一次BlurLayout,
 blurkit:blk_alpha="0.5"//透明度
 blurkit:blk_blurRadius="15"//模糊半径
 blurkit:blk_cornerRadius="30dp"//BlurLayout的圆角半径
 blurkit:blk_downscaleFactor="0.12"//缩放大小,是先放大再缩小,所以值太大则有可能OOM
 >

当然仅在xml文件中定义还不够,还需要在onStart及onStop中开启与暂停

 @Override
 protected void onStart() {
 super.onStart();
 blurLayout.startBlur();
 blurLayout.lockView();
 }
 @Override
 protected void onStop() {
 super.onStop();
 blurLayout.pauseBlur();
 }

根据以上代码就可以使用BlurLayout控件。把BlurLayout作为遮罩,效果还是蛮不错的。效果如下

关于直接对View及Bitmap进行高斯模糊的使用就更简单了:

 //进行BlurKit初始化,在Application中初始化
 BlurKit.init(this);
 //通过RenderScript进行高斯模糊并返回一个bitmap,iv1可以是一个View,也可以是一个ViewGroup,25是模糊半径
 Bitmap bt=BlurKit.getInstance().blur(iv1, 25);
 //通过RenderScript进行高斯模糊并返回一个bitmap,传入的是一个bitmap,25是模糊半径
 Bitmap bt=BlurKit.getInstance().blur(bitmap, 25);
 //通过RenderScript进行高斯模糊并返回一个bitmap,iv1可以是一个View,也可以是一个ViewGroup,25是模糊半径,2代表缩放比例,如果值太大可能会出现OOM
 Bitmap bt=BlurKit.getInstance().fastBlur(iv1,25,2)

通过上面的说明想必了解blurkit-android的使用了。当然blurkit-android的毛玻璃实现原理也很简单,通过RenderScript来实现的。

 //在类BlurKit中
 public Bitmap blur(Bitmap src, int radius) {
 final Allocation input = Allocation.createFromBitmap(rs, src);
 final Allocation output = Allocation.createTyped(rs, input.getType());
 final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
 script.setRadius(radius);
 script.setInput(input);
 script.forEach(output);
 output.copyTo(src);
 return src;
 }

下面就来说明一下blurkit-android中存在的一些问题。

  • 要使用blurkit-android的1.1.1版本(目前最新版本),不要使用1.1.0版本(虽然GitHub上的使用文档还是1.1.0)。因为使用1.1.1版本时minSdkVersion的值可以是17,而使用1.1.0版本时minSdkVersion的值必须是21。
  • 在使用时建议直接拉取源码,因为通过依赖的方式会导入RenderScript兼容包所需的一些so文件,从而增加APK大小,虽然这些文件并没有用到。
  • BlurLayout的blk_fps属性要慎重设置,因为BlurLayout会每隔(1000/fps)的时间重新绘制一次,也就是BlurLayout会不停的重新绘制,就会消耗一定的CPU。如果fps为0则绘制一次即可。
  • 在blurkit-android的目前的代码中(包括最新版本),BlurLayout的blk_alpha属性并不能使用。因为在代码中存在类型转换错误。英语好的同学可以去提issue。

在BlurLayout中,blk_alpha属性的类型是float,但在获取值时却以dimension的类型来接收,所以就会出现类型转换错误。

 <declare-styleable name="BlurLayout">
 ...
 <attr name="blk_alpha" format="float" />
 </declare-styleable>
 public BlurLayout(Context context, AttributeSet attrs) {
 super(context, attrs);
 ...
 try {
 //其实blk_alpha的类型是float,把这里的getDimension改成getFloat即可
 mAlpha = a.getDimension(R.styleable.BlurLayout_blk_alpha, DEFAULT_ALPHA);
 } finally {
 a.recycle();
 }
 ...
 }

3.3 其他开源项目

前面讲了2个GitHub上比较热门的开源项目实现,但这两个项目基本上都是通过RenderScript或者RenderScript+Java来实现毛玻璃效果的。那么如果要通过NDK或者OpenGL来实现尼?下面就来简单介绍几个通过NDK或者OpenGL来实现毛玻璃效果的开源项目。

  • android-stackblur(https://github.com/kikoso/android-stackblur)是GitHub一个比较热门的毛玻璃效果开源项目,但使用起来就要麻烦一点。它没有现成的包可以导入,需要直接复制代码到应用中,所以也需要我们自己来编译so文件。它采用了Java+RenderScript兼容包+NDK来实现毛玻璃效果。NDK实现是Stack模糊算法的C语言版本。需要注意一点的是,在该项目里不能直接编译so文件,需要将blur.c及*.mk文件拿出来单独编译
  • HokoBlur(https://github.com/HokoFly/HokoBlur)这个开源项目虽然star不多,但是也挺有意思的,它不仅有Stack模糊、高斯模糊及均值模糊这三种算法的实现,也有它们的Java版本、C语言版本、RenderScript及OpenGL版本。虽然在使用上难度不是很大,但学习起来就有一定难度。

有兴趣的话可以去看看上面两个项目,当然需要一定的C语言基础。

4. 总结

到这里想必对Android中毛玻璃效果的实现及原理有了一定的了解,那么在应用中该如何选择实现方案尼?本着以现有轮子优先的原则,下面给出一个选择参考:

  • 如果对Bitmap或者View进行模糊处理则优先使用Blurry
  • 如果要遮罩效果,则优先使用blurkit-android,虽然它有点小坑,但完全可以自己解决
  • 如果要对网络图片进行模糊处理,可以参考glide-transformations、picasso-transformations、fresco-processors这三个项目,但不建议直接导入,毕竟仅为一个毛玻璃效果而导入整个库,有点不划算。
  • 如果想要自己来实现毛玻璃效果,可以参考android-stackblur及HokoBlur这两个项目

【参考资料】

Android图像处理 - 高斯模糊的原理及实现

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码