Fast Pixel Ops in GIMP-Python
Last night Joao and I were on IRC helping someone who was learning to write gimp plug-ins. We got to talking about pixel operations and how to do them in Python. I offered my arclayer.py as an example of using pixel regions in gimp, but added that C is a lot faster for pixel operations. I wondered if reading directly from the tiles (then writing to a pixel region) might be faster.
But Joao knew a still faster way. As I understand it, one major reason Python is slow at pixel region operations compared to a C plug-in is that Python only writes to the region one pixel at a time, while C can write batches of pixels by row, column, etc. But it turns out you can grab a whole pixel region into a Python array, manipulate it as an array then write the whole array back to the region. He thought this would probably be quite a bit faster than writing to the pixel region for every pixel.
He showed me how to change the arclayer.py code to use arrays, and I tried it on a few test layers. Was it faster? I made a test I knew would take a long time in arclayer, a line of text about 1500 pixels wide. Tested it in the old arclayer; it took just over a minute to calculate the arc. Then I tried Joao's array version: timing with my wristwatch stopwatch, I call it about 1.7 seconds. Wow! That might be faster than the C version.
The updated, fast version (0.3) of arclayer.py is on my arclayer page.
If you just want the trick to using arrays, here it is:
from array import array [ ... setting up ... ] # initialize the regions and get their contents into arrays: srcRgn = layer.get_pixel_rgn(0, 0, srcWidth, srcHeight, False, False) src_pixels = array("B", srcRgn[0:srcWidth, 0:srcHeight]) dstRgn = destDrawable.get_pixel_rgn(0, 0, newWidth, newHeight, True, True) p_size = len(srcRgn[0,0]) dest_pixels = array("B", "\x00" * (newWidth * newHeight * p_size)) [ ... then inside the loop over x and y ... ] src_pos = (x + srcWidth * y) * p_size dest_pos = (newx + newWidth * newy) * p_size newval = src_pixels[src_pos: src_pos + p_size] dest_pixels[dest_pos : dest_pos + p_size] = newval [ ... when the loop is all finished ... ] # Copy the whole array back to the pixel region: dstRgn[0:newWidth, 0:newHeight] = dest_pixels.tostring()
Good stuff!
[ 22:02 Aug 16, 2008 More gimp | permalink to this entry | ]