android 通过操作像素来实现水波效果

最近又开始学习android了。

在当当网买了一本书《Android应用开发揭秘》,这本书的内容很丰富,讲的很详细,尤其适合我这样的初学者。

刚刚看到第二部分第五章,在做到一个实例的时候,发下代码居然有看不懂的地方,就仔细研究了一下。上网找了一些资料,终于是把问题弄清楚了,分享一下。

首先下载这本书附带的源码压缩包,这个例子为Examples_05_10。既然是实现水波效果,就不得不用到水波算法了。关于水波算法,我是看了

这里 

才晓得的,他这上面讲的很清楚。感谢作者的分享。

在工程代码中找到GameView.java文件。在Android中,每一个图像像素通过一个4字节整数来展现:最高位字节用作alpha通道,接下来的事Red,依次类推,接下来的两个字节对应实现Green和Bule。

要达到现实的水波效果比较难,这里一切从简了。

先复习一下物理学。在一滩平静的水面(所有点的振幅为0),扔上一个半径为r的圆形石头,则第一时间水面上被石头打到的那部分水就会往下沉(振幅变为负)。然后,每一个被打到的点都会把这刚刚获取的能量往四周扩散(在这个例子中,假设只有上下左右四个方向的点受到中心的影响,说了一切从简的),同时,由于扩散的过程当中的能量损失,振幅会变得越来越小,直至整个水面恢复平静。

折射,在一张背景图片模拟水波效果的重点在于模拟水波的折射效果。出现水波的时候,相邻两个点之间的高度不一致,出现了一定的高度差,假定我们从正上方看这个水波,这个高度差就会产生一个折射效果,即我们看到的点应该在实际位置的偏下位置。一切从简的话,这个位置偏移多少就直接由这个高度差来决定算了。就简单模拟一下,其实运行之后的效果也不是那么的差。

一段段代码的分析:

buf1和buf2分别用来存储一个像素点在一次渲染前和渲染后的振幅,BitMap1,BitMap2用来存储获得的图片像素

1 short[] buf2;
2
3 short[] buf1;
4
5 int[] Bitmap2;
6
7 int[] Bitmap1;
1 buf2 = new short[BACKWIDTH * BACKHEIGHT];
2 buf1 = new short[BACKWIDTH * BACKHEIGHT];
3 Bitmap2 = new int[BACKWIDTH * BACKHEIGHT];
4 Bitmap1 = new int[BACKWIDTH * BACKHEIGHT];

扔石头,如上所述    

则第一时间水面上被石头打到的那部分水就会往下沉(振幅变为负)。

 

1 void DropStone(int x,// x坐标
2   int y,// y坐标
3   int stonesize,// 波源半径
4   int stoneweight)// 波源能量
5   {
6 for (int posx = x - stonesize; posx < x + stonesize; posx++)
7 for (int posy = y - stonesize; posy < y + stonesize; posy++)
8 if ((posx - x) * (posx - x) + (posy - y) * (posy - y) < stonesize
9 * stonesize)
10 buf1[BACKWIDTH * posy + posx] = (short) -stoneweight;
11 }

这一段则是现实扩散的过程。buf2[i]为扩散之后的振幅,由于只考虑上下左右四个方向对中心振幅的影响,那么影响一个点在一次扩散之后的振幅为四周的振幅和自己上一次的振幅。四周的影响假定相同。设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变成移位。得到新的振幅之后,执行衰减。

 

1 void RippleSpread() {
2 for (int i = BACKWIDTH; i < BACKWIDTH * BACKHEIGHT - BACKWIDTH; i++) {
3 // 波能扩散
4   buf2[i] = (short) (((buf1[i - 1] + buf1[i + 1]
5 + buf1[i - BACKWIDTH] + buf1[i + BACKWIDTH]) >> 1) - buf2[i]);
6 // 波能衰减
7   buf2[i] -= buf2[i] >> 5;
8 }
9
10 // 交换波能数据缓冲区
11   short[] ptmp = buf1;
12 buf1 = buf2;
13 buf2 = ptmp;
14 }

扩散一次之后,则根据相邻像素点之间的高度差(即振幅差)计算出偏移量Xoff和Yoff,上面说过的,偏移就直接等于高度差算了,一切从简。然后将偏移加到具体的像素里头,新的像素为原来的像素加上偏移之后的像素。(这个表述不清,自己领会吧)

 

1 /* 渲染你水纹效果 */
2 void render() {
3 int xoff, yoff;
4 int k = BACKWIDTH;
5 for (int i = 1; i < BACKHEIGHT - 1; i++) {
6 for (int j = 0; j < BACKWIDTH; j++) {
7 // 计算偏移量
8   xoff = buf1[k - 1] - buf1[k + 1];
9 yoff = buf1[k - BACKWIDTH] - buf1[k + BACKWIDTH];
10
11 // 判断坐标是否在窗口范围内
12   if ((i + yoff) < 0) {
13 k++;
14 continue;
15 }
16 if ((i + yoff) > BACKHEIGHT) {
17 k++;
18 continue;
19 }
20 if ((j + xoff) < 0) {
21 k++;
22 continue;
23 }
24 if ((j + xoff) > BACKWIDTH) {
25 k++;
26 continue;
27 }
28
29 // 计算出偏移象素和原始象素的内存地址偏移量
30   int pos1, pos2;
31 pos1 = BACKWIDTH * (i + yoff) + (j + xoff);
32 pos2 = BACKWIDTH * i + j;
33 Bitmap2[pos2++] = Bitmap1[pos1++];
34 k++;
35 }
36 }
37 }

最后,把这些加到线程里头,DropStone方法在onKeyUp事件中调用,线程绘图了,每过50ms就扩撒一次,直至水面平静。

1 public void run() {
2 while (!Thread.currentThread().isInterrupted()) {
3 try {
4 Thread.sleep(50);
5 } catch (InterruptedException e) {
6 Thread.currentThread().interrupt();
7 }
8 RippleSpread();
9 render();
10 // 使用postInvalidate可以直接在线程中更新界面
11   postInvalidate();
12 }
13 }

我这代码提供的不完整,有兴趣的可以去看些整个项目的代码,最后还是推荐一下这本书 

《Android应用开发揭秘》。

 

 

 

 

PS:第一次发随笔,格式控制的不太好,见谅..

posted @ 2011-05-18 20:36  自由泳的青蛙  阅读(1862)  评论(5编辑  收藏  举报