本文假设读者拥有基础的图像处理概念。
这是今天刚刚完成的一个方法,之前不停地在网上找有关 Android 的图像模糊处理代码。
期间找到了倒影、缩放等参考代码,却无一帖子对模糊处理有过提及。
最多也就是提到使用 BlurMaskFilter 来进行模糊处理。
为了这个害人的帖子,我整整浪费了一下午时间,最后发现它只能用于对 Paint 的边缘进行处理。
而我们要处理的是整幅图像,所以这完完全全是一个骗人的说法。
由于先前在 VB.NET 上处理过图像,也写过相关代码,当时 VB.NET 代码分两种形式:
1、死算:即将每个像素点的颜色值与周围 8 点计算平均值,每计算一次 SetPixel() 一次,这样做非常慢;
2、读取整幅图像的内存数据,也就是已三原色为单位了,数据量(颜色数组)比上一种方法大一倍。
这样的做法可以将所有颜色计算好以后再重新写入内存,肯定比 SetPixel() 更高效。
不过由于也并非通过底层函数对内存操作,因此虽然有提速,效果和 C++ 相比却是小巫见大巫了。
下面说正事,其实 JAVA 和 .NET 很类似的(据说 .NET 的架构师就是 SUN 过去的呢)。
当然也无法直接对内存操作,当然也无法像 .NET 这样间接对内存操作,很抓狂啊!
要知道,我是死也不会用第一种方法死算的!
那么怎么办?我仍然希望从第二种方法入手,虽然慢了一些,但还是能忍受的。
好在 Android 提供了 setPixels() 方法,看清楚,Pixel 后面有 s 的!
这个方法其实是 setPixel() 的“批量”版本,也就是说允许我们一下子填充多个连续的像素。
既然这样,我们离模拟第二点还差一半,那就是取得包含三原色的数组了,看代码吧。
/* 设置图片模糊 */
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 秒,可以说基本保持在一个固定比例中。