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