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() @classmethod def create(cls, w, h): return PngImage(ImageSurface(FORMAT_ARGB32, w, h)) @classmethod def load(cls, file): return PngImage(ImageSurface.create_from_png(file)) def write(self, file): self.surface.write_to_png(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]) exit(1) 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) dest_img.write(sys.argv[2]) |
The final effect:
→ |
Trackbacks