I had a hard time finding a simple and easy-to-use library in Python that would let me draw on PNG images by directly manipulating pixel data. libcairo‘s ImageSurface works well, but the color component ordering is a bit unusual (BGRA) and coordinates have to be manually translated to an array index. You also have to worry about converting data back and forth between bytes (characters) and numeric types. Below you can find a thin wrapper that abstracts away those operations:

class PngImage:
    def __init__(self, surface):
        self.surface = surface;
        self.bpp = 4 # alpha ignored but still present in RGB24
        self.data = self.surface.get_data()    
    def create(cls, w, h):
        return PngImage(ImageSurface(FORMAT_ARGB32, w, h))
    def load(cls, file):
        return PngImage(ImageSurface.create_from_png(file))
    def write(self, file):
    def height(self):
        return self.surface.get_height()
    def width(self):
        return self.surface.get_width()
    def coordinates_to_index(self, x, y):
        return self.bpp*(y*self.width() + x)
    def set_pixel(self, x, y, color, alpha=255):
        (r, g, b) = color
        i = self.coordinates_to_index(x,y)
        d = self.data
        d[i] = chr(b)
        d[i+1] = chr(g)
        d[i+2] = chr(r)
        if self.surface.get_format() == FORMAT_ARGB32:
            d[i+3] = chr(alpha)
    def get_pixel(self, x, y):
        i = self.coordinates_to_index(x,y)
        d = self.data
        return (ord(d[i+2]), ord(d[i+1]), ord(d[i]),
                ord(d[i+3]) if self.surface.get_format() == FORMAT_ARGB32
                            else None)

I hope the methods are self-explanatory. Here’s how PngImage could be used to add a ripple effect to a PNG file:

# returns a ripple magnitude in the range [0.5; 1]
def compute_z(x, y, x0, y0):
    r = sqrt((x-x0)**2 + (y-y0)**2)
    return (3.0 + cos(0.001*r*r))/4.0
if __name__ == "__main__":
    if(len(sys.argv) < 3):
        sys.stderr.write("usage: %s INPUT.PNG OUTPUT.PNG\n" % sys.argv[0])
    src_img = PngImage.load(sys.argv[1])
    dest_img = PngImage.create(src_img.width(), src_img.height())
    # compute and write ripple pixels
    (x0,y0) = (src_img.width()/2, src_img.height()/2)
    for y in range(src_img.height()):
        for x in range(src_img.width()):
            (r, g, b, a) = src_img.get_pixel(x, y)
            z = compute_z(x, y, x0, y0)
            dest_img.set_pixel(x, y, (int(z*r), int(z*g), int(z*b)), a)

The final effect:

Comment now!