Android高斯模糊
传送门
github地址:http://developer.android.com/guide/topics/renderscript/compute.html;
https://github.com/kikoso/android-stackblur。
csdn參考知识:http://blog.csdn.net/huli870715/article/details/39378349
感谢大神们的无私奉献,让我从小彩笔慢慢成长成大彩笔的梦想更近一步。
什么是高斯模糊(依据百科描写叙述)
高斯模糊能够把某一点周围的像素色值按高斯曲线统计起来,採用数学上加权平均的计算方法得到这条曲线的色值,最后能够留下人物的轮廓,即曲线。
全部的颜色只是都是数字,各种模糊只是都是算法。把要模糊的像素色值统计,用数学上加权平均的计算方法(高斯函数)得到色值。对范围、半径等进行模糊,大致就是高斯模糊。
高斯函数、正太分布、权重矩阵……有兴趣的人能够了解一下。我们主要还是调用【stackblur】开源项目中名为fastBlur的方法在java层直接进行高斯模糊处理。
依据网上的一些參考资料,以及自己项目实际的要求。一開始直接调用下述方法等到以及模糊后的效果图
private Bitmap blurImageAmeliorate(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);
}
结果没有问题,效果图就想网上描写叙述的一样。
但有一个问题就是直接使用原图进行高斯模糊处理的时间有点长。大概200毫秒左右,有明显的停顿感,即使用Handler异步处理,但图片展示的延后,用户体验明显下降。
stackOverflow对于程序猿来说永远是最大的宝藏。
http://stackoverflow.com/questions/2067955/fast-bitmap-blur-for-android-sdk这篇提问帖最终提供了新的解决思路:
This is a shot in the dark, but you might try shrinking the image and then enlarging it again. This can be done with Bitmap.createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter). Make sure and set the filter parameter to true. It’ll run in native code so it might be faster.
它所表述的原理为先通过缩小图片,使其丢失一些像素点,接着进行模糊化处理,然后再放大到原来尺寸。
因为图片缩小后再进行模糊处理,须要处理的像素点和半径都变小。从而使得模糊处理速度加快。
于是在高斯模糊算法之外我们套接一层,进行位图的缩小。
private void blur(Bitmap bkg, View view) {
float radius = 2;
float scaleFactor = 8;
Bitmap overlay = Bitmap.createBitmap((int)(view.getMeasuredWidth()/scaleFactor), (int)(view.getMeasuredHeight()/scaleFactor), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(overlay);
canvas.translate(-view.getLeft()/scaleFactor, -view.getTop()/scaleFactor);
canvas.scale(1 / scaleFactor, 1 / scaleFactor);
Paint paint = new Paint();
paint.setFlags(Paint.FILTER_BITMAP_FLAG);
canvas.drawBitmap(bkg, 0, 0, paint);
view.setBackground(new BitmapDrawable(getResources(), blurImageAmeliorate(overlay, (int)radius, true)));
}
Bitmap不懂,不经常使用有没有。。。
Canvas不懂,不经常使用有没有。。。
Paint不懂,不经常使用有没有。
。。
然后直接拿来调用最终的效果不错
那么问题来了,为什么背景图没有填充整个RelativeLayout呢。我们通过RelativeLayout.setBackground来设置背景。
各种測试后问题原因在于Canvas缩小的处理,对于Canvas实在不在行,知道问题所在,也解释不了。
如今回到正题。我们要做得是在高斯模糊处理之前得到一个缩小的位图,在此再感谢一下李刚老师的【疯狂Android讲义】。平时还是能够翻一翻,加深一些相关的知识。
通过调用Bitmap.createScaledBitmap方法,我们能够等到一个缩小后的位图。并且能顺利填充到组件背景,在效率上对照第一种快上不少,问题就如此处理了。。
。
除了Canvas的问题!
要用到高斯模糊上网查了非常久。其他的处理方式我这也从别人那copy一下,以备以后实用。(下面内容,我仅仅是用过一下下。出现的bug临时没有处理)
RenderScript
RenderScript是API11之后才引入的,所以对版本号有限制,并且RenderScript确实挺复杂的,尽管使用他的Blur功能非常easy,可是要真正搞懂,不是一天两天的事。学习文档:http://developer.android.com/guide/topics/renderscript/compute.html
private void blur(Bitmap bkg, View view) {
long startMs = System.currentTimeMillis();
float radius = 20;
Bitmap overlay = Bitmap.createBitmap((int)(view.getMeasuredWidth()), (int)(view.getMeasuredHeight()), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(overlay);
canvas.translate(-view.getLeft(), -view.getTop());
canvas.drawBitmap(bkg, 0, 0, null);
RenderScript rs = RenderScript.create(SecondActivity.this);
Allocation overlayAlloc = Allocation.createFromBitmap(rs, overlay);
ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create(rs, overlayAlloc.getElement());
blur.setInput(overlayAlloc);
blur.setRadius(radius);
blur.forEach(overlayAlloc);
overlayAlloc.copyTo(overlay);
view.setBackground(new BitmapDrawable(getResources(), overlay));
rs.destroy();
statusText.setText("cost " + (System.currentTimeMillis() - startMs) + "ms");
}
布局代码就不具体的copy过来了。实现这种方法一个要求最低的sdk版本号是11,对于硬件要求是17。也就是说假设手机Android版本号是4.2下面的话。应该是用不了的。我測试机还是小米1,让我呵呵一笑。
以上差点儿相同是自己这次接触高斯模糊所见所学,有不足之处,请大家谅解。