水波纹效果 Python 和 Pygame,来自编码训练视频 [英] Water ripple effect Python and Pygame, from coding train video

查看:71
本文介绍了水波纹效果 Python 和 Pygame,来自编码训练视频的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在尝试在 python/pygame 中复制(来自 java,编码火车视频中的 p5:

导入pygamepygame.init()宽度 = 300高度 = 200cols = 宽度行数 = 高度#dampening = 涟漪效应停止的速度.阻尼 = 0.999#保存屏幕颜色的数组.current = [[0]*rows for col in range(cols)]上一个 = [[0]* 范围内列的行数(列)]打印(当前[0][0],当前[199][199])screen = pygame.display.set_mode((width,height))#设置初始背景为黑色screen.fill((0,0,0))img = pygame.Surface((宽,高))#主循环为真:对于 pygame.event.get() 中的事件:如果 event.type == pygame.QUIT:pygame.quit()出口()如果有的话(pygame.mouse.get_pressed()):mouse_pos = pygame.mouse.get_pos()以前的[mouse_pos[0]][mouse_pos[1]] = 500#这部分好像有问题pixelArray = pygame.PixelArray(img)对于范围内的 i (1,cols-1):对于范围内的 j (1,rows-1):当前[i][j] = (上一个[i-1][j] +上一个[i+1][j] +上一个[i][j-1] +前一个[i][j+1])/2 - 当前[i][j]current[i][j] *= 阻尼val = min(255, max(0, round(current[i][j])))pixelArray[i, j] = (val, val, val)pixelArray.close()# 切换数组以前,当前 = 当前,以前screen.blit(img, (0, 0))pygame.display.flip()


要获得真正好的性能,您必须使用

import numpy导入pygame进口scipypygame.init()窗口 = pygame.display.set_mode((300, 300))时钟 = pygame.time.Clock()大小 = window.get_size()阻尼 = 0.999当前 = numpy.zeros(大小,numpy.float32)前一个 = numpy.zeros(大小,numpy.float32)内核 = numpy.array([[0.0, 0.5, 0], [0.5, 0, 0.5], [0, 0.5, 0]])运行 = 真运行时:时钟滴答(60)对于 pygame.event.get() 中的事件:如果 event.type == pygame.QUIT:运行 = 错误如果有的话(pygame.mouse.get_pressed()):mouse_pos = pygame.mouse.get_pos()以前的[鼠标位置] = 1000# 任何一个:# current = (scipy.ndimage.convolve(previous, kernel) - current) * 阻尼# 或者:当前[1:size[0]-1, 1:size[1]-1] = ((上一个[0:size[0]-2, 0:size[1]-2] +上一个[2:size[0], 0:size[1]-2] +上一个[0:size[0]-2, 2:size[1]] +上一个[2:size[0], 2:size[1]])/2 -current[1:size[0]-1, 1:size[1]-1]) * 阻尼数组 = numpy.transpose(255 - numpy.around(numpy.clip(current, 0, 255)))数组 = numpy.repeat(array.reshape(*size, 1).astype('uint8'), 3,axis = 2)图像 = pygame.image.frombuffer(array.flatten(), size, 'RGB')以前,当前 = 当前,以前window.blit(图像, (0, 0))pygame.display.flip()pygame.quit()出口()

I am currently trying to replicate in python/pygame (from java, p5 from the coding train video: https://www.youtube.com/watch?v=BZUdGqeOD0w) the water ripple effect.

I am unable to make it work, the screen stays blank (black: background color) or I gives me the error:

Traceback (most recent call last):
  File "/Users/vicki/Documents/Atom Test/Water Ripple.py", line 39, in <module>
    screen.fill((current[i][j],current[i][j],current[i][j]),(i,j,1,1))
TypeError: invalid color argument

which I actually find to be normal since we substrate a positive number from 0 to get the color of the pixel:

current[i][j] = (previous[i-1][j]+previous[i+1][j]+ previous[i][j+1]+ previous[i][j-1])/ 2 - current[i][j]

The: previous[i-1][j], previous[i+1][j], previous[i][j-1], previous[i][j+1] are all equal to 0, whereas the current[I][j] is a positive number, so logically it should give a negative color value. Maybe it has to do with how the arrays work in Python vs Java?

For more help, here is the website coding train has used to make his video: https://web.archive.org/web/20160418004149/http://freespace.virgin.net/hugo.elias/graphics/x_water.htm

Here is my code:

import pygame

pygame.init()

width = 200
height = 200

cols = width
rows = height

#dampening = how fast the ripple effect stops.
dampening = 0.999


#Arrays that hold the colors of the screen.
current = [[0]*cols]*rows
previous = [[0]*cols]*rows


screen = pygame.display.set_mode((width,height))
#Sets the initial background to black
screen.fill((0,0,0))

#Mainloop
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            exit()
        if event.type == pygame.MOUSEBUTTONDOWN:
            #if the user clicks the screen, it sets the pixel the user clicked upon white
            mouse_pos = pygame.mouse.get_pos()
            current[mouse_pos[0]][mouse_pos[1]] = 255



    #This part seems to be the problem
    for i in range(1,rows-1):
        for j in range(1,cols-1):
            #This computation is weird: we are substracting positive/zero number from a null number which should normally give us a negative number. A color value (RGB) cannot be a negative number!
            current[i][j] = (previous[i-1][j]+previous[i+1][j]+ previous[i][j+1]+ previous[i][j-1])/ 2 - current[i][j]
            current[i][j] *= dampening
            #This line is where it gives me the error
            screen.fill((current[i][j],current[i][j],current[i][j]),(i,j,1,1))

    #Switching the arrays, have also tried: previous, current = current, previous (doesn't work either)
    temp = previous
    previous = current
    current = temp
    pygame.display.flip()

Here is the code of coding train (in java P5): (Note: he used mousedraft and not mouseTouch (in the video he uses mousetouch) but it isn't important.)

// Daniel Shiffman
// http://codingtra.in
// http://patreon.com/codingtrain

// 2D Water Ripples
// Video: https://youtu.be/BZUdGqeOD0w
// Algorithm: https://web.archive.org/web/20160418004149/http://freespace.virgin.net/hugo.elias/graphics/x_water.htm

int cols;
int rows;
float[][] current;// = new float[cols][rows];
float[][] previous;// = new float[cols][rows];

float dampening = 0.99;

void setup() {
  size(600, 400);
  cols = width;
  rows = height;
  current = new float[cols][rows];
  previous = new float[cols][rows];
}

void mouseDragged() {
  previous[mouseX][mouseY] = 500;
}

void draw() {
  background(0);

  loadPixels();
  for (int i = 1; i < cols-1; i++) {
    for (int j = 1; j < rows-1; j++) {
      current[i][j] = (
        previous[i-1][j] + 
        previous[i+1][j] +
        previous[i][j-1] + 
        previous[i][j+1]) / 2 -
        current[i][j];
      current[i][j] = current[i][j] * dampening;
      int index = i + j * cols;
      pixels[index] = color(current[i][j]);
    }
  }
  updatePixels();

  float[][] temp = previous;
  previous = current;
  current = temp;
}

解决方案

First of all

current = [[0]*cols]*rows
previous = [[0]*cols]*rows

is not a 2 dimensional array. It generate a single list and than a outer list is generated where each element refers to the same inner list.

If you want to generate a list, where each element is a separate list, then you have to:
(The inner dimension should be rows e.g. current[col][row]/current[x][y])

current = [[0]*rows for col in range(cols)]
previous = [[0]*rows for col in range(cols)]


In pygame the values for the color channel have to be integral values in range [0, 255]:

val = min(255, max(0, round(current[i][j])))
screen.fill((val, val, val), (i,j,1,1)) 


Furthermore the MOUSEBUTTONDOWN event occurs only once when a mouse button is pressed. u have to evaluate if the current state of the mouse is pressed by pygame.mouse.get_pressed():

while True:
    # [...]
    
    if any(pygame.mouse.get_pressed()):
        mouse_pos = pygame.mouse.get_pos()
        previous[mouse_pos[0]][mouse_pos[1]] = 500


I recommend to use pygame.Surface respectively pygame.PixelArray to improve the performance. That's similar loadPixels()/updatePixels() in Processing:

img = pygame.Surface((width, height))
while True:
    # [...]

    pixelArray = pygame.PixelArray(img)
    for i in range(1,cols-1):
        for j in range(1,rows-1):
            current[i][j] = (
                previous[i-1][j] + 
                previous[i+1][j] +
                previous[i][j-1] + 
                previous[i][j+1]) / 2 - current[i][j]
            current[i][j] *= dampening
            val = min(255, max(0, round(current[i][j])))
            pixelArray[i, j] = (val, val, val)
    pixelArray.close()
    screen.blit(img, (0, 0))


Sadly that all is still incredibly slow. Full example:

import pygame
    
pygame.init()

width = 300
height = 200

cols = width
rows = height

#dampening = how fast the ripple effect stops.
dampening = 0.999

#Arrays that hold the colors of the screen.
current = [[0]*rows for col in range(cols)]
previous = [[0]*rows for col in range(cols)]

print(current[0][0], current[199][199])

screen = pygame.display.set_mode((width,height))
#Sets the initial background to black
screen.fill((0,0,0))

img = pygame.Surface((width, height))

#Mainloop
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            exit()         

    if any(pygame.mouse.get_pressed()):
        mouse_pos = pygame.mouse.get_pos()
        previous[mouse_pos[0]][mouse_pos[1]] = 500

    #This part seems to be the problem
    pixelArray = pygame.PixelArray(img)
    for i in range(1,cols-1):
        for j in range(1,rows-1):
            current[i][j] = (
                previous[i-1][j] + 
                previous[i+1][j] +
                previous[i][j-1] + 
                previous[i][j+1]) / 2 - current[i][j]
            current[i][j] *= dampening
            val = min(255, max(0, round(current[i][j])))
            pixelArray[i, j] = (val, val, val)
    pixelArray.close()

    # Switching the arrays
    previous, current = current, previous

    screen.blit(img, (0, 0))
    pygame.display.flip()


For really good performance, you must be using NumPy. In the following example I have throttled the frames per second with pygame.time.Clock.tick:

import numpy
import pygame
import scipy
pygame.init()

window = pygame.display.set_mode((300, 300))
clock = pygame.time.Clock()

size = window.get_size()
dampening = 0.999

current = numpy.zeros(size, numpy.float32)
previous = numpy.zeros(size, numpy.float32)
kernel = numpy.array([[0.0, 0.5, 0], [0.5, 0, 0.5], [0, 0.5, 0]])

run = True
while run:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False    
        
    if any(pygame.mouse.get_pressed()):
        mouse_pos = pygame.mouse.get_pos()
        previous[mouse_pos] = 1000

    # either:
    # current = (scipy.ndimage.convolve(previous, kernel) - current) * dampening
    # or:
    current[1:size[0]-1, 1:size[1]-1] = (
        (previous[0:size[0]-2, 0:size[1]-2] + 
         previous[2:size[0], 0:size[1]-2] + 
         previous[0:size[0]-2, 2:size[1]] + 
         previous[2:size[0], 2:size[1]]) / 2 - 
        current[1:size[0]-1, 1:size[1]-1]) * dampening

    array = numpy.transpose(255 - numpy.around(numpy.clip(current, 0, 255)))
    array = numpy.repeat(array.reshape(*size, 1).astype('uint8'), 3, axis = 2)
    image = pygame.image.frombuffer(array.flatten(), size, 'RGB')

    previous, current = current, previous

    window.blit(image, (0, 0))
    pygame.display.flip()

pygame.quit()
exit()    

这篇关于水波纹效果 Python 和 Pygame,来自编码训练视频的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆