Pygame Tutorials
Surfarray Introduction

by Pete Shinners
pete@shinners.org

Revision 1.02, Sep 7, 2001



Introduction

This tutorial will attempt to introduce users to both Numeric and the pygame Surfarray module. To beginners, the code that uses surfarray can be quite intimidating. But actually there are only a few concepts to understand and you will be up and running. Using the surfarray module, it becomes possible to perform pixel level operations from straight python code. The performance can become quite close to the level of doing the code in C.
 
You may just want to jump down to the "Examples" section to get an idea of what is possible with this module, then start at the beginning here to work your way up.
 
Now I won't try to fool you into thinking everything is very easy. To get more advanced effects by modifying pixel values is very tricky. Just mastering Numeric Python takes a lot of learning. In this tutorial I'll be sticking with the basics and using a lot of examples in an attempt to plant seeds of wisdom. After finishing the tutorial you should have a basic handle on how the surfarray works.

Numeric Python

If you do not have the python Numeric package installed, you will need to do that now. You can download the package from the Numeric Downloads Page. To make sure Numeric is working for you, you should get something like this from the interactive python prompt.
>>> from Numeric import *                  #import numeric
>>> a = array((1,2,3,4,5))                 #create an array
>>> a                                      #display the array
array([1, 2, 3, 4, 5])
>>> a[2]                                   #index into the array
3
>>> a*2                                    #new array with twiced values
array([ 2,  4,  6,  8, 10])

As you can see, the Numeric module gives us a new data type, the array. This object holds an array of fixed size, and all values inside are of the same type. The arrays can also be multidimensional, which is how we will use them with images. There's a bit more to it than this, but it is enough to get us started.
 
If you look at the last command above, you'll see that mathematical operations on Numeric arrays apply to all values in the array. This is called "elementwise operations". These arrays can also be sliced like normal lists. The slicing syntax is the same as used on standard python objects. (so study up if you need to :] ). Here are some more examples of working with arrays.
>>> len(a)                                 #get array size
5
>>> a[2:]                                  #elements 2 and up
array([3, 4, 5])
>>> a[:-2]                                 #all except last 2
array([1, 2, 3])
>>> a[2:] + a[:-2]                         #add first and last
array([4, 6, 8])
>>> array((1,2,3)) + array((3,4))          #add arrays of wrong sizes
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
ValueError: frames are not aligned

We get an error on the last commend, because we try add together two arrays that are different sizes. In order for two arrays two operate with each other, including comparisons and assignment, they must have the same dimensions. It is very important to know that the new arrays created from slicing the original all reference the same values. So changing the values in a slice also changes the original values. It is important how this is done.
>>> a                                      #show our starting array
array([1, 2, 3, 4, 5])
>>> aa = a[1:3]                            #slice middle 2 elements
>>> aa                                     #show the slice
array([2, 3])
>>> aa[1] = 13                             #chance value in slice
>>> a                                      #show change in original
array([ 1, 2, 13,  4,  5])
>>> aaa = array(a)                         #make copy of array
>>> aaa                                    #show copy
array([ 1, 2, 13,  4,  5])
>>> aaa[1:4] = 0                           #set middle values to 0
>>> aaa                                    #show copy
array([1, 0, 0, 0, 5])
>>> a                                      #show original again
array([ 1, 2, 13,  4,  5])

Now we will look at small arrays with two dimensions. Don't be too worried, getting started it is the same as having a two dimensional tuple (a tuple inside a tuple). Let's get started with two dimensional arrays.
>>> row1 = (1,2,3)                         #create a tuple of vals
>>> row2 = (3,4,5)                         #another tuple
>>> (row1,row2)                            #show as a 2D tuple
((1, 2, 3), (3, 4, 5))
>>> b = array((row1, row2))                #create a 2D array
>>> b                                      #show the array
array([[1, 2, 3],
       [3, 4, 5]])
>>> array(((1,2),(3,4),(5,6)))             #show a new 2D array
array([[1, 2],
       [3, 4],
       [5, 6]])

Now with this two dimensional array (from now on as "2D") we can index specific values and do slicing on both dimensions. Simply using a comma to separate the indices allows us to lookup/slice in multiple dimensions. Just using ":" as an index (or not supplying enough indices) gives us all the values in that dimension. Let's see how this works.
>>> b                                      #show our array from above
array([[1, 2, 3],
       [3, 4, 5]])
>>> b[0,1]                                 #index a single value
2
>>> b[1,:]                                 #slice second row
array([3, 4, 5])
>>> b[1]                                   #slice second row (same as above)
array([3, 4, 5])
>>> b[:,2]                                 #slice last column
array([3, 5])
>>> b[:,:2]                                #slice into a 2x2 array
array([[1, 2],
       [3, 4]])

Ok, stay with me here, this is about as hard as it gets. When using Numeric there is one more feature to slicing. Slicing arrays also allow you to specify a slice increment. The syntax for a slice with increment is start_index : end_index : increment.
>>> c = arange(10)                         #like range, but makes an array
>>> c                                      #show the array
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> c[1:6:2]                               #slice odd values from 1 to 6
array([1, 3, 5])
>>> c[4::4]                                #slice every 4th val starting at 4
array([4, 8])
>>> c[8:1:-1]                              #slice 1 to 8, reversed
array([8, 7, 6, 5, 4, 3, 2])

Well that is it. There's enough information there to get you started using Numeric with the surfarray module. There's certainly a lot more to Numeric, but this is only an introduction. Besides, we want to get on to the fun stuff, correct?
 

Import Surfarray

In order to use the surfarray module we need to import it. Since both surfarray and Numeric are optional components for pygame, it is nice to make sure they import correctly before using them. In these examples I'm going to import Numeric into a variable named N. This will let you know which functions I'm using are from the Numeric package. (and is a lot shorter than typing Numeric before each function)
try:
    import Numeric as N
    import pygame.surfarray as surfarray
except ImportError:
    raise ImportError, "Numeric and Surfarray are required."


 

Surfarray Introduction

There are two main types of functions in surfarray. One set of functions for creating an array that is a copy of a surface pixel data. The other functions create a referenced copy of the array pixel data, so that changes to the array directly affect the original surface. There are other functions that allow you to access any per-pixel alpha values as arrays along with a few other helpful functions. We will look at these other functions later on.
 
When working with these surface arrays, there are two ways of representing the pixel values. First, they can be represented as mapped integers. This type of array is a simple 2D array with a single integer representing the surface's mapped color value. This type of array is good for moving parts of an image around. The other type of array uses three RGB values to represent each pixel color. This type of array makes it extremely simple to do types of effects that change the color of each pixel. This type of array is