转自:http://www.littledai.com/tag/blurmaskfilter
本文假设读者拥有基础的图像处理概念。
这是今天刚刚完成的一个方法,之前不停地在网上找有关 Android 的图像模糊处理代码。
期间找到了倒影、缩放等参考代码,却无一帖子对模糊处理有过提及。
最多也就是提到使用 BlurMaskFilter 来进行模糊处理。
为了这个害人的帖子,我整整浪费了一下午时间,最后发现它只能用于对 Paint 的边缘进行处理。
而我们要处理的是整幅图像,所以这完完全全是一个骗人的说法。
由于先前在 VB.NET 上处理过图像,也写过相关代码,当时 VB.NET 代码分两种形式:
1、死算:即将每个像素点的颜色值与周围 8 点计算平均值,每计算一次 SetPixel() 一次,这样做非常慢;
2、读取整幅图像的内存数据,也就是已三原色为单位了,数据量(颜色数组)比上一种方法大一倍。
这样的做法可以将所有颜色计算好以后再重新写入内存,肯定比 SetPixel() 更高效。
不过由于也并非通过底层函数对内存操作,因此虽然有提速,效果和 C++ 相比却是小巫见大巫了。
下面说正事,其实 JAVA 和 .NET 很类似的(据说 .NET 的架构师就是 SUN 过去的呢)。
当然也无法直接对内存操作,当然也无法像 .NET 这样间接对内存操作,很抓狂啊!
要知道,我是死也不会用第一种方法死算的!
那么怎么办?我仍然希望从第二种方法入手,虽然慢了一些,但还是能忍受的。
好在 Android 提供了 setPixels() 方法,看清楚,Pixel 后面有 s 的!
这个方法其实是 setPixel() 的“批量”版本,也就是说允许我们一下子填充多个连续的像素。
既然这样,我们离模拟第二点还差一半,那就是取得包含三原色的数组了,看代码吧。
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
|
/* 设置图片模糊 */
public
static
Bitmap SetBlur(Bitmap bmpSource,
int
Blur)
//源位图,模糊强度
{
int
pixels[] =
new
int
[bmpSource.getWidth() * bmpSource.getHeight()];
//颜色数组,一个像素对应一个元素
int
pixelsRawSource[] =
new
int
[bmpSource.getWidth() * bmpSource.getHeight() *
3
];
//三原色数组,作为元数据,在每一层模糊强度的时候不可更改
int
pixelsRawNew[] =
new
int
[bmpSource.getWidth() * bmpSource.getHeight() *
3
];
//三原色数组,接受计算过的三原色值
bmpSource.getPixels(pixels,
0
, bmpSource.getWidth(),
0
,
0
, bmpSource.getWidth(), bmpSource.getHeight());
//获取像素点
//模糊强度,每循环一次强度增加一次
for
(
int
k =
1
; k <= Blur; k++)
{
//从图片中获取每个像素三原色的值
for
(
int
i =
0
; i < pixels.length; i++)
{
pixelsRawSource[i *
3
+
0
] = Color.red(pixels[i]);
pixelsRawSource[i *
3
+
1
] = Color.green(pixels[i]);
pixelsRawSource[i *
3
+
2
] = Color.blue(pixels[i]);
}
//取每个点上下左右点的平均值作自己的值
int
CurrentPixel = bmpSource.getWidth() *
3
+
3
;
// 当前处理的像素点,从点(2,2)开始
for
(
int
i =
0
; i < bmpSource.getHeight() -
3
; i++)
// 高度循环
{
for
(
int
j =
0
; j < bmpSource.getWidth() *
3
; j++)
// 宽度循环
{
CurrentPixel +=
1
;
// 取上下左右,取平均值
int
sumColor =
0
;
// 颜色和
sumColor = pixelsRawSource[CurrentPixel - bmpSource.getWidth() *
3
];
// 上一点
sumColor = sumColor + pixelsRawSource[CurrentPixel -
3
];
// 左一点
sumColor = sumColor + pixelsRawSource[CurrentPixel +
3
];
// 右一点
sumColor = sumColor + pixelsRawSource[CurrentPixel + bmpSource.getWidth() *
3
];
// 下一点
pixelsRawNew[CurrentPixel] = Math.round(sumColor /
4
);
// 设置像素点
}
}
//将新三原色组合成像素颜色
for
(
int
i =
0
; i < pixels.length; i++)
{
pixels[i] = Color.rgb(pixelsRawNew[i *
3
+
0
], pixelsRawNew[i *
3
+
1
], pixelsRawNew[i *
3
+
2
]);
}
}
//应用到图像
Bitmap bmpReturn = Bitmap.createBitmap(bmpSource.getWidth(), bmpSource.getHeight(), Config.ARGB_8888);
bmpReturn.setPixels(pixels,
0
, bmpSource.getWidth(),
0
,
0
, bmpSource.getWidth(), bmpSource.getHeight());
//必须新建位图然后填充,不能直接填充源图像,否则内存报错
return
bmpReturn;
}
|
代码其实很简单,总体而言就是分为三步:
1、取得该位图所有的字节对应的数据;
2、计算每个像素的模糊平均值;
3、利用颜色数组重新绘图。
这就是跟 VB.NET 不同的地方了,我们在 VB.NET 中是改写内存,而这里是申请新的内存区域。
此外,还多了一个大循环,将上述三步进行多次循环,每次循环前将上一次计算好的平均颜色数组作为新的源数据,这样就能获取更模糊的效果了。
要注意的是,此方法对设备性能要求较高,在 1 GHz 处理器上(三星 Galaxy S i9000)模糊强度为 8 时处理 800 × 400 的图像耗时约 20 秒。
HTC Magic 512 MHz 处理器中约 40 秒,可以说基本保持在一个固定比例中。