本文已授权「玉刚说」微信公众号独家发布
毛玻璃效果实际上是对原图片的严重劣化,突出朦胧感,一般都是通过图片的缩放+模糊算法来实现,从性能角度考虑,模糊半径不能大于25,所以要更高的模糊效果则需要进行缩放。具体实现方案有以下几种。
- Java实现,一般都是采用
Stack模糊
算法 RenderScript
实现- Native实现
- OpenCV或者OpenGL实现,由于其复杂度,本文暂不讨论该方案
关于模糊算法及上面各种方案的性能分析可以参考Android动态模糊实现的研究这篇文章
1、Java实现
Java代码实现毛玻璃效果基本上都是采用的Stack模糊
算法,该算法比高斯模糊
及均值模糊
算法更高效,效果更好。实现代码如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
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
,代码如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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是GitHub一个比较热门的毛玻璃效果实现库。首先导入该库。
1
2
3
4
5
dependencies {
compile 'jp.wasabeef:blurry:3.x.x'
}
复制代码
由于该库并没有使用RenderScript
的向下兼容库,所以不会导入一些so文件,也就不会增加APK大小。
Blurry
在使用上是非常简单的,只要使用过Glide
,基本上就能快速上手。使用方式如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//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
方法中实现了毛玻璃效果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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也是GitHub上比较热门的毛玻璃效果实现库。首先导入该库。
1
2
3
4
5
dependencies {
implementation 'io.alterac.blurkit:blurkit:1.1.1'
}
复制代码
blurkit-android
有两种使用方式,使用BlurLayout
控件或者直接对View及Bitmap进行高斯模糊。
先来看BlurLayout
的使用,非常简单。
1
2
3
4
5
6
7
8
9
10
11
12
13
<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
中开启与暂停。
1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
protected void onStart() {
super.onStart();
blurLayout.startBlur();
blurLayout.lockView();
}
@Override
protected void onStop() {
super.onStop();
blurLayout.pauseBlur();
}
复制代码
根据以上代码就可以使用BlurLayout
控件。把BlurLayout
作为遮罩,效果还是蛮不错的。效果如下。
1
2
3
4
5
6
7
8
9
10
//进行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
来实现的。
1
2
3
4
5
6
7
8
9
10
11
12
13
//在类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的类型来接收,所以就会出现类型转换错误。
1
2
3
4
5
6
<declare-styleable name="BlurLayout">
...
<attr name="blk_alpha" format="float" />
</declare-styleable>
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
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是GitHub一个比较热门的毛玻璃效果开源项目,但使用起来就要麻烦一点。它没有现成的包可以导入,需要直接复制代码到应用中,所以也需要我们自己来编译so文件。它采用了Java+
RenderScript
兼容包+NDK来实现毛玻璃效果。NDK实现是Stack模糊
算法的C语言版本。需要注意一点的是,在该项目里不能直接编译so文件,需要将blur.c及*.mk文件拿出来单独编译。 - 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图像处理 - 高斯模糊的原理及实现
转载于:https://juejin.im/post/5c8b233cf265da2de970c352
最后
以上就是可爱鞋垫最近收集整理的关于毛玻璃效果在Android的实现的全部内容,更多相关毛玻璃效果在Android内容请搜索靠谱客的其他文章。
发表评论 取消回复