Title: Flames Example

Author: Pete Shinners (pete at shinners.org)
Submission date: April 03, 2001

Description: Example of a realtime fire effect. Makes heavy use of Numeric python and the surfarray module in pygame.

Download: flames.zip

pygame version required: Any (Numeric required)
SDL version required: Any
Python version required: Any

Comments: This submission reminds us of a few important optimization techniques. First, use the right tool for the job. Pete is doing a recursive blur, a per-pixel activity. The surfarray extensions to pygame are almost mandatory here - you could do it with get_at() and set_at(), but the cost in speed would be prohibitive. Second, do your work in integers where possible. Python doesn't free you from the costs associated with floating-point mathematics. Finally, it's often possible to create an effect and then scale it up for display. The results may not be as precise as a full-sized effect, but the speed will be much greater, and for some effects (blur comes to mind), the difference won't be visible.

The code has been updated - it is optimized further, and the new speed improvements have allowed the resolution to be increased.

Messages: 5


#!/usr/bin/env python

"""flames.py - Realtime Fire Effect Demo
Pete Shinners, April 3, 2001

On December 6, 60% speedup by using the pygame.transform.scale
function instead of doing a simple Numeric scale.

Ok, this is a pretty intense demonstation of using
the surfarray module and numeric. It uses an 8bit
surfaces with a colormap to represent the fire. We
then go crazy on a separate Numeric array to get
realtime fire. I'll try to explain my methods here...

This flame algorithm is very popular, and you can find
it for just about all graphic libraries. The fire effect
works by placing random values on the bottom row of the
image. Then doing a simplish blur that is weighted to
move the values upward. Then slightly darken the image
so the colors get darker as they move up. The secret is
that the blur routine is "recursive" so when you blur
the 2nd row, the values there are used when you blur
the 3rd row, and all the way up.

This fire algorithm works great, but the bottom rows
are usually a bit ugly. In this demo we just render
a fire surface that has 3 extra rows at the bottom we
just don't use.

Also, the fire is rendered at half the resolution of
the full image. We then simply double the size of the
fire data before applying to the surface.

Several of these techniques are covered in the pygame
surfarray tutorial. doubling an image, and the fancy
blur is just a modified version of what is in the tutorial.

This runs at about 40fps on my celeron-400
"""


import pygame, pygame.transform
from pygame.surfarray import *
from pygame.locals import *
from Numeric import *
from RandomArray import *

RES = array((280, 200))
MAX = 246
RESIDUAL = 86
HSPREAD, VSPREAD = 26, 78
VARMIN, VARMAX = -2, 3

def main():
    "main function called when the script is run"
    #first we just init pygame and create some empty arrays to work with    
    pygame.init()
    screen = pygame.display.set_mode(RES, 0, 8)
    setpalette(screen)
    flame = zeros(RES/2 + (0,3))
    miniflame = pygame.Surface((RES[0]/2, RES[1]/2), 0, 8)
    miniflame.set_palette(screen.get_palette())
    randomflamebase(flame)    

    while 1:
        for e in pygame.event.get():
            if e.type in (QUIT,KEYDOWN,MOUSEBUTTONDOWN):
                return
            
        modifyflamebase(flame)
        processflame(flame)
        blitdouble(screen, flame, miniflame)
        pygame.display.flip()



def setpalette(screen):
    "here we create a numeric array for the colormap"
    gstep, bstep = 75, 150
    cmap = zeros((256, 3))
    cmap[:,0] = minimum(arange(256)*3, 255)
    cmap[gstep:,1] = cmap[:-gstep,0]
    cmap[bstep:,2] = cmap[:-bstep,0]
    screen.set_palette(cmap)


def randomflamebase(flame):
    "just set random values on the bottom row"
    flame[:,-1] = randint(0, MAX, flame.shape[0])


def modifyflamebase(flame):
    "slightly change the bottom row with random values"
    bottom = flame[:,-1]
    mod = randint(VARMIN, VARMAX, bottom.shape[0])
    add(bottom, mod, bottom)
    maximum(bottom, 0, bottom)
    #if values overflow, reset them to 0
    bottom[:] = choose(greater(bottom,MAX), (bottom,0))


def processflame(flame):
    "this function does the real work, tough to follow"
    notbottom = flame[:,:-1]    

    #first we multiply by about 60%
    multiply(notbottom, 146, notbottom)
    right_shift(notbottom, 8, notbottom)

    #work with flipped image so math accumulates.. magic!
    flipped = flame[:,::-1]

    #all integer based blur, pulls image up too
    tmp = flipped * 20
    right_shift(tmp, 8, tmp)
    tmp2 = tmp >> 1
    add(flipped[1:,:], tmp2[:-1,:], flipped[1:,:])
    add(flipped[:-1,:], tmp2[1:,:], flipped[:-1,:])
    add(flipped[1:,1:], tmp[:-1,:-1], flipped[1:,1:])
    add(flipped[:-1,1:], tmp[1:,:-1], flipped[:-1,1:])

    tmp = flipped * 80
    right_shift(tmp, 8, tmp)
    add(flipped[:,1:], tmp[:,:-1]>>1, flipped[:,1:])
    add(flipped[:,2:], tmp[:,:-2], flipped[:,2:])

    #make sure no values got too hot
    minimum(notbottom, MAX, notbottom)


def blitdouble(screen, flame, miniflame):
    "double the size of the data, and blit to screen"
    blit_array(miniflame, flame[:,:-3])
    s2 = pygame.transform.scale(miniflame, screen.get_size())
    screen.blit(s2, (0,0))


if __name__ == '__main__': main()


From: Anonymous

Date: March 11, 2003 20:16 GMT

Using Active Python2.2 Install, Pygame Binary for Windows (2000). I get the following error message:

C:\dev\pygame\flames>python flames.py
536870912
Traceback (most recent call last):
File "flames.py", line 134, in ?
if __name__ == '__main__': main()
File "flames.py", line 66, in main
blitdouble(screen, flame, doubleflame)
File "flames.py", line 130, in blitdouble
blit_array(screen, doubleflame)
ValueError: unsupported datatype for array

 

From: Anonymous

Date: May 16, 2003 17:36 GMT

 

From: weenus!

Date: November 04, 2003 01:58 GMT

uh-hyuh! This thang is vewy coow!
nice flame technique!

 

From: Dave

Date: February 05, 2004 01:38 GMT

Thanks! I like the effect.

I had some additional luck speeding up the algorithm:

It appears to work faster (at least for me) if I use a call to
"pygame.transform.scale2x()" in the blitdouble function.

I also got a noticable speed increase by adding the lines:
import psyco; psyco.full()
to the top of the file. (Of course, you'd have to have pysco installed and
be running on an i386, but I find that this is a wonderful tool for making
python go faster --often twice or more, but it depends.)

By the way, the image doubling _looks_ much better if you use
pygame.transform.rotozoom(image, 0.0, 2.0) to scale the image.
(You get a smooth scaling effect), but the speed suffers greatly.

 

From: Anonymous

Date: March 01, 2004 11:40 GMT

aren't there decent convultion operations on 2d matrices in numeric?

 

Main - Repository - Submit - News

Feedback