Android 實現水波紋
在Android中,每一個圖像像素通過一個4字節整數來展現:最高位字節用作alpha通道,接下來的事Red,依次類推,接下來的兩個字節對應實現Green和Bule。
要達到現實的水波效果比較難,這里一切從簡了。
先復習一下物理學。在一灘平靜的水面(所有點的振幅為0),扔上一個半徑為r的圓形石頭,則第一時間水面上被石頭打到的那部分水就會往下沉(振幅變為負)。然后,每一個被打到的點都會把這剛剛獲取的能量往四周擴散(在這個例子中,假設只有上下左右四個方向的點受到中心的影響,說了一切從簡的),同時,由于擴散的過程當中的能量損失,振幅會變得越來越小,直至整個水面恢復平靜。
折射,在一張背景圖片模擬水波效果的重點在于模擬水波的折射效果。出現水波的時候,相鄰兩個點之間的高度不一致,出現了一定的高度差,假定我們從正上方看這個水波,這個高度差就會產生一個折射效果,即我們看到的點應該在實際位置的偏下位置。一切從簡的話,這個位置偏移多少就直接由這個高度差來決定算了。就簡單模擬一下,其實運行之后的效果也不是那么的差。
一段段代碼的分析:
buf1和buf2分別用來存儲一個像素點在一次渲染前和渲染后的振幅,BitMap1,BitMap2用來存儲獲得的圖片像素
short[] buf2; short[] buf1; int[] Bitmap2; int[] Bitmap1; buf2 = new short[BACKWIDTH BACKHEIGHT]; buf1 = new short[BACKWIDTH BACKHEIGHT];Bitmap2 = new int[BACKWIDTH BACKHEIGHT]; Bitmap1 = new int[BACKWIDTH BACKHEIGHT];</pre>
扔石頭,如上所述
則第一時間水面上被石頭打到的那部分水就會往下沉(振幅變為負)。
void DropStone(int x,// x坐標 int y,// y坐標 int stonesize,// 波源半徑 int stoneweight)// 波源能量 { for (int posx = x - stonesize; posx < x + stonesize; posx++) for (int posy = y - stonesize; posy < y + stonesize; posy++) if ((posx - x) * (posx - x) + (posy - y) * (posy - y) < stonesize* stonesize) buf1[BACKWIDTH * posy + posx] = (short) -stoneweight; }這一段則是現實擴散的過程。buf2為擴散之后的振幅,由于只考慮上下左右四個方向對中心振幅的影響,那么影響一個點在一次擴散之后的振幅為四周的振幅和自己上一次的振幅。四周的影響假定相同。設X為中心處得振幅,上下左右振幅為X1,X2,X3,X4,X’為一次擴散之后的振幅則。X’=(X1 +X2+X3+X4)a+Xb。 這個擴散是相對的,四個方向同樣要受到中心點的影響,非常粗虐的計算,根據能量守恒,同時把這個局部當做整體。X+ X1 + X2+ X3 +X4=X'+X1' +X2'+ X3' + X4'。代入之后得出結果 4a+b=1。取一組合理的解為a=1/2,b=-1。為了提高效率,將除以2變成移位。得到新的振幅之后,執行衰減。
void RippleSpread() { for (int i = BACKWIDTH; i < BACKWIDTH * BACKHEIGHT - BACKWIDTH; i++) { // 波能擴散 buf2[i] = (short) (((buf1[i - 1] + buf1[i + 1]+ buf1[i - BACKWIDTH] + buf1[i + BACKWIDTH]) >> 1) - buf2[i]); // 波能衰減 buf2[i] -= buf2[i] >> 5; } // 交換波能數據緩沖區 short[] ptmp = buf1; buf1 = buf2; buf2 = ptmp; }擴散一次之后,則根據相鄰像素點之間的高度差(即振幅差)計算出偏移量Xoff和Yoff,上面說過的,偏移就直接等于高度差算了,一切從簡。然后將偏移加到具體的像素里頭,新的像素為原來的像素加上偏移之后的像素。
/ 渲染你水紋效果 / void render() { int xoff, yoff; int k = BACKWIDTH; for (int i = 1; i < BACKHEIGHT - 1; i++) { for (int j = 0; j < BACKWIDTH; j++) { // 計算偏移量 xoff = buf1[k - 1] - buf1[k + 1]; yoff = buf1[k - BACKWIDTH] - buf1[k + BACKWIDTH]; // 判斷坐標是否在窗口范圍內 if ((i + yoff) < 0) { k++; continue; }if ((i + yoff) > BACKHEIGHT) { k++; continue; }
if ((j + xoff) < 0) { k++; continue; }
if ((j + xoff) > BACKWIDTH) { k++; continue; }
// 計算出偏移象素和原始象素的內存地址偏移量 int pos1, pos2; pos1 = BACKWIDTH (i + yoff) + (j + xoff); pos2 = BACKWIDTH i + j; Bitmap2[pos2++] = Bitmap1[pos1++]; k++; } }
}</pre>
最后,把這些加到線程里頭,DropStone方法在onKeyUp事件中調用,線程繪圖了,每過50ms就擴撒一次,直至水面平靜。public void run() { while (!Thread.currentThread().isInterrupted()) { try { Thread.sleep(50); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }RippleSpread(); render(); // 使用postInvalidate可以直接在線程中更新界面 postInvalidate(); }
}</pre>