GTK实现水波纹效果

⌚Time: 2022-08-02 22:18:54

👨‍💻Author: Jack Ge

参考网络上VC++实现水波纹

水波有如下几个特性:

对于水波纹

波形计算

有这样的一个公式

X0'=(X1+X2+X3+X4)/ 2- X0

已知某一时刻水面上任意一点的波幅,那么,在下一时刻,任意一点的波幅就等于与该点紧邻的前、后、左、右四点的波幅的和除以2、再减去该点的波幅。


// 计算出下一个时刻所有点的波幅

void nextFrame()

{

    for(int i = PIC_WIDTH; i < PIC_HEIGHT*(PIC_WIDTH-1); i++)

    {

        // 公式:X0'= (X1+X2+X3+X4) / 2 - X0

        buf2[i] = ((buf[i-PIC_WIDTH] + buf[i+PIC_WIDTH] + buf[i-1] + buf[i+1]) >> 1) - buf2[i];

 

        // 波能衰减

        buf2[i] -= buf2[i] >> 5;

    }

 

    short *ptmp = buf;

    buf  = buf2;

    buf2 = ptmp;

}


模拟水的折射效果

因为水的折射,当水面不与我们的视线相垂直的时候,我们所看到的水下的景物并不是在观察点的正下方,而存在一定的偏移。偏移的程度与水波的斜率,水的折射率和水的深度都有关系,如果要进行精确的计算的话,显然是很不现实的。同样,我们只需要做线形的近似处理就行了。因为水面越倾斜,所看到的水下景物偏移量就越大,所以,我们可以近似的用水面上某点的前后、左右两点的波幅之差来代表所看到水底景物的偏移量。

水的折射,受到波形的影响


// 处理当前时刻波幅影响之后的位图,保存在 dest_img 中

void RenderRipple()

{

    int i = 0;

    for (int y = 0; y < PIC_HEIGHT; y++) 

    {

            for (int x = 0; x < PIC_WIDTH; x++) 

            {

                short data = 1024 - buf[i];

 

                // 偏移

                int a = ((x - PIC_WIDTH  / 2) * data / 1024) + PIC_WIDTH  / 2;

                int b = ((y - PIC_HEIGHT / 2) * data / 1024) + PIC_HEIGHT / 2;

 

                // 边界处理

                if (a >= PIC_WIDTH)     a = PIC_WIDTH - 1;

                if (a < 0)              a = 0;

                if (b >= PIC_HEIGHT)    b = PIC_HEIGHT - 1;

                if (b < 0)              b = 0;

                

                // 处理偏移 

                img_ptr2[i] = img_ptr1[a + (b * PIC_WIDTH)];

                i++;

            }

        }

}


gtk实现代码:


//gtk实现水波纹效果

//by GeXingli

//2022/08/02

#include <gtk/gtk.h>

#include <stdio.h>

//malloc()

#include <stdlib.h>

//memset()

#include <string.h>



int PIC_WIDTH;

int PIC_HEIGHT;



GtkWidget *window;

int *waveBuff_0;

int *waveBuff_1;

GdkPixbuf *pixbuf_0,*pixbuf_1;

guint timer_0;

char *fileName;

//计算下一时刻各点的波幅

void nextFrame()

{

    int i;

    for(i = PIC_WIDTH; i < PIC_HEIGHT*(PIC_WIDTH-1); i++)

    {

        // 公式:X0'= (X1+X2+X3+X4) / 2 - X0

        waveBuff_1[i] = ((waveBuff_0[i-PIC_WIDTH] + waveBuff_0[i+PIC_WIDTH] + waveBuff_0[i-1] + waveBuff_0[i+1]) >> 1) - waveBuff_1[i];

 

        // 波能衰减

        waveBuff_1[i] -= waveBuff_1[i] >> 5;

    }



    int *ptmp = waveBuff_0;

    waveBuff_0  = waveBuff_1;

    waveBuff_1 = ptmp;

}



// 显示当前时刻受波幅影响的位图

void RenderRipple()

{

    int i = 0;

    int x,y;

    guchar *rgbbuf_0,*rgbbuf_1;

    pixbuf_0 = gdk_pixbuf_new_from_file(fileName,NULL);

    pixbuf_1 = gdk_pixbuf_new_from_file(fileName,NULL);

    //获得pixbuf的像素数据指针

    rgbbuf_0 = gdk_pixbuf_get_pixels (pixbuf_0);

    rgbbuf_1 = gdk_pixbuf_get_pixels (pixbuf_1);



    for (y = 0; y < PIC_HEIGHT; y++) 

    {

            for (x = 0; x < PIC_WIDTH; x++) 

            {

                short data = 1024 - waveBuff_0[i];

 

                // 偏移

                int a = ((x - PIC_WIDTH  / 2) * data / 1024) + PIC_WIDTH  / 2;

                int b = ((y - PIC_HEIGHT / 2) * data / 1024) + PIC_HEIGHT / 2;

 

                // 边界处理

                if (a >= PIC_WIDTH){

                    a = PIC_WIDTH - 1;

                }

                if (a < 0){

                    a = 0;

                }

                if (b >= PIC_HEIGHT){

                     b = PIC_HEIGHT - 1;

                }

                if (b < 0){

                    b = 0;

                }

                // 处理偏移 

                //这里是处理没有alpha 通道RGB像素数据

                rgbbuf_0[i*3] = rgbbuf_1[(a + (b * PIC_WIDTH))*3];

                rgbbuf_0[i*3+1] = rgbbuf_1[(a + (b * PIC_WIDTH))*3+1];

                rgbbuf_0[i*3+2] = rgbbuf_1[(a + (b * PIC_WIDTH))*3+2];

                i++;

            }

    }

    //绘制图像

    gdk_draw_pixbuf(GTK_WIDGET (window)->window,NULL,pixbuf_0,0, 0,0, 0,PIC_WIDTH, PIC_HEIGHT,GDK_RGB_DITHER_NORMAL,0,0);



}

//定时器函数,实时渲染画面

gboolean RenderFun()

{

    //显示当前波幅的图像

    RenderRipple();

    //计算下一时刻波幅

    nextFrame();

    return TRUE;

}

//投石头产生波纹

//参数x、y是石头掉落的位置,stonesize是石头大小(影响的区域),stoneweight是石头重量(影响波的幅度)

void disturb(int x, int y, int stonesize, int stoneweight) 

{

    int posx,posy;

    // 突破边界不处理

    if ((x >= PIC_WIDTH - stonesize) ||

        (x < stonesize) ||

        (y >= PIC_HEIGHT - stonesize) ||

        (y < stonesize))

        return;

 

    for (posx=x-stonesize; posx<x+stonesize; posx++)

    {

        for (posy=y-stonesize; posy<y+stonesize; posy++)

        {

            if ((posx-x)*(posx-x) + (posy-y)*(posy-y) < stonesize*stonesize)

            {

                waveBuff_0[PIC_WIDTH*posy+posx] += stoneweight;

            }

        }

    }

}



//鼠标移动回调函数

void mouse_move(GtkWidget *widget,GdkEventMotion *event,gpointer data){

    //鼠标移动产生波纹较小

    disturb((int)event->x,(int)event->y,2,222);

}

//鼠标释放回调函数

void mouse_release(GtkWidget *widget,GdkEventButton *event,gpointer data){

    //鼠标点击产生波纹较大

    disturb((int)event->x,(int)event->y,5,2222);

}



int main() {



    gtk_init(NULL, NULL);

    fileName = "bg.jpg";

        //获取图片大小

    pixbuf_0 = gdk_pixbuf_new_from_file(fileName,NULL);

    PIC_WIDTH = gdk_pixbuf_get_width(pixbuf_0);

    PIC_HEIGHT = gdk_pixbuf_get_height(pixbuf_0);

        //建立窗口

    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

    gtk_window_set_default_size(GTK_WINDOW(window), PIC_WIDTH, PIC_HEIGHT);

    gtk_window_set_title(GTK_WINDOW(window), "Demo");

    //初始化波幅数组

    waveBuff_0 = malloc(PIC_HEIGHT*PIC_WIDTH*sizeof(int));

    waveBuff_1 = malloc(PIC_HEIGHT*PIC_WIDTH*sizeof(int));

    memset(waveBuff_0,0,PIC_HEIGHT*PIC_WIDTH*sizeof(int));

    memset(waveBuff_1,0,PIC_HEIGHT*PIC_WIDTH*sizeof(int));

    //为窗体添加鼠标事件,添加回调函数

    gtk_widget_add_events(window,GDK_BUTTON_MOTION_MASK|GDK_BUTTON_RELEASE_MASK);

    g_signal_connect(window,"motion-notify-event",G_CALLBACK(mouse_move),NULL);

    g_signal_connect(window,"button-release-event",G_CALLBACK(mouse_release),NULL);



    gtk_widget_show_all(window);

    g_signal_connect(G_OBJECT(window),"destroy",G_CALLBACK(gtk_main_quit),NULL);

    //启动定时器,实时渲染画面

    timer_0 = g_timeout_add(30,(GSourceFunc)RenderFun,window);

    

    gtk_main();

}


参考VC++代码能够实现水波纹效果,但是gtk与vc的绘图机制不一样,并且涉及到像素的操作。但是从最终效果来看还是达到了目的

对于水平两侧边界的波纹,会互相影响,如右边界的波纹超出的部分会在左侧显示

猜测是因为这个波纹计算算法


buf2[i] = ((buf[i-PIC_WIDTH] + buf[i+PIC_WIDTH] + buf[i-1] + buf[i+1]) >> 1) - buf2[i];

在垂直方向上数组中的像素不是连续的,而水平方向的像素是连续的,因此会彼此影响,因为对于水平边界实际上在数组中是连续存在的