本文假設讀者擁有基礎的影像處理概念。
這是今天剛剛完成的一個方法,之前不停地在網上找有關 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 秒,可以說基本保持在一個固定比例中。